ビットマップによって引き起こされる OOM の問題


このセクションの紹介:

前のセクションでは、ビットマップの基本的な使用法を学習しました。このセクションでは、ビットマップの OOM 問題について説明します。 実際の開発では、ビットマップによって引き起こされる OOM の問題に遭遇したことがあるかもしれませんし、経験していないかもしれません。 このトピックについて学びましょう ~ OOM とは何か、OOM が原因で OOM が発生する理由を理解し、ビットマップによって引き起こされる問題を改善します OOM の問題~


1. OOM とは何ですか?なぜ OOM が発生するのでしょうか?

答え: メモリ不足(メモリオーバーフロー)が発生すると、Androidシステムが各APPに独立した作業スペースを割り当てることは誰もが知っています。 または、個別の Dalvik 仮想マシンを割り当てて、各アプリが相互に影響を与えることなく独立して実行できるようにします。そしてそれぞれのAndroid 現在占有されているメモリと申請するメモリ リソースがこの制限を超える場合、Dalvik 仮想マシンには最大メモリ制限が設定されます。 、システムは OOM エラーをスローします。また、RAM に 1G 以上のメモリが残っている場合でも、OOM は発生します。 RAM (物理メモリ) と OOM を混同しないでください。さらに、RAM が不十分な場合、OOM だけでなくアプリケーションが強制終了されてしまいます。 Dalvik の最大メモリ標準はモデルによって異なります。

ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());

を呼び出して通常の最大メモリ標準を取得するか、コマンド ラインに直接入力します:

adb shell getprop | grep dalvik.vm.heapgrowthlimit

システム ソース コード/system /build を開くこともできます。 .prop ファイルで、ファイルのこの部分の情報を確認して次の情報を取得します。

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=2m
dalvik.vm.heapmaxfree=8m

私たちが懸念している領域は 3 つあります: heapstartsize ヒープ メモリの初期サイズ、および heapgrowthlimit 標準に基づくアプリケーションの最大ヒープです。 メモリ サイズ、ヒープサイズは、android:largeHeap を使用するアプリケーションの最大ヒープ メモリ サイズを設定します。

ここで、手持ちのいくつかのモデルの通常の最大メモリ割り当て基準を試してみました:

1.png

手持ちのマシンを試すこともできます~

さて、早速、OOM 問題の発生について話しましょう。この時点で、メモリ管理の部分に移りますが、これは非常に重要です。 今でもまだ理解できません...ビットマップ OOM を回避するためのヒントをいくつか見てみましょう。


2. Bitmap による OOM を回避するためのヒントのまとめ


1) メモリ使用量の少ないエンコーディング方式を採用します

前のセクションで説明したようにBitmapFactory.Optionsこのクラスでは、inPreferredConfig 属性を設定できます。 デフォルトは Bitmap.Config.ARGB_8888 ですが、Bitmap.Config.ARGB_4444
Bitmap.Config ARGB_4444 に変更できます: 各ピクセルは 4 ビット、つまり A=4、R=4、G=4 を占有します。 B=4 の場合、1 つのピクセルは 4+4+4+4=16 ビットを占有します
Bitmap.Config ARGB_8888: 各ピクセルは 8 ビットを占有します。つまり、A=8、R=8、G=8、B=8、その後1 ピクセルは 8+8+8+8=32 ビットを占有します
デフォルトは ARGB_8888、つまり 1 ピクセルは 4 バイトを占有します。


2) 画像圧縮

も BitmapFactory.Options です。inSampleSize を通じてスケーリング係数を設定します。たとえば、2 と書きます。つまり、長さと幅が元の 1/2 になり、画像は 1 になります。スケーリングが実行されない場合は、元の /4。その場合は、1 に設定してください。しかし、この値は小さすぎるため、やみくもに圧縮することはできません。 そうしないと、画像が非常にぼやけてしまい、画像の引き伸ばしや変形を避ける必要があるため、これをプログラム内で動的に計算する必要があります。 inSampleSize の適切な値。オプションにそのようなメソッドがあります: inJustDecodeBounds、このパラメータを次のように設定します。 true の後、decodeFiel はメモリ領域を割り当てませんが、元の画像の長さと幅を計算できます。 options.outWidth/outHeight 画像の幅と高さを取得し、特定のアルゴリズムを使用して適切な幅と高さを取得します inSampleSize さん、ストリート ゴッド さん、コードをありがとうございます - ホンヤン ブログから引用しました!

public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    if (width > reqWidth || height > reqHeight) {
        int widthRadio = Math.round(width * 1.0f / reqWidth);
        int heightRadio = Math.round(height * 1.0f / reqHeight);
        inSampleSize = Math.max(widthRadio, heightRadio);
    }
    return inSampleSize;
}

その後、上記の方法を使用します:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 设置了此属性一定要记得将值设置为false
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeFile(url, options);
options.inSampleSize = computeSampleSize(options,128,128);
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
/* 下面两个字段需要组合使用 */  
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
try {
    bitmap = BitmapFactory.decodeFile(url, options);
} catch (OutOfMemoryError e) {
        Log.e(TAG, "OutOfMemoryError");
}

3. 適時に画像をリサイクルします

