Android ImageLoaderフレームワークの画像キャッシュを書くことを教えます(完結編)


Android ImageLoaderフレームワークシリーズの作成を教えるブログでは、基本アーキテクチャから実装まで、ほとんどの内容を更新しました.今日は、最後のキー、すなわち画像のキャッシュについて説明します.ユーザー体験のために、通常はダウンロードした画像をキャッシュします.一般的にはメモリとローカルに画像キャッシュがあります.それはフレームワークである以上、必ず良いカスタマイズ性が必要であり、抽象的なことを自然に考えさせます.キャッシュの実装を見てみましょう.
キャッシュインタフェース
Android ImageLoaderフレームワークの画像ロードとロードポリシーを教えて、Loaderについて話して、AbsLoaderの基本的な論理を述べて、その中に画像キャッシュがあります.したがって、AbsLoaderには必ずキャッシュオブジェクトの参照が含まれます.関連コードを見てみましょう.
/** * @author mrsimple */
public abstract class AbsLoader implements Loader {

    /** *      */
    private static BitmapCache mCache = SimpleImageLoader.getInstance().getConfig().bitmapCache;

    //     
}

AbsLoaderでは、ピクチャキャッシュオブジェクトであるstaticのBitmapCacheオブジェクトが定義されています.なぜstaticなの?Loaderがいくつあっても、キャッシュオブジェクトは共有されるべきです.つまり、キャッシュは1つしかありません.そんなにたくさん話したので、まずBitmapCacheについて知りましょう.
public interface BitmapCache {

    public Bitmap get(BitmapRequest key);

    public void put(BitmapRequest key, Bitmap value);

    public void remove(BitmapRequest key);

}

BitmapCacheは簡単で、取得、追加、削除の3つの方法だけを宣言してピクチャキャッシュを操作します.ここにはBitmapRequestクラスが依存しており、このクラスは、そのリクエストに対応するImageView、ピクチャuri、表示Configなどの属性を持つピクチャロードリクエストを表す.このキャッシュでは主にピクチャのuriを用いてキャッシュにそのピクチャが含まれているかどうかを検索し,キャッシュはピクチャのuriをkey,Bitmapをvalueとして格納を関連付ける.また、BitmapRequestのImageViewの幅と高さが必要で、画像をサイズ別にロードします.
BitmapCacheインタフェースを定義するか、拡張性のためにインタフェース向けのプログラミングの理念が再びあなたの前に浮かび上がっています.もしあなただったら、あなたはどんなデザインをしますか?自分でコードを書いて練習しましょう.自分がどう考えているかを見てみましょう.もし実現すれば、あなたはそこからもっと深い悟りを得ることができます.
メモリキャッシュ
フレームワークである以上,ユーザの様々なニーズを受け入れる必要がある.しかし、通常、フレームワークにはデフォルトの実装があり、ピクチャキャッシュにとってメモリキャッシュはデフォルトの実装の1つであり、ロードされたピクチャをメモリにキャッシュし、ピクチャの繰り返しロードの速度を大幅に向上させる.メモリキャッシュのポリシーはLRUアルゴリズムを使用し、supportを直接使用することです.v 4のLruCacheクラス、関連コードは以下の通りです.
/** *        ,key    uri,       * * @author mrsimple */
public class MemoryCache implements BitmapCache {

    private LruCache<String, Bitmap> mMemeryCache;

    public MemoryCache() {

        //           
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        //  4            
        final int cacheSize = maxMemory / 4;
        mMemeryCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };

    }

    @Override
    public Bitmap get(BitmapRequest key) {
        return mMemeryCache.get(key.imageUri);
    }

    @Override
    public void put(BitmapRequest key, Bitmap value) {
        mMemeryCache.put(key.imageUri, value);
    }

    @Override
    public void remove(BitmapRequest key) {
        mMemeryCache.remove(key.imageUri);
    }

}

BitmapCacheインタフェースを簡単に実装し、内部でLruCacheクラスを使用してメモリキャッシュを実装します.簡単ですから、説明しません.
sdカードキャッシュ
ピクチャキャッシュでは、メモリキャッシュが不足しており、sdカードにピクチャをキャッシュする必要があります.これにより、ユーザーは次回appに入るときに直接ローカルからピクチャをロードすることができ、ネットワークからピクチャデータを繰り返し読み取ることを避けることができます.つまり、トラフィックが消費され、ユーザー体験が悪いです.sdカードキャッシュJake WhartonのDiskLruCacheクラスを使用しました.sdカードキャッシュクラスはDiskCacheです.コードは以下の通りです.
public class DiskCache implements BitmapCache {

