ビットマップのメモリキャッシュとディスクキャッシュの詳細な説明

元のテキストは、最初にWeChatパブリックアカウントで公開されました:Gongxingzhi(jzman-blog)

Androidでのキャッシュの使用は比較的一般的です。対応するキャッシュ戦略を使用すると、トラフィックの消費を削減でき、アプリケーションのパフォーマンスをある程度向上させることもできます。たとえば、ネットワーク画像をロードするときは、毎回ネットワークから画像をロードするべきではありません。これは、メモリとディスクにキャッシュされ、次回はメモリまたはディスクから直接取得されます。キャッシュ戦略は、通常、最も使用頻度の低いアルゴリズムであるLRU(Least Recently Used)アルゴリズムを使用します。以下は、メモリキャッシュとディスクキャッシュから画像を取得します。この例は、Androidでキャッシュを使用する方法を示しています。この記事を読む前に、前の記事を読んでください。

メモリキャッシュ

LruCacheは、Android 3.1で提供されるキャッシュクラスです。このクラスを使用すると、キャッシュされたBitmapオブジェクトにすばやくアクセスできます。LinkedHashMapは、強力な参照でキャッシュする必要があるBitmapオブジェクトを格納するために内部的に使用されます。キャッシュが指定されたサイズを超えると、最近使用されたリリースはほとんどありませんオブジェクトが占有するメモリ。

:Android 3.1以前は、一般的なメモリキャッシュはSoftReferenceまたはWeakReferenceビットマップキャッシュでしたが、これは推奨されなくなりました。Android 3.1以降、ガベージコレクターはSoftWeakference / WeakReferenceの回復により多くの注意を払っています。これにより、この方法を使用してキャッシュを無効にすることができます。support-v4互換パッケージでLruCacheを使用すると、Android 3.1より前のバージョンと互換性があります。

LruCacheの使用

  1. LruCacheを初期化する

最初に、必要なキャッシュサイズを次のように計算します。

//第一种方式:
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//获取当前硬件条件下应用所占的大致内存大小,单位为M
int memorySize = manager.getMemoryClass();//M
int cacheSize = memorySize/ 8;
//第二种方式(比较常用)
int memorySize = (int) Runtime.getRuntime().maxMemory();//bytes
int cacheSize = memorySize / 8;

次に、次のようにLruCacheを初期化します。

//初始化 LruCache 且设置了缓存大小
LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(cacheSize){
    @Override
    protected int sizeOf(String key, Bitmap value) {
        //计算每一个缓存Bitmap的所占内存的大小,内存单位应该和 cacheSize 的单位保持一致
        return value.getByteCount();
    }
};
  1. LruCacheにビットマップオブジェクトを追加する
//参数put(String key,Bitmap bitmap)
lruCache.put(key,bitmap)
  1. キャッシュ内の画像を取得して表示します
//参数get(String key)
Bitmap bitmap = lruCache.get(key);
imageView.setImageBitmap(bitmap);

以下は、LruCacheを使用してネットワーク画像をロードし、LruCacheの簡単な使用法を示しています。

ウェブ画像を読み込む

次のように、キャッシュされたビットマップの取得、キャッシュへのビットマップの追加、およびキャッシュからのビットマップの削除のメソッドをカプセル化する単純なImageLoaderを作成します。

//ImageLoader
public class ImageLoader {
    private LruCache<String , Bitmap> lruCache;
    public ImageLoader() {
        int memorySize = (int) Runtime.getRuntime().maxMemory() / 1024;

        int cacheSize = memorySize / 8;
        lruCache = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //计算每一个缓存Bitmap的所占内存的大小
                return value.getByteCount()/1024;
            }
        };
    }

    /**
     * 添加Bitmapd到LruCache中
     * @param key
     * @param bitmap
     */
    public void addBitmapToLruCache(String key, Bitmap bitmap){
        if (getBitmapFromLruCache(key)==null){
            lruCache.put(key,bitmap);
        }
    }

    /**
     * 获取缓存的Bitmap
     * @param key
     */
    public Bitmap getBitmapFromLruCache(String key){
        if (key!=null){
            return lruCache.get(key);
        }
        return null;
    }

    /**
     * 移出缓存
     * @param key
     */
    public void removeBitmapFromLruCache(String key){
        if (key!=null){
            lruCache.remove(key);
        }
    }
}

