Androidの画像キャッシュImageCache

11191 ワード

なぜキャッシュを作成するのですか?      
UIインタフェースで1枚の画像をロードするのは簡単ですが、大きな画像を複数ロードする必要があると、より複雑になります.多くの場合(ListView、GridView、ViewPagerなどのコンポーネント)、画面上のピクチャの総数は、画面のスクロールに伴って大幅に増加し、基本的には無限である.
メモリの使用を安定した範囲内に保つため、OOMの発生を防止するために、これらのコンポーネントはサブviewに画面を描いた後、リソースの回収を行い、新しく現れた画像を再表示し、ゴミ回収メカニズムは表示されなくなった画像のメモリ空間を解放します.しかし、このように頻繁に画像のロードと回収を処理することは、操作のスムーズさに不利であり、メモリまたはディスクのCacheは、この問題を解決し、ロードされた画像を迅速にロードするのに役立ちます.
キャッシュには、主にLruCacheとDiskLruCacheの2つのレベルがあります.前者はメモリベース、後者はディスクベースです.
メモリのキャッシュ方法
メモリキャッシュによりキャッシュ画像をすばやくロードできますが、アプリケーションのメモリ領域が消費されます.LruCacheクラス(互換パッケージでsdk 4をサポート)は、LinkedHashMapによってピクチャの強い参照を維持することでピクチャを格納し、キャッシュスペースが所定の制限値を超えると初期のキャッシュを解放するピクチャキャッシュに適しています.
注意:従来、よく使われていたメモリキャッシュの実装はSoftReferenceまたはWeakReferenceで行われていましたが、推奨されていません.Android 2から3(APIレベル9)ゴミ収集器は、ソフト/弱参照のより積極的な収集を開始し、かなり無効になる.また、Android 3.0(APIレベル11)までnativeメモリに格納されている可視bitmapは解放されず、アプリケーションが一時的にメモリ制限を超えてクラッシュする可能性があります.
 
