LruCache是android提供的一個緩存工具類,其算法是最近最少使用算法。它把最近使用的對象用「強引用」存儲在LinkedHashMap中,而且把最近最少使用的對象在緩存值達到預設定值以前就從內存中移除。其在API12被引進,低版本能夠用support包中的類。html
1、分析源碼java
這個源碼是從網上找的,本身懶得去找源碼了。android
具體分析也是來自網絡:http://www.open-open.com/lib/view/open1385474073171.html算法
package com.kale.lrucachetest; import java.util.LinkedHashMap; import java.util.Map; /** * 它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,而且把最近最少使用的對象在緩存值達到預設定值以前從內存中移除。 */ /** * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection. * Cache保存一個強引用來限制內容數量,每當Item被訪問的時候,此Item就會移動到隊列的頭部。 * 當cache已滿的時候加入新的item時,在隊列尾部的item會被回收。 * <p> * If your cached values hold resources that need to be explicitly released, * override {@link #entryRemoved}. 若是你cache的某個值須要明確釋放,重寫entryRemoved() * <p> * If a cache miss should be computed on demand for the corresponding keys, * override {@link #create}. This simplifies the calling code, allowing it to * assume a value will always be returned, even when there's a cache miss. * 若是key相對應的item丟掉啦,重寫create().這簡化了調用代碼,即便丟失了也總會返回。 * <p> * By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * is limited to 4MiB of bitmaps: 默認cache大小是測量的item的數量,重寫sizeof計算不一樣item的 大小。 * * <pre> * {@code * int cacheSize = 4 * 1024 * 1024; // 4MiB * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { * protected int sizeOf(String key, Bitmap value) { * return value.getByteCount(); * } * }} * </pre> * * <p> * This class is thread-safe. Perform multiple cache operations atomically by * synchronizing on the cache: * * <pre> * {@code * synchronized (cache) { * if (cache.get(key) == null) { * cache.put(key, value); * } * }} * </pre> * * <p> * This class does not allow null to be used as a key or value. A return value * of null from {@link #get}, {@link #put} or {@link #remove} is unambiguous: * the key was not in the cache. 不容許key或者value爲null * 當get(),put(),remove()返回值爲null時,key相應的項不在cache中 */ public class LruCache<K, V> { 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; // put的次數 private int createCount; // create的次數 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<K, V>(0, 0.75f, true); } /** * 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返回相應的item,或者建立返回相應的item。相應的item會移動到隊列的頭部, * 若是item的value沒有被cache或者不能被建立,則返回null。 */ 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. 若是丟失了就試圖建立一個item */ 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 // 若是前面存在oldValue,那麼撤銷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; } } /** * 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) { // 返回的先前的value值 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. 清空cache空間 */ 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; } 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); } } /** * Removes the entry for {@code key} if it exists. 刪除key相應的cache項,返回相應的value * * @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. * 當item被回收或者刪掉時調用。該方法當value被回收釋放存儲空間時被remove調用, 或者替換item值時put調用,默認實現什麼都沒作。 * <p> * 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}. * true---爲釋放空間被刪除;false---put或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. 當某Item丟失時會調用到,返回計算的相應的value或者null * <p> * The method is called without synchronization: other threads may access * the cache while this method is executing. * * <p> * 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. */ 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. * 返回用戶定義的item的大小,默認返回1表明item的數量,最大size就是最大item值 * <p> * An entry's size must not change while it is in the cache. */ protected int sizeOf(K key, V value) { return 1; } /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. * 清空cacke */ 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. 返回當前cache的副本,從最近最少訪問到最多訪問 */ public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(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); } }
源碼挺長的,內部註釋也已經十分詳盡了,因此有空能夠看看。爲了方便說明,下面說下個人分析:express
/** * 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. * 返回用戶定義的item的大小,默認返回1表明item的數量,最大size就是最大item值 * <p> * An entry's size must not change while it is in the cache. */ protected int sizeOf(K key, V value) { return 1; }
/** * 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. * 當item被回收或者刪掉時調用。該方法當value被回收釋放存儲空間時被remove調用, 或者替換item值時put調用,默認實現什麼都沒作。 * <p> * 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}. * true---爲釋放空間被刪除;false---put或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) { }
經過判斷這裏面的evicted值就知道當前進行的是什麼操做,你能夠在這裏進行你想要的操做。apache
2、初始化LruCache緩存
2.1 定義cache大小網絡
初始化這個cache前須要設定這個cache的大小,這裏的大小官方推薦是用當前app可用內存的八分之一,固然你能夠視狀況而定。經過:數據結構
final int memClass = ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
就能夠獲得當前app可用的內存。app
爲了全局調用,我在application類中定義了計算cache大小的方法。
package com.kale.lrucachetest; import android.app.ActivityManager; import android.app.Application; import android.content.Context; public class KaleApplication extends Application{ /** * @description * * @param context * @return 獲得須要分配的緩存大小,這裏用八分之一的大小來作 */ public int getMemoryCacheSize() { // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int memClass = ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache. return 1024 * 1024 * memClass / 8; } }
2.2 初始化類
/* 內存緩存 */ private LruCache<String, Bitmap> mMemoryCache;
設定緩存時要設定泛型,針對的是hashMap,你能夠看成是key-value。我這裏緩存的是bitmap,用到的key是string對象。
final int memoryCache = ((KaleApplication) getApplication()).getMemoryCacheSize(); Log.d(TAG, "cache size = " + memoryCache / 1024 / 1024 + "M"); mMemoryCache = new LruCache<String, Bitmap>(memoryCache) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。 return bitmap.getByteCount() / 1024; } }; // 初始化
我經過緩存的值來初始化了cache對象,而後重寫了sizeOf()方法。
3、添加/刪除緩存
當咱們初始化緩存後咱們就應該能給這個緩存添加對象和移除對象。
/** * @description 將bitmap添加到內存中去 * * @param key * @param bitmap */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * @description 經過key來從內存緩存中得到bitmap對象 * * @param key * @return */ private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); }
4、模擬從網絡下載圖片並加入緩存
4.1 判斷是否已經緩存過
通常咱們都是將圖片顯示到listview或者是gridView中,適配器讀取view前應該判斷在不在緩存中,若是在就直接顯示,若是不在就從網絡下載,其中可能還要用到viewholder類和滑動監聽器來提升流暢性。
/** * @description 將bitmap加載到imageview中去 * * @param resId * @param imageView */ private void loadBitmapToImageView(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); // 先看這個資源在不在內存中,若是在直接讀取爲bitmap,不然返回null if (bitmap != null) { Log.d(TAG, "in memory"); imageView.setImageBitmap(bitmap); } else { Log.d(TAG, "not in memory"); imageView.setImageResource(R.drawable.ic_launcher); // 若是沒有在內存中,先顯示默認的圖片,而後啓動線程去下載圖片 BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); // 啓動線程,模擬從網絡下載圖片,下載後加入緩存 } }
這個方法是典型的加載模式,看緩存,若是沒有就去啓動asyncTask下載圖片,因爲內部類會保留外部類的強引用,因此asyncTask不該該做爲內部類,並且通常用在10s內中的io操做,這裏爲了說明方便和儘量符合官方實例,就仍是用了asyncTask來講明。
4.2 異步任務中的操做
package com.kale.lrucachetest; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.widget.ImageView; public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap>{ private MainActivity mActivity; private ImageView mImageView; public BitmapWorkerTask(ImageView imageView) { // TODO 自動生成的構造函數存根 mImageView = imageView; mActivity = (MainActivity) imageView.getContext(); // 初始化activity } @Override protected Bitmap doInBackground(Integer... params) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Bitmap bitmap = BitmapFactory.decodeResource(mActivity.getResources(), R.drawable.kale); mActivity.addBitmapToMemoryCache(String.valueOf(R.drawable.kale), bitmap); return bitmap; } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); if (result != null) { mImageView.setImageBitmap(result); // 將bitmap設置到imageView中去 } } }
線程暫停1秒(模擬從網絡下載),而後獲得圖片(網絡訪問正常的狀況下),獲得後將bitmap放入緩存中,最後在imageview中展現。
5、經過Fragment來保存緩存
屏幕方向改變會致使Android摧毀正在運行的Activity,而後使用新的配置重新啓動該Activity (詳情,參考這裏 Handling Runtime Changes)。
須要注意避免在配置改變的時候致使從新處理全部的圖片,從而提升用戶體驗。
幸運的是,您在 使用內存緩存 部分已經有一個很好的圖片緩存了。該緩存能夠經過Fragment (Fragment會經過setRetainInstance(true)函數保存起來)來傳遞給新的Activity。
當Activity從新啓動 後,Fragment 被從新附加到Activity中,您能夠經過該Fragment來獲取緩存對象。下面是一個在 Fragment中保存緩存的示例:
RetainFragment
package com.kale.bitmaptest; import android.app.Fragment; import android.app.FragmentManager; import android.graphics.Bitmap; import android.os.Bundle; import android.util.LruCache; public class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public static LruCache<String, Bitmap> mRetainedCache; public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } }
Fragment中保存了一個lruCache對象,這個fragment有一個findOrCreateRetainFragment()方法。這個方法等同構造函數,它先判斷fragmentManager中有沒有這個fragment,若是有就調出,若是沒有就new一個。在onCreat中調用了setRetainInstance(true)來保存緩存。
Activity
package com.kale.bitmaptest; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.util.LruCache; public class TestActivit extends Activity { private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { // ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache<String, Bitmap>(10); RetainFragment.mRetainedCache = mMemoryCache; } // ... } }
相似的代碼在bitmapfun中就能夠看到:
/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.bitmapfun.util; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; /** * A simple non-UI Fragment that stores a single Object and is retained over configuration changes. * In this sample it will be used to retain the ImageCache object. */ public class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; private Object mObject; /** * Empty constructor as per the Fragment documentation */ public RetainFragment() {} /** * Locate an existing instance of this Fragment or if not found, create and * add it using FragmentManager. * * @param fm The FragmentManager manager to use. * @return The existing instance of the Fragment or the new instance if just * created. */ public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { // Check to see if we have retained the worker fragment. RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG); // If not retained (or first time running), we need to create and add it. if (mRetainFragment == null) { mRetainFragment = new RetainFragment(); fm.beginTransaction().add(mRetainFragment, TAG).commit(); } return mRetainFragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Make sure this Fragment is retained over a configuration change setRetainInstance(true); } /** * Store a single object in this Fragment. * * @param object The object to store */ public void setObject(Object object) { mObject = object; } /** * Get the stored object. * * @return The stored object */ public Object getObject() { return mObject; } }
在Activity中創建一個retainFragment對象,首先看看這個對象中有沒有緩存,若是是第一次啓動的話確定是沒有的,因此就初始化緩存,同時讓fragment去引用這個緩存對象(在fragment中備份)。若是這個activity是因爲屏幕方向改變而再次產生的,那麼就能夠從fragment中得到以前的緩存對象,無須從新初始化緩存了,這樣能夠保證以前的緩存不被丟棄。
6、總結
這樣咱們就完成了對lruCache的使用,如今咱們發現這個緩存類也沒那麼複雜,用法也十分簡單,正是由於簡單,因此咱們能夠很方便的對其進行擴展。固然了,這僅僅是作了內存緩存,熟悉緩存機制的朋友必定會知道磁盤緩存和內存緩存兩者的關係,有關磁盤緩存的問題我將在之後的文章中進行講述。
源碼下載:http://download.csdn.net/detail/shark0017/8395797
參考自:
http://stormzhang.com/android/2013/11/20/android-display-bitmaps-efficiently/
http://blog.csdn.net/androidzhaoxiaogang/article/details/7910364