3
votes

I've read through a lot of webpages discussing how to control memory leakage in a Titanium-based mobile application for Android.

My situation is that I'm building an app that uses multiple levels of lists (in practice these are tableViews) where the user can navigate through. It is using one window, and when the user selects a list item a new view is created which is animated right-to-left. I have chosen this option, as it appeared to be impossible to create a new window which slides in from right to left an all platforms.

In every view, an eventListener is created to check which tableRow is clicked, and the corresponding submenu is then created and is animated into the screen.

I notice that the memory usage is steadily growing after each click on a view, but I'm can't seem to pinpoint where the memory leak is present.

Currently I'm checking the main window to see if the window has been animated out of view (then the .left property is 320 on a 320px wide device). Then I remove this view from the window, and set the proxy to null, using:

for ( i = 0; i < win.children.length; i++) {
    if ( (win.children[i] != null) && (win.children[i].left == 320) ) {
        win.remove(win.children[i]);
        win.children[i] = null;
    }
}

It is still building up memory usage though. This could be because every new view contains a table and an event listener, using a function containing:

var sub_table = Ti.UI.createTableView({top:'50dp',separatorColor: rowSeparatorColor});  
sub_table.setData(data);
sub_table.addEventListener('click', function(e) {
    create(e.rowData.data);
}); 
new_view.add(my_navbar);
new_view.add(sub_table);
return new_view;

Do I have to erase them separately or are they destroyed when the view is destroyed? Of I have to erase them manually, how would I go about that?

On a more general note, I don't know how to determine the causes of memory usage. Is there a way to get all objects and/or variables that are in memory at a certain time? Is there a way to drill down on the memory usage that the Dalvik toolkit provides? Is there a method for obtaining all global variables or event listeners?

3
You can use the dalvik toolkit to analyze your android project that is generated by titanium, check the build folder for it. Also try using removeEventListenerJosiah Hester

3 Answers

2
votes

The most common memory leak in Titanium apps has to do with holding on to variable references. If you've got a context inside a context, the outer context's variables will be held until nullified. Demonstrated below. This can be easily observed in Apple's "Instruments" by filtering for TiUI and watching the number of views and view proxies go up (if you remove all " = null" lines below).

var win = Ti.UI.createWindow();
win.open();

setInterval(function () {
    var view = Ti.UI.createView({
        backgroundColor: 'red',
        width: 300, height: 300,
        top: 100, left: 100
    });
    win.add(view);

    setTimeout(function () {
        fadeAwayAndRemove(view, win);
        view = null;
    }, 1000);
}, 2000);

function fadeAwayAndRemove(view, container) {
    var animation = {
        opacity: 0,
        duration: 300,
        transform: Ti.UI.create2DMatrix().scale(0.9, 0.9)
    };
    view.animate(animation, function () {
        container.remove(view);
        view = container = animation = null;
    });
}

Also note that, in the above example, if I had added views to that "view" that I'm removing, I don't need to explicitly remove them, one at a time. Make sure you nullify all lingering references to them. Then, when the view is removed and nullified, GC can clean it up, and all of its children.

This practice will start to become second nature to you. But when you're first starting to account for it, it's a good idea to repeat discrete actions in your app (go to one screen, navigate back to the previous, go to the same screen, navigate back, and so on) to find the places where you're losing the most memory. Then work on the code to eliminate the drain.

It can be tough and frustrating at times, but persist, and I know you'll figure it out!

1
votes

I haven't worked with the Android memory manager, only the Apple Instruments app.

First thing is to look at your app in pieces, rather than as a whole. I went through the code of my main window line-by-line making sure I eliminated any memory issues known to me. I was able to get a baseline number of objects on my main window and use that to compare to other windows in my app. I would then transition between those windows and resolve all memory issues, expecting to see my number of allocated objects return to my baseline each time.

For example, if I open my main window, I expect 2 buttons and 2 labels to be allocated among other things. I transition to the next window, which has 2 buttons and 2 labels, I expect to see the number of those objects jump to 4 and 4. I then go back to my main window and close the other window, I expect to see the 2 additional buttons and labels eventually get cleaned up. It isn't instant, so I refresh or watch to make sure they go away.

Remember, you can comment out huge sections of your code to see if it is the culprit. For example, when I suspect a piece of code has a leak in it, I comment out that code and see if the number of objects instances increases and drops as expected.

I found that if I closed a window, all the allocated objects eventually got cleaned up if I had them properly declared. I found 1-2 examples of my variables were missing the 'var' keyword, so I believe they were being declared in the global scope, so I cleaned that up.

I posted on Appcelerator's forum looking for help myself, here is my post: http://developer.appcelerator.com/question/148246/profiling-and-cleaning-up-objects

This post has several suggestions as well as a link to a video by Appcelerator that talks about debugging your apps.

0
votes

And would something like this also work? In this case I setup the listener, and attach the listener to the table. After the table is clicked, the listener is removed, and a new table is created using a create() method. It sort of works recursively (the create() function calls itself after the table is clicked), which leaves me in doubt. But it 'should' remove the eventListener, remove the view from the window and set its proxy to zero?

function create(i) {
    var listener = function(e) {
        win.removeEventListener('click', listener);
        win.remove(clickedview);
        clickedview = null;
        create(e.rowData.data);
    }
    var sub_table = Ti.UI.createTableView({top:'50dp',separatorColor: rowSeparatorColor});  
    sub_table.setData(data);
    sub_table.addEventListener('click', listener);
    new_view = populateView(i);
    new_view.add(sub_table);
}