    /** * 1MB */
    private static final int MB = 1024 * 1024;

    /** * cache dir */
    private static final String IMAGE_DISK_CACHE = "bitmap";
    /** * Disk LRU Cache */
    private DiskLruCache mDiskLruCache;
    /** * Disk Cache Instance */
    private static DiskCache mDiskCache;

    /** * @param context */
    private DiskCache(Context context) {
        initDiskCache(context);
    }

    public static DiskCache getDiskCache(Context context) {
        if (mDiskCache == null) {
            synchronized (DiskCache.class) {
                if (mDiskCache == null) {
                    mDiskCache = new DiskCache(context);
                }
            }

        }
        return mDiskCache;
    }

    /** *    sdcard   */
    private void initDiskCache(Context context) {
        try {
            File cacheDir = getDiskCacheDir(context, IMAGE_DISK_CACHE);
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            mDiskLruCache = DiskLruCache
                    .open(cacheDir, getAppVersion(context), 1, 50 * MB);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /** *   sd     ,     sd    sd   ,           。 * @param context Context * @param uniqueName      ,  bitmap * @return */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            Log.d("", "### context : " + context + ", dir = " + context.getExternalCacheDir());
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }


        @Override
    public synchronized Bitmap get(final BitmapRequest bean) {
        //      
        BitmapDecoder decoder = new BitmapDecoder() {

            @Override
            public Bitmap decodeBitmapWithOption(Options options) {
                final InputStream inputStream = getInputStream(bean.imageUriMd5);
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null,
                        options);
                IOUtil.closeQuietly(inputStream);
                return bitmap;
            }
        };

        return decoder.decodeBitmap(bean.getImageViewWidth(),
                bean.getImageViewHeight());
    }

