Introduction : Understanding interaction of GC with your codes:
There is a significant difference between the mechanism of Garbage Collection on a Dalvik and ART architecture , Dalvik is not moving collector , on the other word all allocated objects are placed on the same memory space reserved for that purpose , It increase the time GC takes to find out dead objects to remove and to free space for new objects, It increase also the time for heap fragmentation and squeezing objects in memory in order to prepare memory for next cycle of garbage and collection.
In ART architecture GC is improved and considered to be moving collector so allocated objects are not placed on a fixed memory space location ,they can be placed on different location, the big objects such as Bitmaps are placed on a dedicated location, so the time it takes to free memory or new allocations are less than Dalvik architecture , the same for memory Fragmentation it’s much more smooth and fast.
Dalvik is a JIT (Just-in-Time) compiler but ART is a AOT (ahead of time) compiler ,A JIT compiler makes a set of optimisation and adjustment just before executing your codes , but AOH makes the optimisation in back stage and behind the scene previous to launching your application at system level , It prepare(optimise) it just for you in the case you run it!
There are some important points to take in consideration when coding that I've listed below , notice this cannot cover whole subject with all subtile details but some bold points which maybe important:
Memory is the main location all applications and services in your device use , except those that are running in a privilege mode such as some kernel services or manufacture build-in applications the others are supposed to be running at the same privilege level so they all have the same right and acquirement for memory as your application is supposed to have , so because user space memory is a limited location any abuse will impact directly other application and lead to decrease performance of your device for end-user , you should care about all your objects allocations and the way they are recycled by GC (Garbage Collector), there are many way to prevent your device enter Low Memory state , here we remind a few ones:
- Inner Loop Allocation :
one of the most concrete example of this situation is onDraw especially when using Custom Views in which the heavy task of drawing is running using a canvas , onDraw maybe called frequently at any moment decided by system , and the operation running there is often made by vreation of Bitmap and Paint on a Canvas look at this portion of codes
@Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);//<----re-create object every time onDraw is called by system
Paint paint = new Paint();
}
This codes is called and re-called in unknown number of times and it costs expensive because of number of time we create and re-create objects by using “new” , as the system allocate and re-allocate memory later GC enter in operation to remove dead objects and find location for new objects ,more stay in this loop more GC is busy, you can avoid such a situation by writing your code like that:
private Bitmap bitmap;
private Canvas canvas;
private Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
canvas = new Canvas(bitmap);
}
// drawing operation...
}
this code is better because we don’t re-create objects , now we can go one step more by reducing GC task , we explicitly remove pixels by calling canvas.drawColor(white) like that:
private Bitmap bitmap;
private Canvas canvas;
private Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
canvas = new Canvas(bitmap);
}
// drawing operation...
canvas.drawColor(Color.WHITE); // translated as cleanup for system
}
so those kind of cached objects with your codes can improve significantly your code to save memory.
- Array and collections:
If your list is a pre-defined list and not a dynamic list try to use a size-fixed array , because the system know precisely about your requirement and allocate the location in a smart way.
HashMap is a heavy collection because the key is an object , so if your key is not supposed to be an object why using it? For example note that there are some collections classes in Android like SparseIntArray and SparseLongArray that use primitive types instead of object types
If your key is an Integer use SparseArrays instead , SparseArrays map integers to Objects, it’s is not heavy because It’s not going through a complex mechanism of hashing objects and improve your performance . But If still a collection mapping Object To Object is unavoidable for you , again instead of HashMap there are two other alternative way when you don’t have a large set of items in collection :ArrayMap or SimpleArrayMap , those class are optimized and less expensive than HashMap, use them if you can.
- Avoid to return a new mutable-object in your methods:
Let’s suppose we have this method somewhere in your class:
public List<Model> getList(int fromIndex , int toIndex)
Instead of having a method return a new object (which would have to be allocated), consider taking an object parameter of that type and mutating that object. For example, consider to change your prototype as:
public void getList(List<Model>, int fromIndex , int toIndex)
This way you are bypassing creation of objects by just providing a reference to a List , and will be resulted on less task for GC to create and remove those objects .
- Prefere Primitive over objects:
Sometime your using Integer instead of int , although in many SDK collection class they are a need to handle with them but notice Those objects will be boxing/unboxing in all your conversion method so better to avoid it if possible.Also in most of time prefere Primitive Array over Objects Array, some class such as Point or even a custom class/structure which members are supposed to be primitive , handling array of a splitted solution increase memory performance , example instead of having an Array of Point(int,int) you can have two array of each axis coordinates for example instead of:
Point[] points = pointArray = new Point[n]
You can Use :
int[] x = new int[n]int[] y = new int[n]
Prefere enhanced for-each loop over non-enhanced traditional iteration :
for (Obj a : list) {
//your logic
}
over this:
for (int i = 0; i < len; i++) {
//your logic
}
- Avoid Enums in large scope
Enums costs expensive in term of memory allocation especially when the usage is large along with your codes, so It would be preferable to use final static constants instead of enums.
- Precaution when using static objects:
Static variables are attached to class instances and because class instance is forked by class loader into process , so static variable life cycle is the same that your process , which is different from Activity life cycle , as long as your process is alive your static variables remain in memory they are really useful to avoid temporary location which increase fragmentation task for GC,the problem is not using static variable but how to care about it.
During process life cycle GC may attempt to recycle your Activities at any time if need! meanwhile your Static variable are still resident in memory! that’s why it’s very important you don’t store any context or activity instance in a static variable except when you need for the whole life cycle of your application for example a singleton instance of your application object.
public class MyActivity extends AppCompatActivity {
Static MyActivity mActivity;//⛔❌DOING THIS = MEMORY LEAK
/***********************************************************
* Life cycle
**********************************************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = this;// ⛔❌DOING THIS = MEMORY LEAK
}
or the same mistake when using a Context like that:
public class MyActivity extends AppCompatActivity {
Static Context mContext;//⛔❌DOING THIS = MEMORY LEAK
/***********************************************************
* Life cycle
**********************************************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;// ⛔❌DOING THIS = MEMORY LEAK
}
Both those static variables soon or late leads to a memory leak because static variables are created at the time your process gets into life and they are attached to your process not your Activity .
Now suppose your Activity is no longer used (for example when it’s stopped or it’s in background)and system wants to garbage some unused activities,in this scenario GC cannot remove your Activity from memory because there is a static reference to it! And you end up with memoty leak!
- Perform your big memory task in a smart way:
There are helpful API to detect if your device is suffering from Low Memory , try to use them when your need more memory to perform a task , ActivityManager has a great helper function to detect your current RAM usage:
ActivityManager.isLowRamDevice()
isLowRamDevice return true when the current available RAM is less than 512MB , so based on the task you are going to run you can detect current memory level to perform or not your task .There is also another useful API onTrimMemory() available in API level 14 and later to give you the opportunity to for cleaning up in the case you are in a low memory state.
Avoid Requesting a large heap except you really need it and your need justify doing!
First we should know how to increase heap before stop doing it!
According to official documentation you can do it simply by adding the following line to your manifest app tag :
android:largeHeap="true"
<application
android:name=”MyApplication”
android:largeHeap="true"….</ application>
While increasing heap maybe unavoidable for some situation in which your app is using a large amount of media resources such as Unity3D or other image processing… such as in a game , Keep in mind except justified scenario ,in general avoid increasing your heap the reason is simply because more heap allocation for you is translated to less available memory for others , so probably more garbage collection and more killing other apps , the end-user suffers when switching from one application to another by re-starting each application or activity you switch to , and that will be annoying for users.
Important notice :
Increasing heap by android:largeHeap works only on devices running above 11 (OS >= 2.3 )and this code is not working on lower OS (pre Honeycomb).Other technic to increase heap is using NDK instead of SDK because NDK has no such a heap limitation as SDK so for your intensive memory consumption try to make allocation using NDK.
Consideration about Network operations:
heap limitation Network GET/Post operation based on network quality and band width maybe long , if you run them on the main UI thread operation , you are getting NetworkOnMainThreadException , avoid to run such an operation when it can be done asynchronously out of main UI thread, consider to reserve main UI thread for View manipulation , another precaution is to NEVER EVER DO PULLING for your update or notification operation, and NEVER setup a wakeup for fetching data at an absolute time, if you load network data at an absolute time you increase a significant number of client request at the same time and your server will be overloaded by the number of request which may end up in an unexpected result, and don’t forget to check your current connectivity by NetworkInfo.isConnected() before any Get/Post request .
View optimization:
Inflating views as well as views manipulation are done in main UI thread , so we should minimize the time those operations take, by avoiding using useless view or layouts , there are many ways to achieve that goal , prefer using merge tag instead of include tag if you can , because when using include you should add an additional Root Layout to your xml in order to be an independent layout but merge is just a map to your elements arrange you are planning to stick together , so for example if your purpose is just to make your complex layout hierarchy more readable don’t use include, you can do it by merge tag
Custom Views : There are two situation in which creating a CustomView is unavoidable:
1. When your view is entirely custom and not provided by native SDK , you have to manage all view life cycle such as onDraw , OnMeasure ,…etc , notice that Geometric operation such as onMeasure and onDraw are heavy and you should be careful in term of optimization as said before (avoid re-creating objects).
2. When you want more modularity in your code , although this approach is more likely a Compound View rather than a custom view (often a subclass of ViewGroup)
Khosrov 03April 2017
External references:
Chet Haase article page : https://medium.com/@chethaase
Android Performance : Official Android documentation