The original article was first published on wechat public number: Jzman-blog, welcome to follow and exchange!

Android cache use is common, with the corresponding caching strategy can reduce consumption of flow, also can to a certain extent, improve the performance of application, such as load network image, should not be loading images from the Internet, every time should be cached in memory and disk, the next directly from memory or disk access, LRU(Least Recently Used) algorithm is generally Used for cache policy. The following uses memory cache and disk cache as an example to introduce how to use cache in Android. Before reading this article, please read the previous article:

  • Bitmap Bitmap sampling and memory calculation details

Memory cache

LruCache is a cache class provided by Android 3.1. Through this class, you can quickly access the cached Bitmap objects. Inside, a LinkedHashMap is used to store the Bitmap objects to be cached in the way of strong reference. Frees memory occupied by recently little-used objects until the cache exceeds a specified size.

Note: Before Android 3.1, a common memory cache was a bitmap cache of SoftReference or WeakReference, which is no longer recommended. Android 3.1, the garbage collector pay more attention to recycling SoftWeakference/WeakReference, this makes the use of the method to realize the cache is largely ineffective, LruCache in the support-V4 package is compatible with Android versions prior to 3.1.

The use of the LruCache

  1. Initialize the LruCache

First, calculate the required cache size, as follows:

// The first way:

ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

// Get the approximate memory size of the application under the current hardware condition, in M

int memorySize = manager.getMemoryClass();//M

int cacheSize = memorySize/ 8;

// The second way (more common)

int memorySize = (int) Runtime.getRuntime().maxMemory();//bytes

int cacheSize = memorySize / 8;

Copy the code

LruCache is then initialized as follows:

// Initialize the LruCache and set the cache size

LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(cacheSize){

    @Override

    protected int sizeOf(String key, Bitmap value) {

        // Calculate the memory size of each cached Bitmap in the same unit as cacheSize

        return value.getByteCount();

    }

};

Copy the code
  1. Add a Bitmap object to the LruCache cache
// Put (String key, Bitmap Bitmap)

lruCache.put(key,bitmap)

Copy the code
  1. Get the image from the cache and display it
//参数get(String key)

Bitmap bitmap = lruCache.get(key);

imageView.setImageBitmap(bitmap);

Copy the code

To demonstrate the simple use of LruCache, load a network image using LruCache.

Loading network images

Create a simple ImageLoader that encapsulates the methods to get the cached Bitmap, add the Bitmap to the cache, and remove the Bitmap from the cache as follows:

//ImageLoader

public class ImageLoader {

    private LruCache<String , Bitmap> lruCache;

    public ImageLoader(a) {

        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) {

                // Calculate the memory size of each cached Bitmap

                return value.getByteCount()/1024;

            }

        };

    }



    / * *

* Add Bitmapd to LruCache

     * @param key

     * @param bitmap

* /


    public void addBitmapToLruCache(String key, Bitmap bitmap){

        if (getBitmapFromLruCache(key)==null) {

            lruCache.put(key,bitmap);

        }

    }



    / * *

* Get the cached Bitmap

     * @param key

* /


    public Bitmap getBitmapFromLruCache(String key){

        if(key! =null) {

            return lruCache.get(key);

        }

        return null;

    }



    / * *

* Remove the cache

     * @param key

* /


    public void removeBitmapFromLruCache(String key){

        if(key! =null) {

            lruCache.remove(key);

        }

    }

}

Copy the code

Then create a thread class to load the image as follows:

// Load the image thread

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(a) {

        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(a) {

                        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();

                }

            }

        }

    }

}

Copy the code

Then, MainActivity uses ImageLoader to load and cache the network image into memory. The image is first retrieved from memory. If there is no Bitmap in the cache, the image is retrieved from the network and added to the cache. The key methods are as follows:

// Get the image

private void loadImage(){

    Bitmap bitmap = imageLoader.getBitmapFromLruCache("bitmap");

   if (bitmap==null) {

       Log.i(TAG,"Get pictures from the network");

       new LoadImageThread(this,imageLoader,imageView,url).start();

   }else{

       Log.i(TAG,"Get image from cache");

       imageView.setImageBitmap(bitmap);

   }

}



// Remove the cache

private void removeBitmapFromL(String key){

    imageLoader.removeBitmapFromLruCache(key);

}

Copy the code

Then call the above method to get the image and remove 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;

    }

}

Copy the code

Here is a log screenshot to illustrate the execution:

BitmapLruCache

Disk cache

Disk caching refers to writing cached objects to the file system. Using disk caching can help shorten load times when memory caching is not available. Retrieving images from disk caching is slower than retrieving images from cache, and should be processed in background threads if possible. DiskLruCache is officially recommended by Google. DiskLruCache is not part of the Android SDK. First post a DiskLruCache source link DiskLruCache source address.

The creation of DiskLruCache

The constructor for DiskLruCache is private and therefore cannot be used to create DiskLruCache. It provides an open method to create itself as follows:

/ * *

* Returns the cache in the corresponding directory, or creates it if it does not exist

 * @paramDirectory Cache directory

 * @paramAppVersion Indicates the version number of the application. The value is usually 1

 * @paramValueCount Number of values for each Key. Generally, the Value is set to 1

 * @paramMaxSize Cache size

 * @throws IOException if reading or writing the cache directory fails