多数の Bitmap オブジェクトが参照されており、アプリケーションがすべての画像を同時に表示する必要がない場合。一時的に使用されないビットマップ オブジェクトは、 速やかにリサイクルしてください。イメージの使用法が明確にわかっている一部のシナリオでは、ブート ページ上のイメージなど、イメージを積極的にリサイクルできます。 完了したら、リサイクルしてアニメーションをフレーム化し、1 つロードし、1 つ描画し、1 つをリリースします。使用する場合はロードし、表示されない場合は null を設定するか、直接リサイクルします。 例: imageView.setImageResource(0); ただし、場合によっては、特定の画像のロード、リリース、リロードを繰り返す可能性があり、非効率です...


4. その他の方法

以下の方法は使用していません。関連情報はご自身で確認できます。 :

1. SoftReference リファレンスを通じて画像リソースを管理するだけです

SoftReference ハッシュマップを作成します イメージを使用する場合は、まずハッシュマップにソフトリファレンスがあるかどうか、およびソフトリファレンス内のイメージが空かどうかを確認します。 空の場合は、イメージをソフトリファレンスにロードし、ハッシュマップに追加します。 コード内でイメージのリサイクルと解放を明示的に処理する必要はなく、gc がリソースの解放を自動的に処理します。 この方法は扱いが簡単で実用的であり、以前の方法のロードとリリースを繰り返す非効率性をある程度回避できます。しかし、十分に最適化されていません。

サンプルコード:

private Map<String, SoftReference> imageMap 
                                           = new HashMap<String, SoftReference>();

public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) {
    SoftReference reference = imageMap.get(imageUrl);
    if(reference != null) {
        if(reference.get() != null) {
            return reference.get();
        }
    }
    final Handler handler = new Handler() {
        public void handleMessage(final android.os.Message msg) {
            //加入到缓存中
            Bitmap bitmap = (Bitmap)msg.obj;
            imageMap.put(imageUrl, new SoftReference(bitmap));
            if(imageCallBack != null) {
                imageCallBack.getBitmap(bitmap);
            }
        }
    };
    new Thread(){
        public void run() {
            Message message = handler.obtainMessage();
            message.obj = downloadBitmap(imageUrl);
            handler.sendMessage(message);
        }
    }.start();
    return null ;
}

// 从网上下载图片
private Bitmap downloadBitmap (String imageUrl) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream());
        return bitmap ;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    } 
}
public interface ImageCallBack{
    void getBitmap(Bitmap bitmap);
}

2. LruCache + SDキャッシュ方法

Android 3.1からは、保存された画像のサイズがLruCacheよりも大きい場合に、公式でもキャッシュ処理用にLruCacheが提供されます。 この値が設定されている場合、最も最近使用されていない画像がリサイクルされ、システムは自動的にメモリを解放します。

使用例:

手順:

1) まず、キャッシュされた画像のメモリサイズを携帯電話のメモリの1/8に設定します。 携帯電話のメモリの取得方法: int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);

2) LruCache のキーと値のペアは、それぞれ URL と対応する画像です

3)書き換えられた sizeOf と呼ばれるメソッドは、画像の数を返します。

private LruCache mMemoryCache;
private LruCacheUtils() {
    if (mMemoryCache == null)
        mMemoryCache = new LruCache(
                MAXMEMONRY / 8) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key,
                    Bitmap oldValue, Bitmap newValue) {
                Log.v("tag", "hard cache is full , push to soft cache");
               
            }
        };
}

4) 次の方法は、キャッシュをクリアする、キャッシュに写真を追加する、キャッシュから写真を取得する、キャッシュから削除するです。

キャッシュの削除とクリアは必須です。画像キャッシュが適切に処理されないとメモリオーバーフローが報告されるため、注意が必要です。

public void clearCache() {
    if (mMemoryCache != null) {
        if (mMemoryCache.size() > 0) {
            Log.d("CacheUtils",
                    "mMemoryCache.size() " + mMemoryCache.size());
            mMemoryCache.evictAll();
            Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
        }
        mMemoryCache = null;
    }
}

public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (mMemoryCache.get(key) == null) {
        if (key != null && bitmap != null)
            mMemoryCache.put(key, bitmap);
    } else
        Log.w(TAG, "the res is aready exits");
}

public synchronized Bitmap getBitmapFromMemCache(String key) {
    Bitmap bm = mMemoryCache.get(key);
    if (key != null) {
        return bm;
    }
    return null;
}

/**
 * 移除缓存
 * 
 * @param key
 */
public synchronized void removeImageCache(String key) {
    if (key != null) {
        if (mMemoryCache != null) {
            Bitmap bm = mMemoryCache.remove(key);
            if (bm != null)
                bm.recycle();
        }
    }
}

上記内容は、画像キャッシュメモリキャッシュ技術 LruCache、ソフトリファレンスより抜粋


このセクションの概要:

このセクションでは、OOM 問題の原因を説明し、インターネット OOM で提供されているいくつかの回避方法もまとめていますビットマップが原因 同社が作成したアプリはすべてマップベースであり、写真がほとんど含まれていないため、作成者は OOM の問題に遭遇したことがありません。 私はこれについてはあまり詳しくありません。次のメモリ管理の上級コースでは、この OOM 問題にゆっくりと取り組んでいきます。 このセクションは以上です、ありがとうございます~


参考資料: Android アプリケーションにおける OOM 問題の分析と解決策