Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-13747] Android: Memory leak when ListView template contains an event listener

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionInvalid
Resolution Date2020-06-30T14:46:33.000+0000
Affected Version/sn/a
Fix Version/sn/a
ComponentsAndroid
Labelsn/a
ReporterPhilippe Wueger
AssigneeUnknown
Created2013-04-28T16:12:43.000+0000
Updated2020-06-30T14:46:33.000+0000

Description

Problem

Memory leak when ListView template contains an event listener. Titanium SDK 3.2.0 (HEAD) Android 4.2.1 Nexus S Only tested this on Android. Maybe it should be checked if this leak also occurs on iOS.

Test case

The 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 - can be opened and closed as to expose the memory leak - is a heavyweight window
var rootWin = Ti.UI.createWindow({});
var button = Ti.UI.createButton({
	title: 'Open'
});

button.addEventListener('click', function(evt){
	
	var win = Ti.UI.createWindow({backgroundColor: 'white', modal: false });

	var plainTemplate = {
	    childTemplates: [
	        {                            // Image justified left
	            type: 'Ti.UI.ImageView', // Use an image view for the image
	            bindId: 'pic',           // Maps to a custom pic property of the item data
	            properties: {            // Sets the image view properties
	                width: '50dp', height: '50dp', left: 0
	            }
	        },
	        {                            // Title
	            type: 'Ti.UI.Label',     // Use a label for the title
	            bindId: 'title',         // Maps to a custom title property of the item data
	            properties: {            // Sets the label properties
	                color: 'black',
	                font: { fontFamily:'Arial', fontSize: '20dp', fontWeight:'bold' },
	                left: '60dp', top: 0,
	            },
	        },
	        {                            // Subtitle
	            type: 'Ti.UI.Label',     // Use a label for the subtitle
	            bindId: 'subtitle',      // Maps to a custom subtitle property of the item data
	            properties: {            // Sets the label properties
	                color: 'gray',
	                font: { fontFamily:'Arial', fontSize: '14dp' },
	                left: '60dp', top: '25dp',
	            }
	        }
	    ],
	    // Binds a callback to the click event, which catches events bubbled by the view subcomponents.
	    events: {click: toggleCheck }
	};

	// The following API calls are equivalent to using jQuery.extend(true, {}, oldObject)
	// Copy the plainTemplate
	var redTemplate = JSON.parse(JSON.stringify(plainTemplate));
	// Change the text color to red
	redTemplate.childTemplates[1].properties.color = 'red';
	redTemplate.childTemplates[2].properties.color = 'red';
	// Rebind the click event callback
	redTemplate.events.click = toggleCheck;

	var listView = Ti.UI.createListView({
	    // Maps plainTemplate to 'uncheck' and redTemplate to 'check' 
	    templates: { 'uncheck': plainTemplate, 'check': redTemplate },
	    // Use 'uncheck', that is, the plainTemplate created earlier for all items
	    // Can be overridden by the item's template property
	    defaultItemTemplate: 'uncheck'
	});

	var tasks = [
	    {id: 'trash', name: 'Take Out the Trash', person: 'Yakko', icon: 'trash.png'},
	    {id: 'dishes', name: 'Do the Dishes', person: 'Wakko', icon: 'dishes.png'},
	    {id: 'doggie', name: 'Walk the Dog', person: 'Dot', icon: 'doggie.png'}
	];

	var data = [];
	for (var i = 0; i < tasks.length; i++) {
	    data.push({
	        // Maps to the title component in the template
	        // Sets the text property of the Label component
	        title : { text: tasks[i].name },
	        // Maps to the subtitle component in the template
	        // Sets the text property of the Label component
	        subtitle : { text : tasks[i].person },
	        // Maps to the pic component in the template
	        // Sets the image property of the ImageView component
	        pic : { image : tasks[i].icon },
	        // Sets the regular list data properties
	        properties : {
	            itemId: tasks[i].id,
	            accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_NONE,
	        }
	    });
	}

	var section = Ti.UI.createListSection();
	section.setItems(data);
	listView.sections = [section];

	// Modified version of the itemclick event listener
	// Changes the item template rather than the list item's color property
	function toggleCheck (e) {
	    var item = section.getItemAt(e.itemIndex);
	    if (item.properties.accessoryType == Ti.UI.LIST_ACCESSORY_TYPE_NONE) {
	        item.properties.accessoryType = Ti.UI.LIST_ACCESSORY_TYPE_CHECKMARK;
	        item.template = 'check';
	    }
	    else {
	        item.properties.accessoryType = Ti.UI.LIST_ACCESSORY_TYPE_NONE;
	        item.template = 'uncheck';
	    }
	    section.updateItemAt(e.itemIndex, item);
	} 

	win.add(listView);
	win.open();
	
	
});

rootWin.add(button);
rootWin.open();
Steps to reproduce: 1. Start the application 2. Create a heap dump in DDMS 3. Tap the 'open' button 4. Tap the back button 5. Cause GC 6. Create a second heap dump in DDMS 7. 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.

Comments

  1. Leonardo Leal 2013-06-10

    Memory Leak occurs on iOS as well when using events on gridTemplates.
  2. Hieu Pham 2013-11-12

    Memory is leaking because of a circular reference between section and list item (via 'toggleCheck' function). There are two possible fixes for this: 1. Break the circular reference from Java side - i.e clean up event listeners when the list view is removed from view/window. 2. Break the circular reference from Javascript side. Option 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. Option 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.
  3. Ingo Muschenetz 2014-05-06

    [~bhatfield] we're going to move this to the backlog. Can we get the workaround in the docs?
  4. Mike Stancliffe 2018-01-24

    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

JSON Source