当启用大内存(largeHeap)时,应用程序中不会触发垃圾回收,导致OOM。

7

我是一名有用的助手,可以为您进行翻译。

我有一个小型的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 是一张尺寸为 2560x1707 像素的 JPEG 图片。

当设备旋转时,图片会被重新加载。启用 largeHeap 后,GC 永远不会触发,并且方向变化的序列最终会导致 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(),是否还有其他更好的方法来解决此问题?

@kha,使用onDestroy()recycle()只能稍微减缓内存消耗的上升速度。 - Dalija Prasnikar
你的Activity代码只有这些吗?我看到你添加了onConfigurationChanged,但是似乎在Manifest文件中没有添加。尽管这不应该导致OOM,但还是尝试使用另一个线程来解码位图,并在完成后向Activity回调。当你收到回调时,将位图设置到ImageView上。确保将回调作为弱引用传递。 - km86
@user3431672,你发现了冗余的onConfigurationChanged,但这并不是问题的关键。这只是在创建MCVE时从真实应用程序中复制代码的副作用。我已经编辑了包含简化代码的问题。 - Dalija Prasnikar
你应该尝试在一个没有对活动或ImageView有强引用的不同线程中解码图像。完成后,您可以向活动发出回调。这样,您将拥有更多的控制权,并且如果活动被销毁,您也有机会取消线程。 - km86
@user3431672 的问题不在于强引用,而是因为 GC 从未触发。 - Dalija Prasnikar
显示剩余5条评论
1个回答

1

针对屏幕方向改变时的问题,我将尝试解决,但完整的OOM异常解决方法超出了本答案的范围:

你可以在onDestroy()中对ImageView中的图像进行回收,因为当屏幕方向改变时,活动的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

** 编辑 **

我知道这可能不会有太大的区别,但对我来说很重要。安卓留给我们节省内存的唯一选项是降低图像质量,以及使用LruCache等其他方法。

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

options.inPreferredConfig = Config.RGB_565;

抱歉,但是这段代码并不能解决问题。代码被调用了,但它对内存的增长没有任何影响。 - Dalija Prasnikar
应用程序应用后,是否因为OOM仍然会崩溃? - Sharp Edge
2
节省内存并不是解决问题的方法。 节省内存只是推迟了问题,但问题仍然存在。 我已经有了可行的解决方案,那就是手动触发垃圾回收,使用 System.gc()。 通过AS调试器触发GC也可以起作用。 我在想是否还有其他可能有效的方法。 但显然问题确实出在某些设备上有缺陷的GC上。 - Dalija Prasnikar
我认为recycle()应该由decodeSampledBitmapFromResource创建的Bitmap实例调用;而不是直接将结果传递给iv.setImageBitmap,首先将值分配给一个类变量,然后在销毁方法中从该实例调用recycle();,如@Sharpedge所述。编辑:在recycle()之后也将其设置为null。 - Paizo
@Paizo 我尝试了包括recycle()的所有可能的代码组合,但没有任何改变。 - Dalija Prasnikar
@DalijaPrasnikar,你可以得出结论,安卓是完全随机的...不是所有的API级别都表现相同。我发布了一个问题,它只在较低的API上发生了奇怪的问题,没有解决方法和错误报告。请看一下这个问题 - Sharp Edge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接