我有一个小型的Android测试应用程序,其中启用largeHeap最终会导致内存不足错误,因为永远不会触发垃圾回收。

这是代码:

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像素

旋转设备时,将重新加载图像。启用largeHeap后,GC将永远不会触发,并且方向变化的顺序最终将导致OOM。禁用largeHeap不会发生这种情况。轮换后也调用System.gc()可解决此问题。

启用largeHeap的内存消耗

启用largeHeapSystem.gc()调用后的内存消耗

禁用largeHeap的内存消耗

我可以在Samsung SM-T210 API 19设备上重现此问题。具有API 16的相同类型的设备以及具有API 19的某些其他设备(例如Samsung GT-N7100Asus K01A)都可以正常工作。显然,这是仅在特定的API/设备组合上才会发生的某种错误。

问题是:
  • 我的代码
  • 天生有问题吗
  • 除了调用System.gc()
  • 之外,还有其他(更好)的方法来解决此问题吗?

    最佳答案

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

    您可以在ImageView中的onDestroy()中执行图像的回收,因为当方向更改时,将调用 Activity 的onDestroy()

    您必须区分是否由于方向改变而调用了onDestroy(),为此,您应该使用的是调用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;
    

    关于java - 启用largeHeap导致OOM时,应用程序中未触发垃圾回收,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30776779/

    10-10 18:42