最近は画像ブラウザに取り組んでいますが、大量の画像を読み込むとOOMが発生しました。通常、画像の読み込み時に発生するこの問題を解決するために、Google の公式ドキュメントを確認し、学んだ知識を記録しました。助けて。
まず、ビットマップの使用の最適化に注意を払う必要がある理由を見てみましょう
。1.モバイルデバイスのシステムリソースは通常限られています。たとえば、Androidデバイスでは、各アプリケーションを最大16Mに制限できます。つまり、アプリケーションは16M未満のメモリを使用するように最適化する必要があります。
2.ビットマップはたまたまメモリの大部分です。たとえば、Galaxy Nexusは最大2592x1936のピクセルで写真を撮ることができます。次に、ビットマップはデフォルトでARGB_8888を使用して色を解析します。次に、この画像は約19M(2592 * 1936 * 4バイト)を占めます。このように、今の例のメモリ制限はすぐに超えました。
3. Android uiはビットマップを頻繁に使用します。たとえば、listview、gridview、viewpager。通常、ページには多くのビットマップが表示されます。また、表示されていないスライド先のパーツも含まれます。
多くの場合、インターフェースに表示される画像は、実際の画像のピクセル要件よりも低いことがよくあります。また、低ピクセル要件のインターフェイスでこれらの高ピクセル画像を使用しても、表示効果の利点はありません。逆に、このプロセスはスケーリングを必要とするため、大量のメモリを消費し、効率に影響します。
BitmapFactoryは、さまざまなデータソース(decodeByteArray()、decodeFile()、decodeResource()など)に一連の分析メソッドを提供します。これらのメソッドは、ビットマップを構築するときにメモリを割り当てるため、OOMが発生しやすくなります。各分析メソッドには、分析のプロパティを定義するためのオプションのパラメーターBitmapFactory.Optionsが用意されています。 inJustDecodeBoundsプロパティをtrueに設定すると、解析中にメモリが割り当て られないようにすることができます。このとき、解析によりnullが返されますが、outWidth、outHeigth、outMimeTypeがオプションに割り当てられます。 この手法では、メモリを割り当てずにビットマップのサイズとタイプを取得できます。コードを見てください:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
大きな画像を圧縮する
画像をどのように圧縮できますか?これは、BitmapFactory.OptionsのinSampleSizeの値を設定することで実現できます。たとえば、2048 * 1536ピクセルの画像がある場合、inSampleSizeの値を4に設定することにより、この画像を512 * 384ピクセルに圧縮できます。元々この画像の読み込みには13Mのメモリが必要ですが、圧縮後は0.75Mしかかかりません(画像のタイプがARGB_8888であると仮定すると、各ピクセルは4バイトを占有します)。次のメソッドは、入力の幅と高さに基づいて適切なinSampleSize値を計算できます。
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
このメソッドを使用するには、最初にBitmapFactory.OptionsのinJustDecodeBoundsプロパティをtrueに設定して、画像を一度解析する必要があります。次に、BitmapFactory.Optionsを希望の幅と高さとともにcalculateInSampleSizeメソッドに渡すと、適切なinSampleSize値を取得できます。画像を再度解析し、新しく取得したinSampleSize値を使用して、inJustDecodeBoundsをfalseに設定すると、圧縮された画像を取得できます。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
次のコードは、画像を100 * 100のサムネイルに圧縮して、ImageViewに表示するのが非常に簡単です。
第二に、画像キャッシュ技術を使用する
LruCacheを使用して画像をキャッシュする例を次に示します。
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
この例では、システムによってアプリケーションに割り当てられたメモリの1/8がキャッシュサイズとして使用されます。中〜高構成の電話では、おそらく4メガバイト(32/8)のキャッシュスペースがあります。フルスクリーンのGridViewは、4つの800x480解像度の画像を使用して塗りつぶし、約1.5メガバイトのスペース(800 * 480 * 4)を占有します。したがって、このキャッシュサイズは2.5ページの画像を保存できます。
画像をImageViewにロードするとき、最初にLruCacheのキャッシュでそれをチェックします。対応するキー値が見つかった場合、ImageViewはすぐに更新されます。それ以外の場合は、バックグラウンドスレッドが開始され、画像を読み込みます。
- public void loadBitmap(int resId、ImageView imageView){
- final String imageKey = String.valueOf(resId);
- 最終的な ビットマップビットマップ= getBitmapFromMemCache(imageKey);
- if (ビットマップ!= null ){
- imageView.setImageBitmap(bitmap);
- } else {
- imageView.setImageResource(R.drawable.image_placeholder);
- BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- task.execute(resId);
- }
- }