Androidステップアップトレーニング-Bitmapを効率的に表示(Bitmapsキャッシュ)

55652 ワード

Bitmapsのキャッシュ
個々のピクチャをロードして表示するのは簡単ですが、大量のピクチャを一度にロードすると複雑になり、多くの場合(
  ListView
GridView
 or  ViewPagerのようなコンポーネント)は、画面に表示されている画像に、表示されるすべての画像を加えると、すぐに画面上で無制限にスクロールしたり、切り替えたりする可能性があります.
ListView
GridViewのようなコンポーネントでは、サブアイテムが表示されない場合、フロントでサブアイテムを表示するために使用されるメモリが回収されます.ゴミ回収器は、ロードした画像に消費されるメモリも解放されます.ライフサイクルの長いオブジェクトではないと仮定します.これらはメモリの有効利用に優れていますが、UIをスムーズに実行したい場合は、表示するたびに画像を再ロードするべきではありません.このとき、メモリとファイルキャッシュを維持する必要があります.これにより、処理画像を迅速に再ロードすることができます.
メモリキャッシュの使用
メモリキャッシュは、アプリケーション内のコンポーネントにデータを迅速に提供できるように、アプリケーションのメモリを予め消費してデータを格納するための典型的な空間的な時間交換ポリシーです.LruCache
クラス(Android v 4 Support Libraryクラスライブラリで提供開始)は、画像キャッシュタスクに最適です.LinkedHashMap
最近使用されたオブジェクトを保存するために使用され、保存されたオブジェクトが消費するメモリの合計が設計された最大メモリを超えた場合、頻繁に使用されないオブジェクトメンバーがゴミ回収器の回収のために蹴り出されます.
Note:以前は、SoftReference or WeakReferenceを使用するメモリキャッシュが非常に流行していましたが、この方法は現在推奨されていません.Android 2.3から、ごみ回収器はソフトリファレンスと弱いリファレンスの対象をより積極的に回収し、このようなやり方はかなり無効になります.また、Android 3.0の前に、画像データはローカルメモリに保存され、予見可能な方法で解放されるわけではありません.これにより、アプリケーションメモリの消費量が一時的に超過し、アプリケーションがクラッシュする可能性があります.
    
のために
  LruCache適切なメモリサイズを設定するには、次のような要因が考えられます.
          
           
一、あなたのactivityと/またはアプリケーションにどれだけのメモリが残っているか
二、スクリーンに一度に何枚の画像を表示する必要があり、どれだけの画像がスクリーンに表示されるのを待っているか
三、携帯電話の大きさと密度はいくらで、1つの超高密度スクリーンの設備(Galaxy Nexus
)より大きなキャッシュが必要になることが多い
四、画像のサイズと配置はいくらで、各画像のメモリの大きさを決定しました
五、画像のアクセス頻度はどのくらいで、他のアクセスよりも頻繁であるかどうか.もしそうなら、メモリにそれらをずっと保存する必要があるかもしれません.
用途の異なる画像グループに異なるものを配置する必要もありますLruCache
オブジェクト
六、時には画像の品質と数量をバランスさせ、大量の低品質の画像を保存し、また一時的に高品質の画像バージョンをロードするのは役に立ちます.
     
ここにはすべてのアプリケーションを適用する固定的なサイズや式はありません.アプリケーションの画像の使用状況を詳しく分析して、適切な解決策を見つける必要があります.
1つの設定
  LruCacheの小さな例
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            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);
}

Note:この小さな例のプログラムでは、LruCacheにキャッシュサイズを設定し、アプリケーションが最大でメモリを使用できる1/8を設定し、中密度や高密度のデバイスでは、キャッシュサイズが最低4 M(32/8)で、GridViewを例にとると、GridViewが画面いっぱいに画像を表示すると1.5 M(800*480*4 bytes)のメモリが消費されるので、少なくとも2.5ページ表示する画像データをキャッシュすることができます
ImageViewに画像をロードすると、LruCacheでキャッシュされているかどうかを確認し、ある場合はImageViewに直接更新し、ない場合はバックグラウンドスレッドがトリガーされてこの画像をロードします.
public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    //                  
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}

 
画像がロードされたTaskでは、ロードされた画像をメモリキャッシュに追加する必要があります.
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

ディスク・ファイル・キャッシュの使用
メモリキャッシュは、最近表示された画像をすばやく取得できますが、必ずしも取得できるわけではありません.像
  GridView
などのコンポーネントには大きなデータセットがあり、簡単にメモリキャッシュを満たすことができます.あなたのアプリケーションも他のタスク(着信など)に中断され、アプリケーションがバックグラウンドに切り込まれ、殺され、メモリキャッシュオブジェクトも破棄される可能性があります.アプリケーションがフロントに戻って表示されると、アプリケーションはまた1枚1枚画像をロードする必要があります.
ディスクファイルキャッシュは、これらの状況を処理し、処理されたピクチャを保存し、メモリキャッシュが使用できない場合、ハードディスクに保存されたピクチャを直接読み出すことで、ピクチャのロード回数を効果的に減らすことができます.ディスク・ファイルの読み取りは、メモリ・キャッシュから直接読み込むよりも遅く、ディスクの読み取り速度が保証されないため、UIのメイン・スレッド外のスレッドで行う必要があります.ディスク・ファイル・キャッシュも、スペースと時間を切り替えるポリシーであることは明らかです.
Note:画像が非常に頻繁に使用されている場合、ContentProviderは、画像galleryアプリケーションなどのキャッシュされた画像を格納するのに適している可能性があります.
次はディスク・ファイル・キャッシュを使用したプログラム・クリップです.
private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}

Note:ディスクキャッシュを初期化してもディスク操作が必要なため、UIマスタースレッドではこの操作を行うことはできません.これは、ディスクキャッシュの初期化前にアクセスできることを意味します.このような状況を回避するために、上記のプログラム・クリップでは、ロック・オブジェクトがディスク・キャッシュの初期化が完了するまでディスク・キャッシュにアクセスできないことを保証しています.
 
メモリキャッシュはUIスレッドで検出され、ディスクキャッシュはUIスレッド外のスレッドで検出され、画像処理が完了するとメモリキャッシュとディスクキャッシュにそれぞれ格納される.
デバイス構成変更の処理
アプリケーションが実行中にデバイスの構成パラメータが変更される可能性があります.例えば、デバイスの向きが変更されると、AndroidがActivityを破棄して新しい構成で再起動する可能性があります.この場合、すべての画像を再ロードして処理しないようにし、ユーザーがスムーズな体験を得ることができます.
幸いなことに、前節では画像に非常に良いメモリキャッシュを提供しました.Fragmentを使用すると、メモリキャッシュオブジェクトを新しいactivityインスタンスに渡し、呼び出すことができます.setRetainInstance(true)
)メソッドを使用して、Fragmentインスタンスを保持します.Activityが再作成されると、保存されたFragmentはactivityに依存して存在し、Fragmentによって既存のメモリキャッシュオブジェクトを取得することができ、画像を迅速に取得し、ImageViewに設定することができ、ユーザーにスムーズな体験を与えることができます.
次に、プログラムクリップの例を示します.
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment mRetainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        mRetainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //   Fragment Activity          
        setRetainInstance(true);
    }
}

デバイスの向きを変えることで上のプログラムをテストすることができます.ほとんど遅延がなく、画像がすぐに画面に表示されます.