Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 緩存類LruCache源碼分析

Android 緩存類LruCache源碼分析

編輯:關於Android編程

1.引言

Android開發難免會遇到加載網絡圖片問題,而加載網絡圖片難免會遇到多次重復請求加載同一張圖片問題,這麼一來就導致多次加載網絡,不僅浪費資源而且給用戶體驗感覺加載圖片慢。從Android3.1開始,API中多了一個LruCache類,該類是一個使用最近最少使用算法的緩存類。也就是可以把上次下載的圖片以鍵值對的方式保存在緩存中,以便下載加載同一張圖片時無須再次從網絡上下載,而且加載速度快。

2.LruCache源碼詳解

這裡我把LruCache源碼貼出來,幾乎每行代碼的注釋都有,很詳細,有意者可以仔細跟著注釋閱讀源碼,以便理解LruCache類的原理。

/**
1.從類名LruCache就知道,該類的作用是一個最近最少使用算法來維護的一個緩存類。
2.該類是一個用於緩存一定數量的值,並且該緩存對象是持有強引用。
3.每當成功get一次值時,該值都會移動到鏈表的頭部,以便標記該值為最近最新的值。
4.當緩存滿了時,鏈表尾部的值會被認為是最近最少使用的值,會被從鏈表中移除,以便緩存有空間保存新的值。
5.緩存中鏈表的值發生改變時會調用空方法entryRemoved,開發者可以重寫該方法,以便做相應的操作。
6.開發者應該去重寫該類中sizeOf方法,該方法返回每個key對應value值得大小,以便該類去維護緩存的大小。
7.該類是一個安全類。同時該類不允許key和value為空。該類在Android3.1之後添加到源碼中。
**/
package android.util;

import java.util.LinkedHashMap;
import java.util.Map;


public class LruCache {
    private final LinkedHashMap map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;//已使用緩存大小
    private int maxSize;//總的緩存大小

    private int putCount;//添加記錄次數
    private int createCount;//創建次數
    private int evictionCount;//移除次數
    private int hitCount;//命中次數
    private int missCount;//未命中次數

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap(0, 0.75f, true);//創建鏈表緩存,該鏈表緩存是整個LruCache類的重點。
    }

    /**
     * Sets the size of the cache.
     * @param maxSize The new maximum size.
     *重新設置緩存大小
     * @hide
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     * 根據key值獲得緩存中對應的數據,該方法是線程安全的。
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {//加鎖線程安全
            mapValue = map.get(key);//從鏈表中去取緩存數據
            if (mapValue != null) {//獲取數據成功
                hitCount++;//標記命中的次數
                return mapValue;//返回命中的數據,命中之後直接返回
            }
            missCount++;//標記未命中的次數
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */
        //未命中走以下流程
        V createdValue = create(key);//創建新的數據,默認返回null,創建的時候可以重寫該方法,以便未命中的時候創建一個新的數據添加到緩存鏈表中。
        if (createdValue == null) {
            return null;//未命中時默認到這裡結束。
        }

        //以下代碼是在未命中時創建新數據添加到鏈表緩存中。
        synchronized (this) {
            createCount++;//標記創建新數據的次數
            mapValue = map.put(key, createdValue);
            //如果鏈表中已有該key對應的值,則最後取消添加新創建的值。很多人可能感到奇怪,此時鏈表中應該不存在key對應的值啊?其實這裡是為了防止多線程操作導致數據不同步而添加的安全代碼。不懂得自己體會去吧!
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                //標記已經使用了的內存大小。
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //鏈表中發生數據變化時(調用了put,get方法)調用該方法。該方法默認是個空,開發者可以重寫該方法在鏈表中數據變化時做相應的操作。
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //重新整理當前鏈表維護的內存。
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     * 添加新的鍵值對到鏈表中
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        //同步操作
        synchronized (this) {
            putCount++;//標記添加數據的次數
            size += safeSizeOf(key, value);//標記已使用的內存大小
            previous = map.put(key, value);//保存數據到鏈表
            if (previous != null) {//鏈表中已存在key對應的值則替換原來的值
                size -= safeSizeOf(key, previous);//標記已使用的內存大小
            }
        }

        if (previous != null) {
            //鏈表中數據發生交換時調用該方法。
            entryRemoved(false, key, previous, value);
        }
        //整理鏈表維護的內存大小
        trimToSize(maxSize);
        return previous;
    }

    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     * 管理鏈表中內存的大小
     */
    private 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!");
                }
                //當已經使用了的內存小於申請的最大內存,則說明鏈表未滿,還可以添加新的數據。
                if (size <= maxSize) {
                    break;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry toEvict = null;
                //for循環得到鏈表末尾的數據,然後移除它。
                for (Map.Entry entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }
                //移除鏈表末尾的一個數據,該數據就是最近最少使用到的數據。
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除數據
                size -= safeSizeOf(key, value);//重寫計算已使用的內存大小
                evictionCount++;//標記移除數據的次數
            }
            //開發者可以重新改方法,當移除數據時做相應的操作。
            entryRemoved(true, key, value, null);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     * 從鏈表中移除數據的方法
     */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     *

