{ "id": "113478", "key": "TIMOB-13747", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [], "resolution": { "id": "7", "description": "", "name": "Invalid" }, "resolutiondate": "2020-06-30T14:46:33.000+0000", "created": "2013-04-28T16:12:43.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [], "versions": [], "issuelinks": [], "assignee": null, "updated": "2020-06-30T14:46:33.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" } ], "description": "h2. Problem\r\n\r\nMemory leak when ListView template contains an event listener.\r\n\r\nTitanium SDK 3.2.0 (HEAD)\r\nAndroid 4.2.1\r\nNexus S\r\n\r\nOnly tested this on Android. Maybe it should be checked if this leak also occurs on iOS.\r\n\r\n\r\nh2. Test case\r\n\r\nThe code for the test case is taken from the ListItem example in the documentation. It has been extended, so that the window with the list view \r\n- can be opened and closed as to expose the memory leak\r\n- is a heavyweight window\r\n\r\n{code:lang=javascript|title=app.js}\r\nvar rootWin = Ti.UI.createWindow({});\r\nvar button = Ti.UI.createButton({\r\n\ttitle: 'Open'\r\n});\r\n\r\nbutton.addEventListener('click', function(evt){\r\n\t\r\n\tvar win = Ti.UI.createWindow({backgroundColor: 'white', modal: false });\r\n\r\n\tvar plainTemplate = {\r\n\t childTemplates: [\r\n\t { // Image justified left\r\n\t type: 'Ti.UI.ImageView', // Use an image view for the image\r\n\t bindId: 'pic', // Maps to a custom pic property of the item data\r\n\t properties: { // Sets the image view properties\r\n\t width: '50dp', height: '50dp', left: 0\r\n\t }\r\n\t },\r\n\t { // Title\r\n\t type: 'Ti.UI.Label', // Use a label for the title\r\n\t bindId: 'title', // Maps to a custom title property of the item data\r\n\t properties: { // Sets the label properties\r\n\t color: 'black',\r\n\t font: { fontFamily:'Arial', fontSize: '20dp', fontWeight:'bold' },\r\n\t left: '60dp', top: 0,\r\n\t },\r\n\t },\r\n\t { // Subtitle\r\n\t type: 'Ti.UI.Label', // Use a label for the subtitle\r\n\t bindId: 'subtitle', // Maps to a custom subtitle property of the item data\r\n\t properties: { // Sets the label properties\r\n\t color: 'gray',\r\n\t font: { fontFamily:'Arial', fontSize: '14dp' },\r\n\t left: '60dp', top: '25dp',\r\n\t }\r\n\t }\r\n\t ],\r\n\t // Binds a callback to the click event, which catches events bubbled by the view subcomponents.\r\n\t events: {click: toggleCheck }\r\n\t};\r\n\r\n\t// The following API calls are equivalent to using jQuery.extend(true, {}, oldObject)\r\n\t// Copy the plainTemplate\r\n\tvar redTemplate = JSON.parse(JSON.stringify(plainTemplate));\r\n\t// Change the text color to red\r\n\tredTemplate.childTemplates[1].properties.color = 'red';\r\n\tredTemplate.childTemplates[2].properties.color = 'red';\r\n\t// Rebind the click event callback\r\n\tredTemplate.events.click = toggleCheck;\r\n\r\n\tvar listView = Ti.UI.createListView({\r\n\t // Maps plainTemplate to 'uncheck' and redTemplate to 'check' \r\n\t templates: { 'uncheck': plainTemplate, 'check': redTemplate },\r\n\t // Use 'uncheck', that is, the plainTemplate created earlier for all items\r\n\t // Can be overridden by the item's template property\r\n\t defaultItemTemplate: 'uncheck'\r\n\t});\r\n\r\n\tvar tasks = [\r\n\t {id: 'trash', name: 'Take Out the Trash', person: 'Yakko', icon: 'trash.png'},\r\n\t {id: 'dishes', name: 'Do the Dishes', person: 'Wakko', icon: 'dishes.png'},\r\n\t {id: 'doggie', name: 'Walk the Dog', person: 'Dot', icon: 'doggie.png'}\r\n\t];\r\n\r\n\tvar data = [];\r\n\tfor (var i = 0; i < tasks.length; i++) {\r\n\t data.push({\r\n\t // Maps to the title component in the template\r\n\t // Sets the text property of the Label component\r\n\t title : { text: tasks[i].name },\r\n\t // Maps to the subtitle component in the template\r\n\t // Sets the text property of the Label component\r\n\t subtitle : { text : tasks[i].person },\r\n\t // Maps to the pic component in the template\r\n\t // Sets the image property of the ImageView component\r\n\t pic : { image : tasks[i].icon },\r\n\t // Sets the regular list data properties\r\n\t properties : {\r\n\t itemId: tasks[i].id,\r\n\t accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_NONE,\r\n\t }\r\n\t });\r\n\t}\r\n\r\n\tvar section = Ti.UI.createListSection();\r\n\tsection.setItems(data);\r\n\tlistView.sections = [section];\r\n\r\n\t// Modified version of the `itemclick` event listener\r\n\t// Changes the item template rather than the list item's color property\r\n\tfunction toggleCheck (e) {\r\n\t var item = section.getItemAt(e.itemIndex);\r\n\t if (item.properties.accessoryType == Ti.UI.LIST_ACCESSORY_TYPE_NONE) {\r\n\t item.properties.accessoryType = Ti.UI.LIST_ACCESSORY_TYPE_CHECKMARK;\r\n\t item.template = 'check';\r\n\t }\r\n\t else {\r\n\t item.properties.accessoryType = Ti.UI.LIST_ACCESSORY_TYPE_NONE;\r\n\t item.template = 'uncheck';\r\n\t }\r\n\t section.updateItemAt(e.itemIndex, item);\r\n\t} \r\n\r\n\twin.add(listView);\r\n\twin.open();\r\n\t\r\n\t\r\n});\r\n\r\nrootWin.add(button);\r\nrootWin.open();\r\n{code}\r\n\r\nSteps to reproduce:\r\n1. Start the application\r\n2. Create a heap dump in DDMS\r\n3. Tap the 'open' button\r\n4. Tap the back button\r\n5. Cause GC\r\n6. Create a second heap dump in DDMS\r\n7. Do a comparison of the two heap dumps with Eclipse Memory Analyzer. The second heap dump has multiple new and living objects compared to the first heap dump, e.g. TiBaseWindowProxy, ListViewProxy, etc. This does not happen if the event listener is removed in the template.", "attachment": [], "flagged": false, "summary": "Android: Memory leak when ListView template contains an event listener", "creator": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "subtasks": [], "reporter": { "name": "philet", "key": "philet", "displayName": "Philippe Wueger", "active": true, "timeZone": "Europe/Berlin" }, "environment": null, "closedSprints": [ { "id": 50, "state": "closed", "name": "2014 Sprint 09 Tooling", "startDate": "2014-04-28T15:43:24.381Z", "endDate": "2014-05-10T03:44:00.000Z", "completeDate": "2014-05-12T13:36:16.254Z", "originBoardId": 113 }, { "id": 105, "state": "closed", "name": "2014 Sprint 11 SDK", "startDate": "2014-05-27T21:43:32.338Z", "endDate": "2014-06-07T00:00:00.000Z", "completeDate": "2014-06-09T21:36:37.587Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "257021", "author": { "name": "leoleal", "key": "leoleal", "displayName": "Leonardo Leal", "active": true, "timeZone": "America/Sao_Paulo" }, "body": "Memory Leak occurs on iOS as well when using events on gridTemplates.", "updateAuthor": { "name": "leoleal", "key": "leoleal", "displayName": "Leonardo Leal", "active": true, "timeZone": "America/Sao_Paulo" }, "created": "2013-06-10T21:19:41.000+0000", "updated": "2013-06-10T21:19:41.000+0000" }, { "id": "279218", "author": { "name": "hpham", "key": "hpham", "displayName": "Hieu Pham", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Memory is leaking because of a circular reference between section and list item (via 'toggleCheck' function). There are two possible fixes for this:\r\n1. Break the circular reference from Java side - i.e clean up event listeners when the list view is removed from view/window.\r\n2. Break the circular reference from Javascript side.\r\n\r\nOption 1 would cause ListViews to stop working properly with ScrollableView since ScrollableView remove and re-add children as we flip 'pages'. Our current architecture makes it difficult to add event listeners on Java side.\r\nOption 2 is quite simple. Instead of using 'section' in 'toggleCheck' function, simply use 'e.section'. This would break the circular reference and memory would not leak. \r\n", "updateAuthor": { "name": "hpham", "key": "hpham", "displayName": "Hieu Pham", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2013-11-12T21:53:27.000+0000", "updated": "2013-11-12T21:54:08.000+0000" }, { "id": "303718", "author": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~bhatfield] we're going to move this to the backlog. Can we get the workaround in the docs?", "updateAuthor": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-05-06T22:12:37.000+0000", "updated": "2014-05-06T22:12:37.000+0000" }, { "id": "433592", "author": { "name": "cliff_stander", "key": "cliff_stander", "displayName": "Mike Stancliffe", "active": true, "timeZone": "America/Los_Angeles" }, "body": "The work around does not seem to work, even if I have an empty function for the event it causes a leak. One step further if I have more than one template and only one of the templates has an event listener on it, even if it is never applied, it leaks, I have seen it for iOS and Android", "updateAuthor": { "name": "cliff_stander", "key": "cliff_stander", "displayName": "Mike Stancliffe", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2018-01-24T16:56:00.000+0000", "updated": "2018-01-24T16:56:00.000+0000" } ], "maxResults": 4, "total": 4, "startAt": 0 } } }