次に、画像を読み込むためのスレッドクラスを次のように作成します。

//加载图片的线程
public class LoadImageThread extends Thread {
    private Activity mActivity;
    private String mImageUrl;
    private ImageLoader mImageLoader;
    private ImageView mImageView;

    public LoadImageThread(Activity activity,ImageLoader imageLoader, ImageView imageView,String imageUrl) {
        this.mActivity = activity;
        this.mImageLoader = imageLoader;
        this.mImageView = imageView;
        this.mImageUrl = imageUrl;
    }

    @Override
    public void run() {
        HttpURLConnection connection = null;
        InputStream is = null;
        try {
            URL url = new URL(mImageUrl);
            connection = (HttpURLConnection) url.openConnection();
            is = connection.getInputStream();
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK){
                final Bitmap bitmap = BitmapFactory.decodeStream(is);
                mImageLoader.addBitmapToLruCache("bitmap",bitmap);
                mActivity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mImageView.setImageBitmap(bitmap);
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (connection!=null){
                connection.disconnect();
            }
            if (is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

次に、MainActivityでImageLoaderを使用してネットワークイメージをメモリに読み込み、キャッシュします。まずメモリから取得し、キャッシュに必要なビットマップがない場合は、ネットワークからイメージを取得してキャッシュに追加します。使用中にアプリケーションを終了すると、システムメモリが解放されます。主な方法は次のとおりです。

//获取图片
private void loadImage(){
    Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap");
   if (bitmap==null){
       Log.i(TAG,"从网络获取图片");
       new LoadImageThread(this,imageLoader,imageView,url).start();
   }else{
       Log.i(TAG,"从缓存中获取图片");
       imageView.setImageBitmap(bitmap);
   }
}

// 移出缓存
private void removeBitmapFromL(String key){
    imageLoader.removeBitmapFromLruCache(key);
}

次に、上記のメソッドを呼び出して画像を取得し、対応するイベントで次のようにキャッシュから削除します。

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.btnLoadLruCache:
            loadImage();
            break;
        case R.id.btnRemoveBitmapL:
            removeBitmapFromL("bitmap");
            break;
    }
}

以下は、実装を説明するためのログスクリーンショットです。
BitmapLruCache

ディスクキャッシュ

ディスクキャッシュとは、キャッシュオブジェクトをファイルシステムに書き込むことを指します。ディスクキャッシュを使用すると、メモリキャッシュが利用できない場合の読み込み時間を短縮できます。ディスクキャッシュからの画像の取得は、キャッシュからの取得よりも時間がかかります。処理。ディスクキャッシュはDiskLruCacheクラスを使用してディスクキャッシングを実装します。DiskLruCacheはGoogleによって正式に推奨されています。DiskLruCacheはAndroid SDKの一部ではありません。まず、DiskLruCacheソースリンクを
DiskLruCacheソースアドレスに貼り付けます

DiskLruCacheの作成

DiskLruCacheの構築メソッドはプライベートなので、DiskLruCacheの作成には使用できません。次のように、それ自体を作成するためのオープンメソッドが提供されます。

/**
 * 返回相应目录中的缓存,如果不存在则创建
 * @param directory 缓存目录
 * @param appVersion 表示应用的版本号,一般设为1
 * @param valueCount 每个Key所对应的Value的数量,一般设为1
 * @param maxSize 缓存大小
 * @throws IOException if reading or writing the cache directory fails
 */
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
        throws IOException {
    ...
    // 创建DiskLruCache
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
        ...
        return cache;
    }
    //如果缓存目录不存在,创建缓存目录以及DiskLruCache
    directory.mkdirs();
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    ...
    return cache;
}

:キャッシュディレクトリは、SDカードのキャッシュディレクトリ、および/ sdcard / Android / data /アプリケーションパッケージ名/キャッシュディレクトリを選択できます。もちろん、アプリケーションがアンインストールされている場合は、現在のアプリケーションデータの下のキャッシュディレクトリを選択することもできます。キャッシュファイルを削除する場合は、SDカードのキャッシュディレクトリを選択してください。データを保持する場合は、別のディレクトリを選択してください。メモリキャッシュがある場合は、アプリケーションを終了するとキャッシュがクリアされます。

DiskLruCacheキャッシュの追加

DiskLruCacheキャッシュの追加は、エディターを介して行われます。エディターは、キャッシュオブジェクトの編集されたオブジェクトを表します。編集(文字列キー)メソッドを介して、対応するエディターオブジェクトを取得できます。エディターが編集(文字列キー)メソッドを使用している場合は、nullを返します。つまり、DiskLruCacheは同じキャッシュオブジェクトを同時に操作できません。もちろん、キャッシュは一意のキーを介して追加されるため、画像を例にとると、キーとしてより便利です。一般的に言えば、URLのMD5値がキーとして使用され、計算方法は次のとおりです。

//计算url的MD5值作为key
private String hashKeyForDisk(String url) {
    String cacheKey;
    try {
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
        mDigest.update(url.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
    } catch (NoSuchAlgorithmException e) {
        cacheKey = String.valueOf(url.hashCode());
    }
    return cacheKey;
}

private String bytesToHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(0xFF & bytes[i]);
        if (hex.length() == 1) {
            sb.append('0');
        }
        sb.append(hex);
    }
    return sb.toString();
}

