効率的なBitmapのロード(Displaying Bitmaps Efficiently)

15294 ワード

注:英语版のApi Guideを见ていつも卵が痛いと感じて、今1篇の比较的に重要な内容を翻訳します:Bitmapの効率的なロード、翻訳した后に本当にこの文章の核心の思想を理解することができることを望みます.
一般的に、私たちがネット上にロードした画像のサイズは携帯電話の画面の解像度よりも大きく、携帯電話のメモリは極めて限られているので、アンドロイドの開発では、画像を効率的に処理することが重要な一環です.アンドロイドの開発には、画像に出会ったら、必ず厳しく処理しなければならないという不文律がある.
効率的なBitmapのロード
BitmapFatoryクラスでは、decodeByteArray()、decodeFile()、decodeResource()など、多くの画像を解析する方法が提供されています.これらの方法は画像を我々が必要とするBitmapに変換することができる.しかし、一般的には、携帯電話の解像度が限られており、大きな画像を表示する必要はありません.メモリに完全にロードする必要もありません.そのため、画像をロードする過程でいくつかの処理が必要です.ここでBitmapFactoryは、画像を効率的にロードするためのOptionオプションを提供しています.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//   inJustDecodeBounds   true,         ,              。
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;//      
int imageWidth = options.outWidth;//      
String imageType = options.outMimeType;//     Mime  

上記のコードを用いると,画像をロードせずに画像の幅を事前に得てスケーリング処理を行うことができる.
画像のサイズはわかりましたが、完全な画像をロードするかどうかを決める必要があります.次のような部分を考える必要があります.
1、どれだけのメモリで画像をロードしたいですか.2、ImageViewはどのくらいのサイズを表示する必要がありますか.
例えば、ImageView表示画素が128*96の場合、1024*768のピクチャをメモリにロードするのは適切ではないことは明らかである.画像の解像度をロードするには128*96程度でいいです.画像のスケールを決定するには、次のようにします.
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;//    

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

スケールを決定すると、画像を部分的にロードできます.
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    //       
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    //                   false
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Bitmapが得られ、imageViewに入れて表示することができます.
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 128, 96));

非同期スレッドへのBitmapのロード
また、アプリをよりスムーズにするためには、BitmapFatoryなどのdecode*などの方法は非UIスレッドに入れて処理する.結局、ピクチャのロードは比較的時間のかかる操作です.ここでは、私たちの古い友达:AsynkTaskで処理することができます.
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 128, 96));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        //    imageViewReference        
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

BitmapWorkerTaskでは弱い参照を定義しました.AsyncTaskがImageViewのゴミ回収(WeakReference弱参照)を阻止しないことを保証するために、保存されたオブジェクトインスタンスはGCによって回収されることができる.このクラスは通常、オブジェクトリファレンスをどこかに保存するために使用され、そのオブジェクトがGCによって回収されることを妨げない.SoftReferenceソフトリファレンスは、JVMがOutOfMemoryに近づいていない限り、GCによって回収されない.この特性により、設計オブジェクトCacheに特に適している)ImageView、Activityなどの大きなオブジェクトを扱う場合、弱いリファレンスを採用するのは非常に良い習慣です.ここでは,バックグラウンドスレッドでピクチャをロードし,ロードが完了した後にsetImageBitmapを設定することで,プライマリスレッドのブロックを回避する.
次に、画像をロードできます.
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

ListViewとGridViewのピクチャロード最適化
ListViewとGridViewはAbsListViewを継承しており、スクロール時のViewの繰り返しロードを最適化するためのリスト項目の多重化機能があります.しかし、AsynkTaskによってリストアイテムのピクチャをロードすると問題があります.そのうちの1つのリストアイテムにピクチャをロードすると、ピクチャがロードされずに「出て行った」可能性があります.このリストアイテムはキャッシュとして別のリストアイテムに適用される可能性があります.これにより、ピクチャの乱れが発生します.したがって,ListViewとGridViewのピクチャロードについていくつかの同時処理を行う.
公式ドキュメントの巧みな処理を見てください.まず、ImageViewに対応するBitmapWorkerTaskを格納するBitmapDrawableを書き直します.BitmapWorkerTaskも弱い参照で参照します.
static class AsyncDrawable extends BitmapDrawable {

    //  BitmapWorkerTask
    private final WeakReference bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =  new WeakReference(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

インタフェースメソッドloadBitmap.BitmapWorkerTaskを実行する前に、このAsyncDrawableを定義し、ImagViewにバインドする必要があります.
public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        // asyncDrawable   imageView,   ImageView     
        imageView.setImageDrawable(asyncDrawable);
        //   imageView
        task.execute(resId);
    }
}

ここでAsyncDrawableは、imageViewとそれに対応するAsynkTaskを1つずつバインドします.loadBitmapメソッドは、まずcancelPotentialWorkメソッドを呼び出します.cancelPotentialWorkの目的は、1つのリスト・アイテムが、そのImageViewがロード中またはロード済みである場合、干渉しませんが、リスト・アイテムが多重化され、新しくロードされたImageViewが以前と異なる場合(多重化前にロードされたImageがまだ完了していない場合)、前のAsyncTaskを乾かすことです.実装コードは次のとおりです.
public static boolean cancelPotentialWork(int data, ImageView imageView) {

    //  imageView BitmapWorkerTask 
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    //  BitmapWorkerTask  ,       (   )    
    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        //       bitmapData  bitmapWorkerTask     data  ,         
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

AsyncDrawableからBitmapWorkerTaskを入手する方法:
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}

BitmapWorkerTaskも変更します.
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference imageViewReference;
    private int data = 0;

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }
        //          
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            //     BitmapWorkerTask   
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

ListViewまたはGridView adapterのgetView()メソッドでloadBitmap()を呼び出すことができます.getView()メソッドは頻繁に呼び出されるため、loadBitmapはロードする新しいImageViewが過去のImageViewと同じかどうかを検出し続け、異なる場合は前のタスクを殺すことで、画像の乱れを回避できます.