Detailed explanation of memory cache and disk cache of bitmap

Caching is widely used in Android. Using the corresponding caching strategy can reduce the consumption of traffic and improve the performance of the application to a certain extent. For example, when loading network pictures, you should not load pictures from the network every time. Instead, you should cache them to memory and disk, and get them directly from memory or disk next time. The caching strategy generally uses LRU (least recently used) algorithm, that is, the least recently used algorithm. The following will introduce how to use cache in Android from two aspects: memory cache and disk cache. Please read the previous article before reading this article:

Lrucache is a cache class provided by Android 3.1. Through this class, you can quickly access the cached bitmap objects. Internally, a LinkedHashMap is used to store the bitmap objects to be cached in a strong reference way. When the cache exceeds the specified size, the memory occupied by the recently rarely used objects is released.

Note: before Android 3.1, a common memory cache was a bitmap cache of softreference or WeakReference, which is not recommended now. After Android 3.1, the garbage collector pays more attention to recycling softweakreference / WeakReference, which makes the cache implemented in this way largely invalid. The lrucache in the support-v4 compatibility package can be compatible with versions before Android 3.1.

First, calculate the required cache size as follows:

//第一种方式:
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;

Then, initialize lrucache as follows:

//初始化 LruCache 且设置了缓存大小
LruCache<String,Bitmap> lruCache = new LruCache<String,Bitmap>(cacheSize){
    @Override
    protected int sizeOf(String key,Bitmap value) {
        //计算每一个缓存Bitmap的所占内存的大小,内存单位应该和 cacheSize 的单位保持一致
        return value.getByteCount();
    }
};

//参数put(String key,Bitmap bitmap)
lruCache.put(key,bitmap)

//参数get(String key)
Bitmap bitmap = lruCache.get(key);
imageView.setImageBitmap(bitmap);

Next, use lrucache to load a network picture to demonstrate the simple use of lrucache.

Create a simple imageloader, which encapsulates the methods of obtaining cache bitmap, adding bitmap to cache and removing bitmap from cache, as follows:

//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);
        }
    }
}

Then create a thread class to load pictures, as follows:

//加载图片的线程
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();
                }
            }
        }
    }
}

Then, in mainactivity, use imageloader to load and cache network pictures into memory. First get them from memory. If there is no required bitmap in the cache, get the pictures from the network and add them to the cache. Once the application is exited during use, the system will release memory. The key methods are as follows:

//获取图片
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);
}

Then call the above method of obtaining pictures and moving out of the cache in the corresponding event, as follows:

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

The following is a log screenshot to illustrate the implementation:

Disk cache refers to writing cache objects to the file system. Using disk cache can help shorten the loading time when the memory cache is unavailable. Obtaining pictures from disk cache is slower than from cache. If you can, you should process them in the background thread; Disk cache uses a disklrucache class to implement disk cache. Disklrucache has received the official recommendation of Google. Disklrucache is not part of the Android SDK. First, post a source code link of disklrucache and the source code address of disklrucache.

The construction method of disklrucache is private, so it cannot be used to create disklrucache. It provides an open method to create itself. The methods are as follows:

/**
 * 返回相应目录中的缓存,如果不存在则创建
 * @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,maxSize);
    ...
    return cache;
}

Note: you can select the cache directory on the SD card and / sdcard / Android / data / application package name / cache directory, or the cache directory under the current application data. Of course, you can specify another directory. If you want to delete the cache file after the application is uninstalled, you can select the cache directory on the SD card. If you want to retain the data, you can select another directory, Also, if it is a memory cache, the cache will be cleared after exiting the application.

The addition of disklrucache cache is completed through editor. Editor represents the editing object of a cache object, and the corresponding editor object can be obtained through its edit (string key) method. If the editor is using the edit (string key) method, null will be returned, that is, disklrucache does not allow the same cache object to be operated at the same time. Of course, the cache is added through a unique key. Is it convenient to use what as a key? Take the picture as an example. Generally speaking, the MD5 value of the URL is used as the key. The calculation method is as follows:

//计算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();
}

After obtaining the key through the MD5 value of the URL, you can obtain the editor object through the edit (string key) method of the disklrucache object, and then through the commit method of the editor object, which roughly means to release the editor object, and then you can perform other operations through the key.

Of course, after obtaining the key, you can add things to be cached to disklrucache. To load a network image into the cache, it is obvious that the things to be cached are written to the file system by downloading, so you need an output stream to write things to it. There are two main processing methods:

Taking the first method as an example, the network image will be added to the disk cache according to the URL and also to the memory cache, as follows:

//添加网络图片到内存缓存和磁盘缓存
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,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);
}

In the addition of disklrucache cache, we learned how to obtain the key. After obtaining the key, we can obtain the snapshot object through the get method of disklrucache object, then obtain the InputStream according to the snapshot object, and finally obtain the bitmap through the InputStream. Of course, we can make appropriate adjustments by using the sampling method of bitmap in the previous article, You can also compress and cache before caching. The method to obtain InputStream is as follows:

//获取磁盘缓存
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;
}

The main parts of disklrucache are as follows. A simple three-level cache is implemented to illustrate the specific use of lrucache and disklrucache. The mainactivity code is as follows:

//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);
        }
    }
}

The layout file is relatively simple and no code is pasted. The following is a screenshot of log operation to illustrate the implementation, as shown in the following figure:

This article records the basic usage of lrucache and disklrucache. You should at least have a certain understanding of these two cache auxiliary classes. Please refer to the source code for its specific implementation. [text code]: Portal

We can pay attention to the official account number: jzman-blog.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>