[Android]キャッシュメカニズム


モバイル開発は本質的に携帯電話とサーバの間で通信を行い,サービス側からデータを取得する必要がある.ネットワークを繰り返してデータを取得するのは時間がかかりますが、特にアクセスが多い場合はパフォーマンスに大きく影響します.Androidではキャッシュメカニズムで頻繁なネットワーク操作を減らし、トラフィックを減らし、パフォーマンスを向上させることができます.
実装の原理
リアルタイムで更新する必要のないデータをキャッシュし、時間やその他の要因でリードキャッシュかネットワークリクエストかを判別することで、サーバの圧力を緩和し、アプリケーションの応答速度をある程度向上させ、オフライン読書をサポートすることができます.  
Bitmapのキャッシュ
多くの場合(ListView、GridView、ViewPagerなどのコンポーネント)大量の画像を一度にロードする必要があります.画面に表示される画像と表示されるすべての画像は、すぐに画面上で無制限にスクロールしたり、切り替えたりする必要があります.
ListView、GridViewのようなコンポーネントでは、サブアイテムが表示されない場合、フロントでサブアイテムを表示するために使用されるメモリが回収されます.ゴミ回収器は、ロードした画像のメモリも解放されます.UIをスムーズに実行したい場合は、表示するたびに画像を再ロードするべきではありません.メモリとファイルキャッシュを維持する必要があります.
メモリキャッシュの使用
アプリケーションのメモリを予め消費してデータを格納することで、アプリケーション内のコンポーネントにデータを迅速に提供することができ、典型的な空間で時間を変える戦略です.LruCacheクラス(Android v 4 Support Libraryクラスライブラリで提供開始)は、最近使用したオブジェクトを保存するためにLinkedHashMapの強力なインデックスを使用する画像キャッシュタスクに適しています.また、保存されたオブジェクトの合計メモリが設計された最大メモリを超えている場合、使用頻度の低いオブジェクトメンバーがゴミ回収機の回収のために蹴り出されます.
LruCacheに適切なメモリサイズを設定するには、次の要因を考慮します.
  • 残りのメモリはactivityまたはアプリケーションに
  • を使用します.
  • 画面に何枚の画像と何枚の画像を一度に表示する必要があるか
  • を待つ.
  • 携帯電話の大きさと密度はいくらですか(密度の高い設備ほど大きなキャッシュが必要です)
  • ピクチャのサイズ(使用メモリサイズが決定)
  • ピクチャのアクセス頻度(周波数の高いメモリに保存されている)
  • は、画像の品質(異なる画素が異なる場合に表示される)
  • を保存する.
    具体的には、アプリケーション画像の使用状況に応じて適切な解決策を見つけ、LruCacheの例を設定します.
    private LruCache<String, Bitmap> mMemoryCache;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        //              ,         OutOfMemory   
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    
        //  1/8           
        final int cacheSize = maxMemory / 8;
    
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                //        item   , cache size(  1024   )
                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);
    }

    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> {
        ...
        //      
        @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のメイン・スレッド外のスレッドで行う必要があります.ディスク・ファイル・キャッシュも明らかに空間的に時間を変えるポリシーです.
    画像が非常に頻繁に使用される場合、ContentProviderは、画像galleryアプリケーションなどのキャッシュされた画像を格納するのに適している可能性があります.
    次はDiskLruCacheのコードの一部です.
    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) {
        ...
        //        
        ...
        //              
        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; //      
      mDiskCacheLock.notifyAll(); //       
            }
            return null;
        }
    }
    
    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        ...
        //        
        @Override
        protected Bitmap doInBackground(Integer... params) {
            final String imageKey = String.valueOf(params[0]);
    
            //             
            Bitmap bitmap = getBitmapFromDiskCache(imageKey);
    
            if (bitmap == null) { //             
     final Bitmap bitmap = decodeSampledBitmapFromResource(
                        getResources(), params[0], 100, 100));
            }
    
            //    final   bitmap     
            addBitmapToCache(imageKey, bitmap);
    
            return bitmap;
        }
        ...
    }
    
    public void addBitmapToCache(String key, Bitmap bitmap) {
        //        
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    
        //       
        synchronized (mDiskCacheLock) {
            if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
                mDiskLruCache.put(key, bitmap);
            }
        }
    }
    
    public Bitmap getBitmapFromDiskCache(String key) {
        synchronized (mDiskCacheLock) {
            //              
            while (mDiskCacheStarting) {
                try {
                    mDiskCacheLock.wait();
                } catch (InterruptedException e) {}
            }
            if (mDiskLruCache != null) {
                return mDiskLruCache.get(key);
            }
        }
        return null;
    }
    
    public static File getDiskCacheDir(Context context, String uniqueName) {
        //          ,         ,        
    final String cachePath =
                Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
    !isExternalStorageRemovable() ?getExternalCacheDir(context).getPath():context.getCacheDir().getPath();
    
        return new File(cachePath + File.separator + uniqueName);
    }

    この操作は、ディスクキャッシュの初期化にもディスクを操作する必要があるため、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) {
                ... //             
            }
            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);
        }
    }

    Fragment(インタフェースのないサービスクラスFragment)を適用せずにデバイス画面を回転できます.キャッシュを保持している場合は、Activityに画像を埋め込むのはほとんど瞬時にメモリから取り出され、遅延の感じがしないことがわかります.任意のピクチャはメモリキャッシュから優先的に取得され、なければハードディスクキャッシュに検索され、いずれもなければ通常の方法でピクチャがロードされます.参照先:
    Caching Bitmaps
    LruCache
    SQLiteによるキャッシュ
    ネットワークリクエストデータが完了すると、url(一般的には一意の表示)、ダウンロード時間、期限切れ時間など、ファイルに関する情報をデータベースに格納します.次にロードするときはurlに基づいてデータベースからクエリーし、クエリーが期限切れでない場合はパスに基づいてローカルファイルを読み込み、キャッシュの効果を実現します.
    注意:キャッシュされたデータベースは/data/data//databases/ディレクトリの下に保存され、メモリ領域を占有しています.キャッシュが累計されると、メモリが浪費されやすく、キャッシュをタイムリーにクリーンアップする必要があります.
    ファイルキャッシュ
    一般的なキャッシュと同様に、必要なデータをファイルに格納し、次回のロード時にファイルの存在と期限切れ(File.lastModified()メソッドを使用してファイルの最終修正時間と現在の時間判断)を判断し、期限切れでない場合はファイルのデータをロードし、そうでない場合はサーバに再ダウンロードを要求する.
    ネットワーク環境なしでは、ファイルキャッシュの読み込みがデフォルトで行われます.