The method is called without synchronization: other threads may * access the cache while this method is executing. * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */ //該方法默認是一個空方法。當鏈表中發生數據變化時調用該方法,所以開發者可以重寫該方法,以便做相應的操作。 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * *

The method is called without synchronization: other threads may * access the cache while this method is executing. * *

If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */ //該方法默認返回空,開發者可以重新該方法以便在get鏈表中的數據失敗時創建新的數據添加到鏈表中。 protected V create(K key) { return null; } //該方法是重寫計算鏈表中已經使用了的內存大小 private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } /** * Returns the size of the entry for {@code key} and {@code value} in * user-defined units. The default implementation returns 1 so that size * is the number of entries and max size is the maximum number of entries. * *

An entry's size must not change while it is in the cache. */ //該方法默認返回1,開發者必須重寫該方法,用來計算每個key對應value值得大小,以便維護鏈表中緩存的大小。 protected int sizeOf(K key, V value) { return 1; } /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. */ //移除鏈表中所有的數據 public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. * 得到已經使用緩存中內存的大小 */ public synchronized final int size() { return size; } /** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. * 得到分配給鏈表緩存的大小 */ public synchronized final int maxSize() { return maxSize; } /** * Returns the number of times {@link #get} returned a value that was * already present in the cache. * 得到命中的次數 */ public synchronized final int hitCount() { return hitCount; } /** * Returns the number of times {@link #get} returned null or required a new * value to be created. * 得到未命中的次數 */ public synchronized final int missCount() { return missCount; } /** * Returns the number of times {@link #create(Object)} returned a value. * 得到創建新數據的次數 */ public synchronized final int createCount() { return createCount; } /** * Returns the number of times {@link #put} was called. * 得到添加新數據次數 */ public synchronized final int putCount() { return putCount; } /** * Returns the number of values that have been evicted. * 得到移除數據的次數 */ public synchronized final int evictionCount() { return evictionCount; } /** * Returns a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. * 得到鏈表對象 */ public synchronized final Map snapshot() { return new LinkedHashMap(map); } @Override public synchronized final String toString() { int accesses = hitCount + missCount; int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount, hitPercent); } }

LruCache圖片使用示例

一下代碼是圖片緩存類ImageCache示例代碼:

import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * Created by xjp on 2016/4/2.
 */
public class ImageCache {
    private LruCache mLruCache;
    private static ImageCache instance = new ImageCache();

    public ImageCache() {
        //得到當前應用總的內存大小
        int appTotalCache = (int) Runtime.getRuntime().totalMemory();
        //取當前應用內存的1/8作為緩存大小
        int maxSize = appTotalCache / 8;
        mLruCache = new LruCache(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //此處返回每個Bitmap對象的大小。值得注意:此處返回值並不是圖片的張數,
                // 且返回值的單位應該和maxSize的單位一樣。也就是maxSize單位是B,那麼此處返回值單位也是B
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                //如果緩存中的值被移除,則去回收Bitmap內存。
                if (evicted) {
                    if (oldValue != null && !oldValue.isRecycled()) {
                        oldValue.recycle();
                    }
                }
            }
        };
    }

    public static ImageCache getInstance() {
        return instance;
    }

    //從緩存中讀取數據
    public Bitmap getBitmapFromCache(String key) {
        if (mLruCache != null) {
            mLruCache.get(key);
        }
        return null;
    }

    //保存數據到緩存中
    public void putBitmapToCache(String key, Bitmap value) {
        if (mLruCache != null) {
            if (mLruCache.get(key) == null) {
                mLruCache.put(key, value);
            }
        }
    }

    //清除緩存中所有數據
    public void clearCache() {
        if (mLruCache != null) {
            mLruCache.evictAll();
        }
    }
}

總結:以後就可以直接用ImageCache類來作為圖片緩存了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved