启用largeHeap时,应用中未触发垃圾收集导致OOM

时间:2015-06-11 09:27:48

标签: java android out-of-memory

我有一个小的Android测试应用程序,其中启用largeHeap最终会导致Out of Memory Error,因为垃圾收集永远不会被触发。

这是代码:

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是JPEG图片2560x1707像素

旋转设备时会重新加载图像。启用largeHeapGC永远不会被触发,方向更改序列最终会导致OOM。禁用largeHeap不会发生这种情况。在轮换后调用System.gc()也可以解决问题。

启用largeHeap时的内存消耗 Memory consumption with largeHeap enabled

启用largeHeapSystem.gc()通话时的内存消耗 Memory consumption with largeHeap enabled and System.gc() call

禁用largeHeap时的内存消耗 Memory consumption with largeHeap disabled

我可以在Samsung SM-T210 API 19设备上重现此问题。具有API 16的相同类型的设备可以正常工作,以及API 19 Samsung GT-N7100Asus K01A之类的其他设备。显然,只有特定的API /设备组合才会出现某种错误。

问题是:

  1. 我的代码存在任何内在错误
  2. 除了调用System.gc()
  3. 之外,还有其他一些(更好的)解决问题的方法吗?

1 个答案:

答案 0 :(得分:1)

我将尝试仅在方向更改的情况下解决此问题,完整的OOM异常解决方法超出了此答案的范围:

您可以在ImageView onDestroy()中执行图像的回收,因为当方向更改时,系统会调用活动onDestroy()

您必须区分是否因为方向更改而调用了onDestroy(),您应该使用is call isFinishing()

以下是演示此内容的摘要:

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
    }
  }
}

创建WeakReference对象

时,使用Bitmap除此之外还有帮助
WeakReference<Bitmap> bm; //initialize it however you want

** 编辑 **

我知道这不会有太大的不同,但对我来说确实如此。 android为我们节省内存的唯一选择是降低图像质量以及使用LruCache等其他方法。

您可以在options.inSampleSize之前添加另一行,以降低图像的质量以节省一些内存。

options.inPreferredConfig = Config.RGB_565;