URLのMD5値を通じてキーを取得した後、DiskLruCacheオブジェクトのEdit(文字列キー)メソッドを介してEditorオブジェクトを取得し、次にEditorオブジェクトのcommitメソッドを介して取得できます。これは、大まかにEditirオブジェクトを解放することを意味し、その後、キーを使用して他の操作を実行できます。少し。

もちろん、キーを取得した後、キャッシュするものをDiskLruCacheに追加できます。ネットワークイメージをキャッシュにロードするには、キャッシュするものをダウンロードしてファイルシステムに書き込み、出力ストリームが必要です。そこに物事を書くには主に2つの方法があります:

  1. キャッシュされるデータを書き込むためのOutputStreamを作成し、DiskLruCacheのEdit(String key)メソッドを介してEditorオブジェクトを取得します。次に、それをOutputStreamを介してBirmapに変換し、BitmapをEditorオブジェクトによって作成されたOutputStreamに書き込み、最後にEditorオブジェクトのcommitメソッドを呼び出して送信します;
  2. 最初にEditorオブジェクトを取得し、Editorオブジェクトに基づいてOutputStreamを作成し、キャッシュするデータを直接書き込み、最後にEditorオブジェクトのcommitメソッドを呼び出して送信します。

ここで最初の方法を例にとると、次のように、ネットワークイメージがURLに従ってディスクキャッシュに追加され、メモリキャッシュにも追加されます。

//添加网络图片到内存缓存和磁盘缓存
public void putCache(final String url, final CallBack callBack){
    Log.i(TAG,"putCache...");
    new AsyncTask<String,Void,Bitmap>(){
        @Override
        protected Bitmap doInBackground(String... params) {
            String key = hashKeyForDisk(params[0]);
            DiskLruCache.Editor editor = null;
            Bitmap bitmap = null;
            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(1000 * 30);
                conn.setConnectTimeout(1000 * 30);
                ByteArrayOutputStream baos = null;
                if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){
                    BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
                    baos = new ByteArrayOutputStream();
                    byte[] bytes = new byte[1024];
                    int len = -1;
                    while((len=bis.read(bytes))!=-1){
                        baos.write(bytes,0,len);
                    }
                    bis.close();
                    baos.close();
                    conn.disconnect();
                }
                if (baos!=null){
                    bitmap = decodeSampledBitmapFromStream(baos.toByteArray(),300,200);
                    addBitmapToCache(params[0],bitmap);//添加到内存缓存
                    editor = diskLruCache.edit(key);
                    //关键
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));
                    editor.commit();//提交
                }
            } catch (IOException e) {
                try {
                    editor.abort();//放弃写入
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            callBack.response(bitmap);
        }
    }.execute(url);
}

