1. What is LruCache?

Before you can figure out what LruCache is, you need to know Android’s caching strategy. In fact, the cache strategy is very simple. For example, when the user loads an image from the network for the first time, the next time the image is loaded, it will not be loaded from the network, but from the memory or hard disk.

Cache policies are add, get, and remove. Why remove the cache? Each device has a certain capacity limit and needs to be deleted when the capacity is full.

So what is a LruCache? In fact, LRU(Least Recently Used) means the Least Recently Used algorithm, its core idea is to prioritize the Least Recently Used cache objects.

2. How to use LruCache?

Now load an image from the web using okhttp:

Create a new ImageLoader class:

public class ImageLoader {

    private LruCache<String, Bitmap> lruCache;

    public ImageLoader() {
        int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        lruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                returnbitmap.getRowBytes() * bitmap.getHeight() / 1024; }}; } public void addBitmap(String key, Bitmap bitmap) {if (getBitmap(key) == null) {
            lruCache.put(key, bitmap);
        }
    }

    public Bitmap getBitmap(String key) {
        returnlruCache.get(key); }}Copy the code

Overwrite sizeOf() to calculate the sizeOf an element’s cache. When the total cacheSize of stored elements is greater than cacheSize, LruCache removes the least recently used element.

MainActivity:

public class MainActivity extends AppCompatActivity {

    private String Path = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1532852517262&di=bcbc367241183c39d6e6c9ea2f879166&i mgtype=0&src=http%3A%2F%2Fimg4q.duitang.com%2Fuploads%2Fitem%2F201409%2F07%2F20140907002919_eCXPM.jpeg";

    private Button btn;

    private ImageView imageView;

    private ImageLoader imageLoader;

    private static final int SUCCESS = 1;
    private static final int FAIL = 2;

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SUCCESS:
                    byte[] Picture = (byte[]) msg.obj;
                    Bitmap bitmap = BitmapFactory.decodeByteArray(Picture, 0, Picture.length);
                    imageLoader.addBitmap("bitmap", bitmap);
                    imageView.setImageBitmap(bitmap);

                    break;
                case FAIL:
                    break; }}}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);

        btn = findViewById(R.id.btn);
        imageView = findViewById(R.id.imageview);
        imageLoader = new ImageLoader();
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap bitmap = getBitmapFromCache();
                if(bitmap ! = null) { imageView.setImageBitmap(bitmap); }else{ getBitmapFromInternet(); }}}); } private BitmapgetBitmapFromCache() {
        Log.e("chan"."===============getBitmapFromCache");
        return imageLoader.getBitmap("bitmap");
    }

    private void getBitmapFromInternet() {
        Log.e("chan"."===============getBitmapFromInternet");
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(Path)
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { byte[] Picture_bt = response.body().bytes(); Message message = handler.obtainMessage(); message.obj = Picture_bt; message.what = SUCCESS; handler.sendMessage(message); }}); }}Copy the code

This method is very simple, using okHTTP to load an image from the network. If the image does not exist in the cache before the network loads, it will be loaded directly from the cache.

3. The principle of LruCache

LruCache actually uses the LinkedHashMap bidirectional list structure, now analyze the LinkedHashMap using method.

Construction method:

public LinkedHashMap(int initialCapacity,
    loat loadFactor,
    boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
Copy the code

When accessOrder is true, the order of the elements in the collection will be the accessOrder, which means that the elements will be placed at the end of the collection.

LinkedHashMap < Integer, Integer > map = new LinkedHashMap < > (0, 0.75f, true);
map.put(0, 0);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.get(1);
map.get(2);

for (Map.Entry < Integer, Integer > entry: map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());

}
Copy the code

Print result:

In the 0-0 tie 1:1 2:2Copy the code

The following analyzes the PUT and GET methods of LruCache respectively.

3.1 Put Method Analysis:

Now explain the principle of this method as a comment.

Public final V PUT (K key, V value) {// If key or value is null, an exception is thrownif (key == null || value == null) {
        throw new NullPointerException("key == null || value == null"); } V previous; Synchronized (this) {// Add the number of elements to putCount() using putCount++; Size += safeSizeOf(key, value); sizeOf(K key, V value); // Return null previous = map.put(key, value); // Return null previous = map.put(key, value);if(previous ! = null) {// safeSizeOf() returns 1 size -= safeSizeOf(key, previous); }}if(previous ! = null) {// This method defaults to entryRemoved(false, key, previous, value);
    }

    trimToSize(maxSize);

    return previous;
}

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized(this) {
            if(size < 0 || (map.isEmpty() && size ! = 0)) { throw new IllegalStateException(getClass().getName() +".sizeOf() is reporting inconsistent results!"); } // Stop the loop until the cache size is less than or equal to the maximum cache size, maxSizeif (size <= maxSize) {
                break; } // Get the map element map.entry < K, V > toEvict = map.eldest();if (toEvict == null) {
                break; } key = toEvict.getKey(); value = toEvict.getValue(); // Remove the element map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null);
    }
}

public Map.Entry<K, V> eldest() {
    return head;
}
Copy the code

The put() method focuses on the trimToSize() method, which determines whether the maximum number of elements in the cache has been exceeded, and if so, removes the least used elements.

3.2 GET method analysis

public final V get(K key) {
    if (key == null) {
        throw new NullPointerException("key == null"); } V mapValue; Synchronized (this) {// obtain Value mapValue = map.get(key);if(mapValue ! = null) { hitCount++;returnmapValue; } missCount++; }... } // LinkedHashMap get method public V get(Object key) {Node < K, V > e;if ((e = getNode(hash(key), key)) == null) return null;
    if (accessOrder) afterNodeAccess(e);
    return e.value;
}

static class Node < K, V > implements Map.Entry < K, V > {
    final int hash;
    final K key;
    V value;
    Node < K, V > next;

    Node(int hash, K key, V value, Node < K, V > next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey() {
        return key;
    }
    public final V getValue() {
        return value;
    }
    public final String toString() {
        return key + "=" + value;
    }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this) return true;
        if (o instanceof Map.Entry) {
            Map.Entry <? , ?> e = (Map.Entry <? , ?> ) o;
            if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true;
        }
        return false;
    }
}

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next); }}Copy the code

AfterNodeAccess () = afterNodeAccess();

3.2.1 afterNodeAccess()

Void afterNodeAccess(Node < K, V > e) {linkedhashmap.entry < K, V > last;if(accessOrder && (last = tail) ! = e) {// convert e to linkedhashmap. Entry // b is the node before this node // A is the node after this node linkedhashmap. Entry < K, V > p = (LinkedHashMap.Entry < K, V > ) e, b = p.before, a = p.after; // set the node after this node to null. // if b is null, this node is the first node, and the node after it is the first nodeif(b == null) head = a; // If not, move a forward one bitelseb.after = a; // If a is not null, change the element of node A to bif(a ! = null) a.before = b;else last = b;
        if (last == null) head = p;
        else{ p.before = last; last.after = p; } tail = p; ++modCount; }}Copy the code

Linked list possibilities:

So let’s do case one.

3.1.2.1 Situation 1:

1. p.after = null;

Here is:

2. b.after = a; a.before = b;

Here is:

3. p.before = last; last.after = p;

Here is:

Case one is basically the same as the other cases, so I won’t go into it here.

4. To summarize

  • LruCache actually maintains a strong reference object using a LinkedHashMap
  • The total cache size is typically 1/8 of the available memory, and when the total cache size is exceeded the least-used element is removed, which is the header element of the internal LinkedHashMap
  • When an element is accessed using get(), it is moved to the end of the LinkedHashMap