How we @Naukri avoid Out of Memory (OOM) in Android Applications

0.00 avg. rating (0% score) - 0 votes

We in Naukri.com always keep memory usage in mind while developing android applications and keep on regularly checking the memory usage of different pages. We added memory usage in our checklist before releasing any new version because racking down the android memory issue (OOM) is one of the pain full task of every android developer. OOM errors often seen on tools like Criticism or Crashlytics make developers run for the solution and finally doomed with fuzzy answers.

Mobile devices typically have constrained system resources so we have to be cautious while loading the media files. The android application memory (memory allocated to a process) is totally depended on device compatibility definition, it can vary from 16MB to 48MB and so on.

There are multiple reasons for an Out of memory error. Some of those are:

  1. Loading Large Bitmap, android devices are normally run with limited heap size so we have to be very careful while loading the large bitmap.
  2. Creating the new instance of same bitmap
  3. Activity with leaking context (Memory leak)

The biggest reasons for Out of memory are

  1. Bitmap
  2. Memory Leak.

How we manage large bitmaps or large image streams

Today’s fast paced development most of the apps are not doing proper memory optimization and shipping with bunch of memory issues. Here are some tips which will help you to reduce the OOM instances.

  1. Strictly check the image size , if it’s large do the proper sampling

BitmapFactory.decodeStream(response.getData()); will blindly get the full input stream and gets the image in to the memory and no matter what size we need it will load the full image. There is an option in the BitmapOption class that helps to read the image bounds without getting that image in the memory i.e inJustDecodeBounds and we have to set it true while decoding the image as following:

public static Bitmap getBitmapFromInputStream(InputStream inputStream, Context context) {
try {
int userImageHeight = context.getResources().getDimensionPixelSize(R.dimen.user_profile_pic_height);
int userImageWidth = context.getResources().getDimensionPixelSize(R.dimen.user_profile_pic_width);
BitmapFactory.Options options =
new BitmapFactory.Options();
options.
inJustDecodeBounds = true;
Bitmap userImage;
if(inputStream.markSupported()){
inputStream.mark(inputStream.available());
BitmapFactory.
decodeStream(inputStream, null, options);
inputStream.reset();
options.
inSampleSize = calculateInSampleSize(options, userImageWidth, userImageHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
userImage = BitmapFactory.
decodeStream(inputStream, null, options);
}
else{
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n = 0;
while ((n = inputStream.read(buf)) >= 0)
baos.write(buf,
0, n);
byte[] content = baos.toByteArray();

InputStream is1 = new ByteArrayInputStream(content);
BitmapFactory.
decodeStream(is1, null, options);
options.
inSampleSize = calculateInSampleSize(options, userImageWidth, userImageHeight);

InputStream is2 = new ByteArrayInputStream(content);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
userImage = BitmapFactory.
decodeStream(is2, null, options);
}

return userImage;
}
catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

 

So on the basis of what size we need i.e user_profile_pic_height we started sampling the image and that decided the sampling size and that will help to make the decoding image smaller by skipping n number of pixels while decoding where n is the inSampleSize. This way we actually got the image of size we require and avoided OOM. We have to differentiate that the stream we have is mark supportable or not and if not then we have to do the manually byte by byte as done in above code.

More insight in to the memory handling

Android 3.0 (API level 11) introduces the BitmapFactory.Options.inBitmap field. If this option is set, decode methods that take the Options object will attempt to reuse an existing bitmap when loading content

LRU Cache with strong referenced LinkedHashmap will really help to improve the image look up. Earlier popular image cache implementation was based on soft reference or weak reference bitmap cache which is not at all recommended now (3.0+ aggressive GC).

Memory Leak

  1. We make an Activity and create a TextView with Activity context and set a sample text in to it.

@Override

protected void onCreate(Bundle state) {

   super.onCreate(state);

   TextView label = new TextView(this);

   label.setText(“Leaks are bad”);

   setContentView(label);

}

This means that views have a reference to the entire Activity and therefore to anything your activity is holding onto; usually the entire View hierarchy and all its resources. Therefore, if you leak the Context (“leak” meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you are not careful.

2. When the screen orientation changes the system will, by default, destroy the current activity and create a new one while preserving its state. In doing so, Android will reload the Application’s UI from the resources. Now imagine you wrote an application with a large bitmap that you don’t want to load on every rotation. The easiest way to keep it around and not having to reload it on every rotation is to keep in a static field:

private static Drawable sBackground;

@Override

protected void onCreate(Bundle state) {

super.onCreate(state);

ImageView label = new ImageView(this);

if (sBackground == null) {

   sBackground = getDrawable(R.drawable.large_bitmap);

}

 label.setBackgroundDrawable(sBackground);

setContentView(label);

}

 

This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When a Drawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the ImageView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.).

Now if the orientation changes the Drawable will not come under GC because it is static and due to this the ImageView will also not go under GC operation because the Drawable is linked to the ImageView by the Drawable callback and same for Activity context becauseImageView was created with Activity context. So this lead to a memory leak and every time you change the orientation this leak will grow in the heap and at some point it will increase the heap limit and android will kill the application and throw Out of memory error.

Tools we use to Diagnose OOM

In Naukri, we use tools that can be used to find the memory leaks and some of them are:

1. Android Studio analyser tool – Please find following step to get and view the dump trace:

  1. In Android Studio, run your application

  2. In the bottom-side panel, Android Monitor and go to Monitors tab.

  3. Click Dump Java Heap

  4. It will open the dump in Android Studio itself and then you can see the object allocation and everything in the heap.

  5. If the studio version is 1.3+ then it will open the heap dump in the native android studio tool else you need to install MAT as described in below section

MAT (Memory Analyser Tool)This can be used as a plugin or as a standalone tool:

1. To start up with this tool you have to download this tool from following link: http://www.eclipse.org/mat/downloads.php

2. Start Eclipse and run your application.

3. Go to DDMS/Android device monitor and select your application and click the Dump HPROF file button and it will ask you to save the hprof file. Save it on your hard disk.

4. Run the MemoryAnalyzer.exe file situated in the MAT package downloaded.

5. This MAT tool will not understand the hprof file generated by Eclipse so you have to convert it using the hprof-conv tool in the tools folder of android-sdk using the following command:

hprof-conv com.example.android.hcgallery.hprof mat.hprof

6. Now this file is ready to use with MAT.

Note: This dump can be generated from Android studio as well and then analyze using MAT as above.

Summary of our Best practices

  • 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)

  • Try using the context of application instead of a context of activity

  • If you are using a large bitmap as background or something in your application than don’t pull the full image in to the main memory. You can use the insample size property of bitmap to bring the size your screen needs.

  • Sending large files to server should be done in chunks because loading a large image in bytes can cause OOM.

  • Try to send weak reference where ever possible for e.g if a listener is required in the adapter do not give the whole activity reference just give that listeners weak reference.

  • When you have some static stuff and it is linked to a UI element try to remove callbacks of the static element with UI to avoid memory leaks.
Posted in Mobile