DiskLruCacheキャッシュの取得

あなたがキーを取得する方法を理解したら、キーを取得し、getメソッドDiskLruCacheオブジェクトを介して、スナップショットオブジェクトを取得し、取得のInputStreamスナップショットオブジェクト、そして最後に、InputStreamを使用してビットマップはもちろん、あなたが使用することができ、DiskLruCacheキャッシュを追加取得することができます応じて記事中のビットマップのサンプリング方法を適切に調整し、キャッシュする前に圧縮してからキャッシュすることもできます。InputStreamを取得する方法は次のとおりです。

//获取磁盘缓存
public InputStream getDiskCache(String url) {
    Log.i(TAG,"getDiskCache...");
    String key = hashKeyForDisk(url);
    try {
        DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
        if (snapshot!=null){
            return snapshot.getInputStream(0);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

DiskLruCacheの主要部分は上記とほぼ同じです。以下は、LruCacheとDiskLruCacheの特定の使用法を示すために、単純な3レベルのキャッシュを実装しています。MainActivityコードは次のとおりです。

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "cache_test";
    public static String CACHE_DIR = "diskCache";  //缓存目录
    public static int CACHE_SIZE = 1024 * 1024 * 10; //缓存大小
    private ImageView imageView;
    private LruCache<String, String> lruCache;
    private LruCacheUtils cacheUtils;
    private String url = "http://img06.tooopen.com/images/20161012/tooopen_sy_181713275376.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        cacheUtils = LruCacheUtils.getInstance();
        //创建内存缓存和磁盘缓存
        cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        cacheUtils.flush();
    }

    @Override
    protected void onStop() {
        super.onStop();
        cacheUtils.close();
    }
    
    public void loadImage(View view){
        load(url,imageView);
    }
    
    public void removeLruCache(View view){
        Log.i(TAG, "移出内存缓存...");
        cacheUtils.removeLruCache(url);
    }
    
    public void removeDiskLruCache(View view){
        Log.i(TAG, "移出磁盘缓存...");
        cacheUtils.removeDiskLruCache(url);
    }
    
    private void load(String url, final ImageView imageView){
        //从内存中获取图片
        Bitmap bitmap = cacheUtils.getBitmapFromCache(url);
        if (bitmap == null){
            //从磁盘中获取图片
            InputStream is = cacheUtils.getDiskCache(url);
            if (is == null){
                //从网络上获取图片
                cacheUtils.putCache(url, new LruCacheUtils.CallBack<Bitmap>() {
                    @Override
                    public void response(Bitmap bitmap1) {
                        Log.i(TAG, "从网络中获取图片...");
                        Log.i(TAG, "正在从网络中下载图片...");
                        imageView.setImageBitmap(bitmap1);
                        Log.i(TAG, "从网络中获取图片成功...");
                    }
                });
            }else{
                Log.i(TAG, "从磁盘中获取图片...");
                bitmap = BitmapFactory.decodeStream(is);
                imageView.setImageBitmap(bitmap);
            }
        }else{
            Log.i(TAG, "从内存中获取图片...");
            imageView.setImageBitmap(bitmap);
        }
    }
}

次の図に示すように、レイアウトファイルは比較的単純で、コードは投稿されていません。

BitmapDiskLruCache

この記事では、LruCacheとDiskLruCacheの基本的な使用法を記録します。少なくとも、これら2つのキャッシュ補助クラスをある程度理解している必要があります。具体的な実装については、ソースコードを参照してください。
[記事のコード]:ポータル

公開番号に注意を払うことができます:jzman-blog、交換学習を一緒に。
に弓

おすすめ

転載: www.cnblogs.com/jzmanu/p/12688906.html