* /


public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

        throws IOException 
{

.

    / / create DiskLruCache

    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);

    if (cache.journalFile.exists()) {

.

        return cache;

    }

    // Create a cache directory and DiskLruCache if the cache directory does not exist

    directory.mkdirs();

    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);

.

    return cache;

}

Copy the code

Note: You can select the cache directory on the SD card, /sdcard/Android/data/ application package name /cache, or select the cache directory under the current application data. Of course, you can specify other directories. If you want to delete the cache files after the application is uninstalled, Select the cache directory on the SD card. If you want to keep the data, select another directory. Also, if it is memory cache, the cache will be cleared when you exit the application.

DiskLruCache cache is added

The DiskLruCache cache is added through the Editor, which represents an edit object of a cache object and can be obtained by its Edit (String key) method. If the Editor is using the Edit (String key) method, null is returned, meaning that DiskLruCache is not allowed to operate on the same cache object at the same time. Of course, only a unique key is used to add the cache, so what is the convenient 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:

// Calculates the MD5 value of the URL as the 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();

}

Copy the code

After obtaining the key from the MD5 value of the URL, you can obtain the Editor object by using the Edit (String key) method of the DiskLruCache object, and then by using the Commit method of the Editor object, Release the Editir object, and then you can do something else with the key.

To load a network image into the cache, you need an output stream to write to the file system. There are two main ways to do this:

  1. Create an OutputStream to write the data to cache, get the Editor object from DiskLruCache’s Edit (String key) method, and convert it to Birmap via OutputStream. The Bitmap is written to an OutputStream created by the Editor object, and finally committed by calling the Editor object’s commit method.
  2. First, obtain the Editor object, create OutputStream according to the Editor object and write the data to cache directly. Finally, call the Commit method of the Editor object to commit.

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

// Add network images to memory cache and disk cache

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);// Add to memory cache

                    editor = diskLruCache.edit(key);

                    / / key

                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, editor.newOutputStream(0));

                    editor.commit();/ / submit

                }

            } catch (IOException e) {

                try {

                    editor.abort();// Abort the write

                } catch (IOException e1) {

                    e1.printStackTrace();

                }

            }

            return bitmap;

        }



        @Override

        protected void onPostExecute(Bitmap bitmap) {

            super.onPostExecute(bitmap);

            callBack.response(bitmap);

        }

    }.execute(url);

}

Copy the code

DiskLruCache Cache is obtained

After obtaining the key, obtain the Snapshot object through the get method of the DiskLruCache object, and then obtain the InputStream according to the Snapshot object. Finally, Bitmap can be obtained through InputStream. Of course, the method of sampling Bitmap in the previous article can be adjusted appropriately, or it can be compressed and cached before caching. The method of obtaining InputStream is as follows:

// Get the disk cache

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;

}

Copy the code

The main part of DiskLruCache is roughly as above, the following implementation of a simple three-level cache to illustrate the specific use of LruCache and DiskLruCache, 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";  // Cache directory

    public static int CACHE_SIZE = 1024 * 1024 * 10// Cache size

    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(a) {

        super.onResume();

        cacheUtils = LruCacheUtils.getInstance();

        // Create memory cache and disk cache

        cacheUtils.createCache(this,CACHE_DIR,CACHE_SIZE);

    }



    @Override

    protected void onPause(a) {

        super.onPause();

        cacheUtils.flush();

    }



    @Override

    protected void onStop(a) {

        super.onStop();

        cacheUtils.close();

    }



    public void loadImage(View view){

        load(url,imageView);

    }



    public void removeLruCache(View view){

        Log.i(TAG, "Remove memory cache...");

        cacheUtils.removeLruCache(url);

    }



    public void removeDiskLruCache(View view){

        Log.i(TAG, "Remove disk cache...");

        cacheUtils.removeDiskLruCache(url);

    }



    private void load(String url, final ImageView imageView){

        // Get the image from memory

        Bitmap bitmap = cacheUtils.getBitmapFromCache(url);

        if (bitmap == null) {

            // Get the image from disk

            InputStream is = cacheUtils.getDiskCache(url);

            if (is == null) {

                // Get the image from the network

                cacheUtils.putCache(url, new LruCacheUtils.CallBack<Bitmap>() {

                    @Override

                    public void response(Bitmap bitmap1) {

                        Log.i(TAG, "Get pictures from the network...");

                        Log.i(TAG, "Downloading images from the network...");

                        imageView.setImageBitmap(bitmap1);

                        Log.i(TAG, "Obtaining image from network succeeded...");

                    }

                });

            }else{

                Log.i(TAG, "Get pictures from disk...");

                bitmap = BitmapFactory.decodeStream(is);

                imageView.setImageBitmap(bitmap);

            }

        }else{

            Log.i(TAG, "Get pictures from memory...");

            imageView.setImageBitmap(bitmap);

        }

    }

}

Copy the code

The layout file is relatively simple, so I will not paste the code. Here is a screenshot of the log run to illustrate the execution, as shown below:

BitmapDiskLruCache

This article records the basic use of LruCache and DiskLruCache, at least should have a certain understanding of these two cache helper classes, its specific implementation please refer to the source code. [text code] : Portal

You can follow the official account: Jzman-blog to communicate and learn together.

jzman-blog.