    private InputStream getInputStream(String md5) {
        Snapshot snapshot;
        try {
            snapshot = mDiskLruCache.get(md5);
            if (snapshot != null) {
                return snapshot.getInputStream(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public void put(BitmapRequest key, Bitmap value) {
        //      
    }

    public void remove(BitmapRequest key) {
        //     
    }

}

コードは比較的簡単で、すなわちBitmapCacheを実現し、DiskLruCacheクラスをパッケージする方法で画像ファイルの増加、削除、取得方法を実現する.ここでは、画像をImageViewサイズでロードする補助クラス、すなわちBitmapDecoderを簡略化するためのクラスを紹介します.
BitmapDecoder
BitmapDecoderはImageViewサイズで画像をロードする補助クラスで、一般的に私が画像をロードする過程はこうです:1.BitmapFactoryを作成する.Options options,optionsを設定.InJustDecodeBounds=trueは、画像サイズなどの情報のみを解析するようにする.2.ImageViewのサイズに基づいて、ロードするピクチャを縮小する必要があるかどうかを確認し、スケールを計算します.3.optionsを設定する.InJustDecodeBounds=falseとなり、optionsで設定した縮小スケールでピクチャをロードする.
BitmapDecoderクラスはdecodeBitmapメソッドを使用してこのプロセス(テンプレートメソッドよ)をカプセル化し、ユーザーはサブクラスを1つだけ実装し、BitmapDecoderのdecodeBitmapWithOptionを上書きしてピクチャロードを実装すればこのプロセスを完了する(DiskCacheのgetメソッドを参照).コードは次のとおりです.
/** *        bound,   inSmallSize             * * @author mrsimple */
public abstract class BitmapDecoder {

    /** * @param options * @return */
    public abstract Bitmap decodeBitmapWithOption(Options options);

    /** * @param width         * @param height         * @return */
    public Bitmap decodeBitmap(int width, int height) {
        //       ,       
        if (width <= 0 || height <= 0) {
            return decodeBitmapWithOption(null);
        }

        // 1、     Bitmap      Option,    options.inJustDecodeBounds = true;
        BitmapFactory.Options options = getJustDecodeBoundsOptions();
        // 2、  options  bitmap,     bitmap  ,      options 
        decodeBitmapWithOption(options);
        // 3、      ,    options.inJustDecodeBounds   false;
        calculateInSmall(options, width, height);
        // 4、  options           
        return decodeBitmapWithOption(options);
    }

    /** *   BitmapFactory.Options,             */
    private Options getJustDecodeBoundsOptions() {
        //
        BitmapFactory.Options options = new BitmapFactory.Options();
        //    true,    Bitmap  ,       
        options.inJustDecodeBounds = true;
        return options;
    }

    protected void calculateInSmall(Options options, int width, int height) {
        //       
        options.inSampleSize = computeInSmallSize(options, width, height);
        //     
        options.inPreferredConfig = Config.RGB_565;
        //    false,  Bitmap        
        options.inJustDecodeBounds = false;
        options.inPurgeable = true;
        options.inInputShareable = true;
    }

    private int computeInSmallSize(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) {
            // Calculate ratios of height and width to requested height and
            // width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
            final float totalPixels = width * height;

            // Anything more than 2x the requested pixels we'll sample down
            // further
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }

}

decodeBitmapでは、まずBitmapFactoryを作成します.Optionsオブジェクト、optionsを設定します.InJustDecodeBounds=trueで、画像サイズなどの情報のみを解析するようにdecodeBitmapWithOption(options)を初めて呼び出します.次にcalculateInSmallメソッドを呼び出します.このメソッドは、ImageViewのサイズに基づいてロードするピクチャを縮小する必要があるかどうかを確認し、スケールを計算するためにcomputeInSmallSizeを呼び出します.calculateInSmallメソッドの最後にoptions.InJustDecodeBounds=falseは、次回decodeBitmapWithOption(options)を再ロードするときに画像をロードするようにします.最後のステップは必ずdecodeBitmapWithOption(options)を呼び出すことです.そうすると、画像はoptions設定の縮小割合で画像をロードします.
この補助クラスを用いて,この煩雑で反復的なプロセスをカプセル化し,コードをある程度簡略化し,コードの多重性を向上させ,テンプレートメソッドモードの好ましい例でもある.
二次キャッシュ
メモリとsdカードのキャッシュがあって、実はこれはまだ足りません.私たちのニーズは、このキャッシュにメモリとsdカードキャッシュが同時に存在する可能性が高いので、上記の2つのキャッシュの利点を備えています.ここでは、これを2次キャッシュと呼びます.コードを見てみましょう.簡単です.
/** *     ,   sd     * * @author mrsimple */
public class DoubleCache implements BitmapCache {
    DiskCache mDiskCache;
    MemoryCache mMemoryCache = new MemoryCache();

    public DoubleCache(Context context) {
        mDiskCache = DiskCache.getDiskCache(context);
    }

    @Override
    public Bitmap get(BitmapRequest key) {
        Bitmap value = mMemoryCache.get(key);
        if (value == null) {
            value = mDiskCache.get(key);
            saveBitmapIntoMemory(key, value);
        }
        return value;
    }

    private void saveBitmapIntoMemory(BitmapRequest key, Bitmap bitmap) {
        //   Value disk   ,        
        if (bitmap != null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    @Override
    public void put(BitmapRequest key, Bitmap value) {
        mDiskCache.put(key, value);
        mMemoryCache.put(key, value);
    }

    @Override
    public void remove(BitmapRequest key) {
        mDiskCache.remove(key);
        mMemoryCache.remove(key);
    }

}

メモリキャッシュとsdカードキャッシュをカプセル化した操作ですか~では、もう口をつぐんではいけません
カスタムキャッシュ
キャッシュには多くの実装ポリシーがあります.拡張性が必要である以上、ユーザーが自分のキャッシュ実装に注入できるようにします.BitmapCacheを実装すれば、ImageLoaderConfigを介してImageLoader内部に注入できます.
    private void initImageLoader() {
        ImageLoaderConfig config = new ImageLoaderConfig()
                .setLoadingPlaceholder(R.drawable.loading)
                .setNotFoundPlaceholder(R.drawable.not_found)
                .setCache(new MyCache())
        //    
        SimpleImageLoader.getInstance().init(config);
    }

MyCache.java
//         
public class MyCache implements BitmapCache {

    //   

    @Override
    public Bitmap get(BitmapRequest key) {
        //     
    }

    @Override
    public void put(BitmapRequest key, Bitmap value) {
        //      
    }

    @Override
    public void remove(BitmapRequest key) {
        //     
    }

}

Githubアドレス
SimpleImageLoader.
まとめ
ImageLoaderシリーズはここまで終わっても、基本アーキテクチャ、具体的な実現、設計の上から簡単で、拡張性の良いImageLoaderの実現過程を詳しく述べました.このシリーズを見た後、自分で実現することを望んでいます.そうすれば、具体的な問題を発見し、より深く理解することができます.もしあなたがこのシリーズのブログを見ているうちに、「私は拭いて、copyで使えるImageLoaderを見つけました」というのではなく、オブジェクト向けの基本原則、デザイン思考などを本当に体得することができたら、私がしたこれらの共有は目的に達したと思います.