Memory and Performance Optimization for Android Application
In an e-commerce android application, product images are super critical to drive higher engagement and sales. At Urban Ladder, we focus a lot on the quality of visuals shown to consumers. So, how are these images downloaded and managed in the app? Since images are the highest consumer of network data and memory so we figured that the solution needed to be thought over wisely. We explored multiple open source libraries like Picasso, Glide and Fresco. Picasso and Glide are easy to use and quite similar in implementation but Glide provides a better combination in terms of memory usage, performance, ease of use, and unique feature set.
Some of the interesting facts about Glide library :
1. To reduce stuttery scrolling in lists caused by garbage collections due to Bitmap allocations, Glide uses reference counting behind the scenes to track and reuse Bitmap objects. To maximize the number of Bitmaps that are reused, Glide includes a Bitmap pool capable of pooling arbitrary sizes of Bitmaps.
2. Using RGB_565 configuration in glide consumes less memory compared to ARGB_8888.
3. The 3.0 version of Glide includes a number of improvements, including support for animated GIF and video still decoding, improved lifecycle integration to intelligently pause and restart requests, and thumbnailing support.
Analyze App RAM Usage
This is a regular practice we follow at Urban Ladder. With every feature developer must uses android studio Memory Analyzer Tool to check if there are obvious memory leaks in the code. If the memory heap size increases continuously and reaches the memory limit, it may lead to OutOfMemory issue. You can use LeakCanary library to find the root cause of OutOfMemory exception. See LeakCanary FAQs here.
Avoid a deep hierarchy of views
Creating Views are quite costly in terms of memory. Try to keep your views hierarchy as flat as possible and avoid too-much of nested layout and it might hit app performance. Use <include> for reusing layout and <viewstub> to inflate layout at runtime. Use Hierarchy Viewer and Lint tool to optimise the layout. Avoid creating views unnecessary by loading views on demand.
Handling communication with server efficiently
Use high performing networking libraries like Volley or retrofit to communicate with the server. Retrofit performs better as compared to (AsyncTask + Http Client) and Volley. Retrofit also provides scalable interface based implementation which is quite simple to use and less boilerplate code.
Use ProGuard to remove unneeded code
The Proguard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. Using ProGuard can make your code more compact, requiring fewer RAM pages to be mapped.
Understanding Memory Leaks
Memory leaks are unreachable chunks of memory that may not be garbage collected. A garbage collector is not an insurance against memory leaks. Let’s consider an example of Android Context leak. Contexts such as Activities contains many references to large amount of memory (such as view hierarchy’s and other resources). If a Context is leaked, it will leak all the memory it has reference to. Android sets a hard limit on heap size for each app. The heap size will keep increasing due to memory leaks and it may lead to OutOfMemoryError.
Preventing Memory Leaks
1. Use Application context and avoid using activity context if possible.
2. Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
3. Handler should be declared static.
4. Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside.
5. Use tools like LeakCanary, MAT to find the memory leaks in the code.
1. Avoid creating unnecessary objects
2. Avoid using floating point, use double instead
3. Avoid internal getters and setters.
4. Use static final for constants.
5. Don’t invoke System.gc() in your code.
6. Try to use optimized data structures like SparseArray, SparseBooleanArray and LongSparseArray which are more efficient than generic HashMap
7. Cache heavy computational results for re-use
8. Keep heavy work off the main thread
9. Try to avoid too much use of Enums.
10. Initialize ArrayList and StringBuilder with size if possible.
11. Invoking getResources() method is a bit costly. So, if getResource() method is being used at multiple place in the code, then it is better create a reference of Resource and use that reference everywhere (Resource resource = getResource())
This article has been authored by Manish Kumar, a Software Development Engineer at Urban Ladder.