Garbage collection not triggered in app when largeHeap is enabled resulting in OOM

695 views Asked by At

I have small Android test application where enabling largeHeap eventually causes Out of Memory Error because garbage collection never gets triggered.

This is the code:

MainActivity.java

package com.example.oomtest;

import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.widget.ImageView;

public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int width = dm.widthPixels;
        int height = dm.heightPixels;

        ImageView iv = (ImageView) findViewById(R.id.background_image);
        iv.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.drawable.g01, width, height));
//        System.gc();
    }

    public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight)
    {
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth)
        {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth)
            {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
    {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);

        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity">

    <ImageView
        android:id="@+id/background_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"/>

</RelativeLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.oomtest" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:largeHeap="true"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

drawable.g01 is JPEG image 2560x1707 pixels

When device is rotated image is reloaded. With largeHeap enabled GC never gets triggered and sequence of orientation changes will eventually result in OOM. That does not happen with disabled largeHeap. Also calling System.gc() after rotation resolves the issue.

Memory consumption with largeHeap enabled Memory consumption with largeHeap enabled

Memory consumption with largeHeap enabled and System.gc() call Memory consumption with largeHeap enabled and System.gc() call

Memory consumption with largeHeap disabled Memory consumption with largeHeap disabled

I am able to reproduce this issue on Samsung SM-T210 API 19 device. Same type of device with API 16 works fine, as well as some other devices with API 19 like Samsung GT-N7100 and Asus K01A. Obviously it is some kind of bug that happens only on specific API/device combinations.

Questions are:

  1. Is there anything inherently wrong with my code
  2. Is there some other (better) way for resolving the issue other than calling System.gc()
1

There are 1 answers

6
Sharp Edge On

I will try to address this issue in case of Orientation Change only, Complete OOM Exception workaround is beyond the scope of this answer:

You can perform recycling of images in ImageView in onDestroy() because when the Orientation changes, activity's onDestroy() is called.

You have to differentiate whether the onDestroy() is called because of orientation change or not to do this, you should use is call isFinishing()

Following is a snippet to demonstrate this:

ImageView iv; // globally defined in class



 @Override
 protected void onDestroy(){
  super.onDestroy();
  if (isFinishing()) {
     // don't do anything activity is destroying because of other reasons
   }
  else{ // activity is being destroyed because of orientation change
    Drawable drawable = iv.getDrawable();
    if (drawable instanceof BitmapDrawable) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        Bitmap bitmap = bitmapDrawable.getBitmap();
        bitmap.recycle();
        iv.setImageBitmap(null);  // edited
    }
  }
}

Using WeakReference will help in addition to this, when creating the Bitmap object

WeakReference<Bitmap> bm; //initialize it however you want

** EDIT **

I know this won't make much difference, but for me it does. The only option android has left for us to save memory, is to reduce Image quality along with other methods like using LruCache.

You could add another line before options.inSampleSize to reduce image's quality to save some memory.

options.inPreferredConfig = Config.RGB_565;