Slides from my presentation at the 2014 ConnectJS / TiConnect conference in Atlanta. I cover tips and background info on managing memory and performance in your Titanium apps.
1. Memory Management in Titanium Apps
TIM POULSEN
SOFTWARE ENGINEER, TOOLING TEAM
@skypanther
2. What are our goals?
No crashes
Fast, responsive apps
Do more
Longer battery life
Happy users!
<0.1 second = “instant”
<1 second = “uninterrupted”
~10 seconds = user loses attention
-- Nielsen Norman Group
3. Topics I’ll cover
Memory limits
What uses memory
Garbage collection
Garbage is good
Alloy specifics
Profiling
http://muppet.wikia.com/wiki/I_Love_Trash
11. Native memory cleanup
Android / Java layer GC:
Runs at its own interval
Slower & less frequent
iOS
Manual allocation/de-allocation by core SDK
ARC supported in modules
12. Garbage Collection Takeaways
Every allocation gets you closer to a GC pause
More objects = more time for GC
GC is automatic
JavaScript and native processes
to consider
http://muppet.wikia.com/wiki/I_Love_Trash
14. Efficient and GC friendly
Watch those images
Avoid global variables
Don’t create variables you don’t need to
Clean up after yourself
A couple of extras...
15. Images
Decompressed in memory
File format doesn’t matter!
Pixels and color depth do
Use scaled resolutions
Lazy load them
Remove them when you can
Don’t use them
200 by 200 px image
= 40,000 pixels x 32 bits per pixel
= 1,280,000 bits
= 160,000 Bytes
= 156.25 KB
= ~1/6th MB
“Why not just use xhdpi images across all
resolutions and let the device scale to the right
size?” --Q&A user
“I’ve got a table with 200 rows, each with an image
and my app crashes. Titanium sucks” --Q&A user
A 1536 x 2048 image ~ 12 MB
17. Don’t use global variables
Scope - global & function
Globals are bad:
Can’t be garbage collected
Collisions (unintended value changes)
Watch for accidental globals
Use scope to your advantage:
Wrap in functions / IIFEs
28. Android specifics
Use the R bitmaps
Stop services
Clean up in onPause() or onuserleavehint event
Especially sensitive to rapid allocation/de-allocation
30. Alloy considerations
Use the framework
Model/Collection references
Just don’t – er, clean-up functions
Remember the basics
31. Use the framework
config.json Alloy.CFG.foo
Alloy.Globals -> delete Alloy.Globals.foo
Compiler / platform directives
(OS_IOS, OS_ANDROID, etc.)
32. But don’t abuse it
Don’t put images or large objects into Alloy.Globals
Don’t declare variables in alloy.js
Controller listeners: $.view.on() and $.view.off()
Model/Collection listeners: coll.on() and coll.off()
44. Takeaways
Limit images, lazy load, use fonts ...
I love trash (aka: code with GC in mind)
Let Alloy do its stuff
But clean up after yourself
Profile your apps
Very squishy figures ... They’re probably not right but they’re in the ballpark
Requesting “large heap” on Android is generally not recommended, certainly not as a simple “out of memory fix” as it is problematic on many devices, plus not guaranteed.
See docs.appcelerator.com
Let’s get a little esoteric here for a few...
Circles represent objects, lines are references
There can be multiple references to an object
Nulling a reference breaks the line
But an object can’t be removed until all references to it are removed
Addy referred to these as the To and From spaces
Allocating obj 5 would cross GC threshold
Your app is paused and GC process begins
Live & Reserve spaces swapped (internal references are changed)
JS interpreter sweeps memory & marks live objects
It copies living objects to the live space
This is the slow part of GC
Orphaned objects are torn down
Your app is resumed and Obj 5 can be allocated now
See docs.appcelerator.com
We’ll talk more later about how to mark objects as collectible
Android Java layer also uses a garbage collection method of its own. It runs less frequently than JavaScript’s. This can cause issues where the JS layer cleans up objects while the associated proxies remain valid.
Our current iOS SDK does not implement ARC. Proxies are manually allocated and de-allocated. This causes fewer issues like described for Android/Java.
Every object you create eats up your budget and gets you close to a garbage collection pause
Once that pause is hit, the more objects in memory the longer the GC pause
GC is automatic, with no way for you to start or prevent
Don’t forget that the native objects must be cleaned up too, and each platform handles that in its own way.
Let’s talk about some ways to play nice with garbage collection as well as write memory-efficient apps
Images are decompressed in memory to a bitmap
A 200 x 200 px 5kb JPG will form just as big a bitmap as a 200x200 px 50kb GIF – it’s not the file size that matters but the number of pixels and the number of bits/pixel (color depth) that do
Load images only when you need them
Remove them from the screen and destroy their associated objects to free memory as soon as you can
When possible, don’t use images – views and icon fonts are two ways you can avoid using images.
Icon fonts are a great way to:
Provide resolution-independent assets (no scaling, no mult. assets, easy to recolor)
Reduce memory
Reduce package size
Let’s get into some coding principles
var keyword required to make variable local to a function
There are other ways to make globals unintentionally
CommonJS modules are a great way to enforce scope
They are essentially functions that return an object with a standard interface
A singleton object might be a better way to pass values throughout your app
Will consume some memory, but eliminates other problems from globals
If you don’t create a variable, you don’t have to remember to clean it up.
Create a variable reference only when you need to manipulate the object in some way: add child views, hide/show it later, call its methods, etc.
Where this becomes a problem is in loops like this, which create a lot of objects that need to be cleaned up
Remember Android does this slowly / differently than the JS level
For the last option: If possible, pass generic objects and let the native side do all the work. My sample wouldn’t be creating custom TableViewRows and would rely on the native title, etc. properties. Thus, it’s not applicable in every case.
Doing so would also require you to manipulate the data to create generic dictionaries that can be passed to TableViews, ListViews, etc.
Screwy way to do this, but it’s here to make a point about closures
The defaults object persists – not taking up a lot of memory, but still persist
Using scope to our advantage, we make sure that the names & values & q arrays are GC’d when the function finishes
This from the Alloy SQL sync adapter
Especially on Android, something like this will probably run faster in JavaScript than the native side GC can keep up with
Could cause a crash with a rather cryptic error message
Whenever possible, do your data processing at the right layer:
Filter and manipulate API-generated data in the cloud
Sort, select, filter data at the DBMS level
This reduces the amount of processing (thus memory) required by your app.
The built-in R resources include resolution-specific optimized bitmaps – so you don’t have to worry about creating resolution-specific icons & buttons.
Android will try to keep an app alive even after backgrounded if it started a service. So your app won’t necessarily take more memory to run, but it will use more overall memory and be less likely to be killed. This will slow the user’s device, maybe making it unstable. Users could determine it was your app that was at fault and uninstall or review your app poorly.
When possible, destroy objects when the app is put into the background. Onuserleavehint is fired right before the app is paused. OnPause() runs as the app is paused.
The config file is created for you. Use it rather than creating your own global object or singleton module
Useful for any values that can be JSON-stringified
Alloy.Globals is an object property – you’re not really setting a global per se.
Compiler / platform directives can cut large chunks out of your runtime code. Use them!
Avoid wasting memory by limiting what you store in Alloy.Globals
Variables & functions defined in alloy.js will be retained throughout your app and could act as globals
If you add custom listeners to controller or models/collections, make sure to remove them
You need to destroy collections associated with a controller when you close that controller.
References are stored in the global Alloy.Collections array
$.destroy() accepts no arguments and destroys all collections associated with that controller
People seem to love clean up functions
But most that I’ve seen are crap
This one, for example, comes from an “answer” in the Q&A. It removes all the child objects, but ultimately probably does nothing more than slow down the app
The original view objects might still exist elsewhere. And simply nulling out the $.items object would delete its children
Fokke’s function is about the best I’ve seen
It encapsulates the basics you must do:
- destroy collections
- remove controller listeners
- remove global event listeners
- null out and/or clean up child controllers
Just some quick notes on profiling, this isn’t a how-to tutorial
Leaks is gone in Xcode 6. Allocations seems to be sufficient.
Filter on TiUI
Goal should be nothing in the Transient column
There’s a lot more you can do with Instruments, but not today ...
DDMS is deprecated, stop using it
Traditionally, Monitor provided little help beyond a simple object count
The Allocation Tracker is almost useful
In this case, I can filter to my target object – row proxies
While this isn’t live, I was able to see the count increase as I opened & closed a leaky window
Have to do something in your app, click Get Allocations, then the view will be updated