LruCache快取使用
1.為什麼要快取?
在android專案中經常需要請求網路圖片,這個時候如果每張網路圖片顯示都要去獲取一次的話,是非常耗費效能的。從網路中成功獲取一張圖片需要發起請求、等待響應、接收資料、解析資料、在做顯示。這一系列操作我們都需要放到子執行緒中去執行,確認程式不會出現ANR問題。
對於快取來說,無法就是新增快取、刪除快取、獲取快取這三種操作。先判斷是否有快取,沒有就進行快取操作,當快取滿了之後我們就可以把最久沒使用的快取給清除掉,已達到不超出快取邊界的問題。
2.二種快取方式
為了應對這種效能問題,我們有可以選擇對圖片進行快取,在android中主要有二種方式來快取,一種是記憶體快取、磁碟快取。記憶體快取是最快的因為它是直接從記憶體中讀取資料,磁碟快取稍慢因為它要做IO流操作,但是它可以持久化資料,各有各的好處,所以我們可以結合情況二種方式都用上。
3.LruCache
結合以上出現的問題android在3.1的時候推出了一個LRU(Least Recently Used)最近期最少使用演算法 ,它的核心思想就是,優先清除那些最近最少使用的物件。LRU體現有二種一個是記憶體中的LruCache和一個針對磁碟的DisLruCache。它們的核心都是LRU思想。
4.使用
這裡初始化了一個LruCache類,建構函式我們傳入了一個Int型別的 cacheSize引數,這個引數代表快取的最大邊界是當前顯示的1/8的容量。
sizeOf
這個方法是計算每個快取物件大小的,內部通過這個方法疊加大小看是否超過了,我們初始化建構函式中的大小。
private LruCache<String, Bitmap> mCache; private Bitmap bitmap; private LruCache<String, Bitmap> mCache; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_glide); img = findViewById(R.id.iamge); initLRUCache(); findViewById(R.id.but).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = getCacheImage("bitmap"); if (bitmap == null) { requestImage(); } else { Log.i("jinwei", "獲取快取"); img.setImageBitmap(bitmap); } } }); private void initLRUCache() { long maxMemory = Runtime.getRuntime().maxMemory(); int cacheSize = (int) (maxMemory / 8); mCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; } android.os.Handler handler = new android.os.Handler() { @Override public void dispatchMessage(Message msg) { super.dispatchMessage(msg); img.setImageBitmap(bitmap); } }; private void httpUrlConnectionImage() throws MalformedURLException { Log.i("jinwei", "request Image"); URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1535715922665&di=b706c8b7a4f907a07b1f5062073a4e2a&imgtype=0&src=http%3A%2F%2Fwww.baimg.com%2Fuploadfile%2F2015%2F0413%2FBTW_2015041347455.jpg"); try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setReadTimeout(5000); connection.setConnectTimeout(5000); InputStream inputStream = connection.getInputStream(); bitmap = BitmapFactory.decodeStream(inputStream); addCache("bitmap", bitmap); handler.sendEmptyMessage(2); } catch (IOException e) { e.printStackTrace(); } } private void initLRUCache() { long maxMemory = Runtime.getRuntime().maxMemory(); int cacheSize = (int) (maxMemory / 8); mCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; } /** * 快取取bitmap * * @param key key * @return 快取bm */ private Bitmap getCacheImage(String key) { Bitmap mb = mCache.get(key); return mb; } /** * 移除快取 * * @param key */ private void removeCache(String key) { mCache.remove(key); } /** * 新增一個快取 * * @param key * @param bitmap */ private void addCache(String key, Bitmap bitmap) { if (getCacheImage(key) == null) { mCache.put(key, bitmap); } } private void requestImage() { new Thread(new Runnable() { @Override public void run() { try { httpUrlConnectionImage(); } catch (MalformedURLException e) { e.printStackTrace(); } } }).start(); }
原理分析
可以看到構造裡面做的操作,初始化可一個LinkedHashMap,所有LRU的核心就是通過hasmap來儲存快取物件的,maxSize儲存了傳入預設的大小值。
private final LinkedHashMap<K, V> 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; public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); }
put方法
首先判斷了key-value是否為null,為Null會丟擲一個異常,所以LRU不支援key-value為null值。Lru的put方法是執行緒安全的,safeSizeOf就是會呼叫我們之前實現的sizeOf方法。
/** * 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) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; } private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; }
trimToSize
註釋說明的檢查最近最少使用的的物件進行清除,當然是要快取超出邊界了才會進行清除。size <= maxSize這個判斷可以看出來。這個方法在put方法中呼叫,意思就是每次Put的時候都會檢查當然的快取容量,如果超出就會清除最近最少使用的物件。
/** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 *to evict even 0-sized elements. */ 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!"); } if (size <= maxSize) { break; } Map.Entry<K, V> toEvict = map.eldest(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
get
這個方法比較容易理解,mapValue = map.get(key);如果mapValue不為NUll就直接返回我們快取的物件。
/** * 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. */ 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); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
remove
remove直接會key 對應的物件清除掉,響應快取容量也會減少。
/** * 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; }