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

Dal*_*kar 8 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

drawable.g01是JPEG图像2560x1707像素

当设备旋转时,重新加载图像.随着largeHeap启用GC从未被触发和方位的变化序列,最终将导致OOM.禁用时不会发生这种情况largeHeap.System.gc()轮换后调用也可以解决问题.

largeHeap启用 内存消耗启用largeHeap时的内存消耗

largeHeap启用和System.gc()调用的 内存消耗启用largeHeap和System.gc()调用的内存消耗

largeHeap禁用 内存消耗禁用largeHeap时的内存消耗

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

问题是:

  1. 我的代码有什么本质上的错误
  2. 除了调用之外,还有其他(更好的)解决问题的方法吗? System.gc()

Sha*_*dge 1

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

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

您必须区分是否onDestroy()因方向变化而被调用,以执行此操作,您应该使用 is callisFinishing()

以下是演示这一点的片段:

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

WeakReference除此之外,在创建Bitmap对象时使用也会有所帮助

WeakReference<Bitmap> bm; //initialize it however you want
Run Code Online (Sandbox Code Playgroud)

**编辑**

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

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

options.inPreferredConfig = Config.RGB_565;
Run Code Online (Sandbox Code Playgroud)

  • 节省内存并不能解决当前问题。节省内存只是推迟了问题的发生,但问题仍然存在。我已经有了可行的解决方案,那就是使用“System.gc()”手动触发垃圾收集。在AS中通过调试器触发GC也可以。我想知道是否还有其他可行的方法。但是,显然问题确实出在某些设备上的错误 GC 上。 (2认同)