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 and System.gc()
call
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:
System.gc()
We have three ways to achieve same - 1) Increasing the Heap -Eden space size . 2) Create Singleton class with Static reference . 3) Override finalize() method and never let that object dereference.
Garbage Collection occurs if at least one of multiple conditions is satisfied. These conditions are given as follows: If the system has low physical memory, then garbage collection is necessary. If the memory allocated to various objects in the heap memory exceeds a pre-set threshold, then garbage collection occurs.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With