Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-26225] Android: TableView leaks memory when overwriting "data" property

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionFixed
Resolution Date2018-07-31T18:20:38.000+0000
Affected Version/sn/a
Fix Version/sRelease 7.2.1
ComponentsAndroid
LabelsTableView, android, memoryleak
ReporterJoshua Quick
AssigneeGary Mathews
Created2018-07-21T07:41:58.000+0000
Updated2020-08-31T16:14:21.000+0000

Description

*Summary:* Replacing a TableView's "data" array property causes a memory leak where the previous array of rows do not get garbage collected. *Steps to reproduce:* _(Below requires an Android emulator and Chrome web browser to be installed.)_

Create a "classic" Titanium project with the below code.

Go to the project directory at the command line.

Enter: appc ti build -p android --debug-host /127.0.0.1:51388

Wait for the app to launch. It'll "hang" on the splash screen.

In the log, copy the the "chrome-devtools://devtools/bundled/inspector.html?..." URL.

Open the Chrome browser and paste the copied URL.

In Chrome, select the "Memory" tab.

Select the "Allocation instrumentation on timeline" radio button.

Click the "Start" button.

Select the "Sources" tab.

Click the "Resume script execution" toolbar button in top-right corner. Looks like: [>]

Wait for the Titanium app to display a TableView.

Click the "Reload" button in the bottom-right corner of the app.

Click the Android back button.

In Chrome, select the "Memory" tab.

In Chrome, top-left corner, click the "Stop recording heap profile" toolbar button.

Under the "Constructor" column, expand the "Label" tree.

Note that 50 Labels are listed under it. These have not been garbage collected.

function createTableData() {
	var tableData = [];
	var maxIndex = 50;
//var maxIndex = Math.floor(Math.random() * 100) + 1;
	for (var index = 1; index <= maxIndex; index++) {
		var row = Ti.UI.createTableViewRow();
		row.add(Ti.UI.createLabel({ text: "Row " + index.toString() }));
		tableData.push(row);
	}
	return tableData;
}

var window = Ti.UI.createWindow({ exitOnClose: false });
var container = Ti.UI.createView({
	width: Ti.UI.FILL,
	height: Ti.UI.FILL,
});
var tableView = Ti.UI.createTableView({
	data: createTableData(),
});
container.add(tableView);
var reloadButton = Ti.UI.createButton({
	title: "Reload",
	bottom: "10dp",
	right: "10dp",
});
reloadButton.addEventListener("click", function(e) {
	Ti.API.info("@@@ Reloading table data.");
	tableView.data = createTableData();
});
container.add(reloadButton);
window.add(container);
window.open();
*Notes:* * If a TableView is garbage collected, then its rows are correctly garbage collected as well. This is only an issue when overwriting the rows via the "data" property. * This is an issue for Alloy since its generated code will replace the TableView "data" array any time the model changes via a "fetch", "destroy", "change", "add", "remove", or "reset" event.

Comments

  1. Gary Mathews 2018-07-26

    master: https://github.com/appcelerator/titanium_mobile/pull/10195 7_3_X: https://github.com/appcelerator/titanium_mobile/pull/10203 7_2_X: https://github.com/appcelerator/titanium_mobile/pull/10197
  2. Sergey Volkov 2018-07-27

    You can't simply call [section.releaseViews()](https://github.com/appcelerator/titanium_mobile/pull/10195/files#diff-9f3bae7cb02c715af9fc45d9bc16b732R678) in cleanupSections, because sometimes you will come into this method from [TTVListAdapter.getView](https://github.com/appcelerator/titanium_mobile/blob/7_2_X/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TiTableView.java#L186) (attention on notice). [Here](https://gist.github.com/drauggres/4ab66ec8175665ec277384d0b942afc4) is stack trace for this case.
  3. Sergey Volkov 2018-07-27

    Click the "Reload" button until app hangs.
       var image = "https://raw.githubusercontent.com/appcelerator/titanium_mobile/" +
       		"master/.github/logo-titanium.png";
       
       function createTableData() {
       	var tableData = [];
       	var maxIndex = 50;
       //var maxIndex = Math.floor(Math.random() * 100) + 1;
       	for (var index = 1; index <= maxIndex; index++) {
       		var row = Ti.UI.createTableViewRow();
       		var view = Ti.UI.createView({
       			width: Ti.UI.FILL,
       			height: Ti.UI.SIZE,
       			backgroundColor: '#555'
       		});
       		var url = [image, '?time=', Date.now(), '_', index].join('');
       		view.add(Ti.UI.createImageView({
       			width: 20,
       			height: 20,
       			defaultImage: image,
       			image: url
       		}));
       		row.add(view);
       		tableData.push(row);
       	}
       	return tableData;
       }
       
       var window = Ti.UI.createWindow({ exitOnClose: false });
       var container = Ti.UI.createView({
       	width: Ti.UI.FILL,
       	height: Ti.UI.FILL,
       });
       var tableView = Ti.UI.createTableView({
       	data: createTableData(),
       });
       container.add(tableView);
       var reloadButton = Ti.UI.createButton({
       	title: "Reload",
       	bottom: "10dp",
       	right: "10dp",
       });
       reloadButton.addEventListener("click", function(e) {
       	Ti.API.info("@@@ Reloading table data.");
       	tableView.data = createTableData();
       });
       container.add(reloadButton);
       window.add(container);
       window.open();
       
  4. Lokesh Choudhary 2018-08-01

    Verified the fix in SDK's 7.4.0.v20180801012329, 7.3.0.v20180731101200 & 7.2.1.v20180726150551. Closing.

JSON Source