LruCacheに適切なサイズを設定するには、以下の要因を考慮する必要があります.
  • あなたのアプリケーションの空きメモリはどれくらいですか?
  • 画面に何枚の画像を一度に表示しますか?表示するために何枚の画像を用意しますか?
  • デバイスのスクリーンサイズとdensityはいくらですか?超高画面densityのデバイス(xhdpi)Galaxy NexusはNexus S(hdpi)のようなデバイスよりも同じピクチャをキャッシュする際により大きなCache空間を必要とする.
  • ピクチャのサイズとプロパティとメモリ容量はどれくらい必要ですか?
  • 画像のアクセス頻度はいくらですか?他の画像より使用頻度が高いですか?この場合、画像を長期にわたってメモリに保存するか、異なるタイプの画像に対して異なるキャッシュポリシーを使用することを考慮する必要があります.
  • 品質と数量をどのようにバランスさせるかについては、よく使われる低品質の画像ユーザー表示を格納し、非同期スレッドで高品質の画像をロードすることがあります.

  • 画像キャッシュスキームは固定的なモードですべてのアプリケーションを使用していません.アプリケーションの具体的なアプリケーションシーンに基づいて分析し、適切なスキームを選択して行う必要があります.キャッシュが小さすぎてキャッシュの優位性を発揮できません.メモリが多すぎて、アプリケーションの性能を低下させたり、メモリオーバーフロー異常が発生したりする可能性があります.
    LruCacheを使用する例を次に示します.
    private LruCache mMemoryCache; 
                                                                             
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    { 
        ... 
        // Get memory class of this device, exceeding this amount will throw an OutOfMemory exception. 
        final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); 
                                                                             
        // Use 1/8th of the available memory for this memory cache. 
        final int cacheSize = 1024 * 1024 * memClass / 8; 
                                                                             
        mMemoryCache = new LruCache(cacheSize) 
        { 
            @Override
            protected int sizeOf(String key, Bitmap bitmap) 
            { 
                // The cache size will be measured in bytes rather than number of items. 
                return bitmap.getByteCount(); 
            } 
        }; 
        ... 
    } 
                                                                             
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) 
    { 
        if (getBitmapFromMemCache(key) == null) 
        { 
            mMemoryCache.put(key, bitmap); 
        } 
    } 
                                                                             
    public Bitmap getBitmapFromMemCache(String key) 
    { 
        return mMemoryCache.get(key); 
    }

    なお、この例では、8分の1のメモリがピクチャキャッシュに割り当てられ、通常/hdpiデバイスでは約4 MB(32/8)である.GirdViewフルスクリーンでは、800 x 480解像度のデバイスに1.5 Mピクチャ空間(800*480*4 bytes)が必要であり、メモリに2.5スクリーンのピクチャをキャッシュすることができます.
     
    LruCacheを使用してImageViewにピクチャを追加する場合は、まずピクチャが存在するかどうかを確認し、直接ImageViewを実行しないと、バックグラウンドスレッドでピクチャをロードします.
    public void loadBitmap(int resId, ImageView imageView) 
    { 
        final String imageKey = String.valueOf(resId); 
                                                             
        final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
        if (bitmap != null) 
        { 
            imageView.setImageBitmap(bitmap); 
        } 
        else 
        { 
            imageView.setImageResource(R.drawable.image_placeholder); 
            BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
            task.execute(resId); 
        } 
    }

     
    BitmapWorkerTaskでは、ロードされたピクチャをキャッシュに追加する必要があります.
    class BitmapWorkerTask extends AsyncTask 
    { 
        ... 
        // 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; 
        } 
        ... 
    }

     
    ディスクキャッシュの使用方法
    メモリキャッシュは、最近使用した画像にアクセスするのに効率的ですが、常にキャッシュされていることは保証されません.GirdViewのような大きなデータ量のコンポーネントは、メモリキャッシュに満ちやすい.あなたのアプリケーションは「着信」で中断される可能性があります.バックグラウンドで殺される可能性があります.メモリキャッシュは失効します.ユーザーがアプリケーションに戻ると、各画像を再処理する必要があります.
    この場合、ディスクキャッシュを使用して処理されたピクチャを格納することができます.ピクチャがメモリに含まれなくなった場合、再ロードの時間を減らすことができます.もちろん、ディスクからピクチャをロードするときはメモリよりも遅く、バックグラウンドスレッドで行う必要があります.ディスクの読み取り時間は未知です.
    注意:画像によくアクセスする場合は、ContentProviderは、Gallery画像管理アプリケーションなどの画像を格納するのに良い場所である必要があります.
    以下は簡単なDiskLruCache実装です.ただし、推奨実装DiskLruCache案はAndroid 4を参照してください.0(libcore/luni/src/main/java/libcore/io/diskLruCache.java)のソースコード.本明細書では、以前のバージョンの簡単な実装(Quick Searchでは別の実装)を用いる.
    DiskLruCacheの更新を簡単に実現した例を示します.
    private DiskLruCache mDiskCache; 
    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 
        ... 
        File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); 
        mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); 
        ... 
    } 
                                       
    class BitmapWorkerTask extends AsyncTask 
    { 
        ... 
        // 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(String.valueOf(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 
        if (!mDiskCache.containsKey(key)) 
        { 
            mDiskCache.put(key, bitmap); 
        } 
    } 
                                       
    public Bitmap getBitmapFromDiskCache(String key) 
    { 
        return mDiskCache.get(key); 
    } 
                                       
    // 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 getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED || 
                                 !Environment.isExternalStorageRemovable()) ? 
                                 context.getExternalCacheDir().getPath() : 
                                 context.getCacheDir().getPath(); 
                                       
        return new File(cachePath + File.separator + uniqueName); 
    }

    メモリキャッシュチェックはUIスレッドで行い、ディスクキャッシュのチェックはバックグラウンドスレッドで行います.ハードディスク(HDD)の操作はUIスレッドにはありません.画像処理が完了したら、使用中のメモリ、ディスクキャッシュに追加します.
    構成の変更をどのように処理しますか?
    アプリケーションの実行中に構成が変更された場合、画面の方向が変更された場合、新しい構成Androidを適用するために現在のActivityを再実行することは破棄されます.この場合、ユーザーに迅速で緩やかなユーザー体験を与えるために、画像を再ロードしたくない場合があります.
    キャッシュ・テクノロジーを実行したおかげで、setRetainInstance(true))を使用して新しいActivityにキャッシュを渡すことができ、Activityが再起動した後、既存のキャッシュをアタッチされたFragmentで再使用することで、ImageViewに迅速にロードできます.
    次に、構成が変更されたときに既存のキャッシュをFragmentで再利用する例を示します.
    private LruCache mMemoryCache; 
                      
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    { 
        ... 
        RetainFragment mRetainFragment = 
                RetainFragment.findOrCreateRetainFragment(getFragmentManager()); 
        mMemoryCache = RetainFragment.mRetainedCache; 
        if (mMemoryCache == null) 
        { 
            mMemoryCache = new LruCache(cacheSize) 
            { 
                ... // Initialize cache here as usual 
            } 
            mRetainFragment.mRetainedCache = mMemoryCache; 
        } 
        ... 
    } 
                      
    class RetainFragment extends Fragment 
    { 
        private static final String TAG = "RetainFragment"; 
        public LruCache 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); 
            setRetainInstance(true); 
        } 
    }

    テストのために、Fragmentを使用している場合と使用していない場合、画面を強制的に回転させると、メモリキャッシュからピクチャをロードするときにヒステリシスがほとんどないことがわかります.
     
    回転元:リンク