中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中如何使用LruCache內存緩存框架

發布時間:2021-07-12 14:07:21 來源:億速云 閱讀:159 作者:Leah 欄目:移動開發

本篇文章為大家展示了Android中如何使用LruCache內存緩存框架,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

1、基本的使用示例

首先,讓我們來簡單介紹一下如何使用 LruCache 實現內存緩存。下面是 LruCache 的一個使用示例。

這里我們實現的是對 RecyclerView 的列表的截圖的功能。因為我們需要將列表的每個項的 Bitmap 存儲下來,然后當所有的列表項的 Bitmap 都拿到的時候,將其按照順序和位置繪制到一個完整的 Bitmap 上面。如果我們不使用 LruCache 的話,當然也能夠是實現這個功能——將所有的列表項的 Bitmap 放置到一個 List 中即可。但是那種方式存在缺點:因為是強引用類型,所以當內存不足的時候會導致 OOM。

在下面的方法中,我們先獲取了內存的大小的 8 分之一作為緩存空間的大小,用來初始化 LruCache 對象,然后從 RecyclerView 的適配器中取出所有的 ViewHolder 并獲取其對應的 Bitmap,然后按照鍵值對的方式將其放置到 LruCache 中。當所有的列表項的 Bitmap 都拿到之后,我們再創建最終的 Bitmap 并將之前的 Bitmap 依次繪制到最終的 Bitmap 上面:

public static Bitmap shotRecyclerView(RecyclerView view) {
 RecyclerView.Adapter adapter = view.getAdapter();
 Bitmap bigBitmap = null; if (adapter != null) { int size = adapter.getItemCount(); int height = 0;
 Paint paint = new Paint(); int iHeight = 0; final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用內存的 8 分之一作為該緩存框架的緩存空間
 final int cacheSize = maxMemory / 8;
 LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize); for (int i = 0; i < size; i++) {
 RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));
 adapter.onBindViewHolder(holder, i);
 holder.itemView.measure(
 View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
 holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),
 holder.itemView.getMeasuredHeight());
 holder.itemView.setDrawingCacheEnabled(true);
 holder.itemView.buildDrawingCache();
 Bitmap drawingCache = holder.itemView.getDrawingCache(); if (drawingCache != null) {
 bitmaCache.put(String.valueOf(i), drawingCache);
 }
 height += holder.itemView.getMeasuredHeight();
 }
 bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
 Canvas bigCanvas = new Canvas(bigBitmap);
 Drawable lBackground = view.getBackground(); if (lBackground instanceof ColorDrawable) {
 ColorDrawable lColorDrawable = (ColorDrawable) lBackground; int lColor = lColorDrawable.getColor();
 bigCanvas.drawColor(lColor);
 } for (int i = 0; i < size; i++) {
 Bitmap bitmap = bitmaCache.get(String.valueOf(i));
 bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);
 iHeight += bitmap.getHeight();
 bitmap.recycle();
 }
 } return bigBitmap;
}

因此,我們可以總結出 LruCahce 的基本用法如下:

首先,你要聲明一個緩存空間的大小,在這里我們用了運行時內存的 8 分之 1 作為緩存空間的大小

LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize);

但是應該注意的一個問題是緩存空間的單位的問題。因為 LruCache 的鍵值對的值可能是任何類型的,所以你傳入的類型的大小如何統計需要自己去指定。后面我們在分析它的源碼的時候會指出它的單位的問題。LruCahce 的 API 中也已經提供了計算傳入的值的大小的方法。我們只需要在實例化一個 LruCache 的時候覆寫該方法即可。而這里我們認為一個 Bitmap 對象所占用的內存的大小不超過 1KB.

然后,我們可以像普通的 Map 一樣調用它的 put() 和 get() 方法向緩存中插入和從緩存中取出數據:

bitmaCache.put(String.valueOf(i), drawingCache);
Bitmap bitmap = bitmaCache.get(String.valueOf(i));

2、LruCahce 源碼分析

2.1 分析之前:當我們自己實現一個 LruCache 的時候,我們需要考慮什么

在我們對 LruCache 的源碼進行分析之前,我們現來考慮一下當我們自己去實現一個 LruCache 的時候需要考慮哪些東西,以此來帶著問題閱讀源碼。

因為我們需要對數據進行存儲,并且又能夠根據指定的 id 將數據從緩存中取出,所以我們需要使用哈希表表結構。或者使用兩個數組,一個作為鍵一個作為值,然后使用它們的索引來實現映射也行。但是,后者的效率不如前者高。

此外,我們還要對插入的元素進行排序,因為我們需要移除那些使用頻率最小的元素。我們可以使用鏈表來達到這個目的,每當一個數據被用到的時候,我們可以將其移向鏈表的頭節點。這樣當要插入的元素大于緩存的最大空間的時候,我們就將鏈表末位的元素移除,以在緩存中騰出空間。

綜合這兩點,我們需要一個既有哈希表功能,又有隊列功能的數據結構。在 Java 的集合中,已經為我們提供了 LinkedHashMap 用來實現這個功能。

實際上在 Android 中的 LruCache 也正是使用 LinkedHashMap 來實現的。LinkedHashMap 拓展自HashMap。如果理解 HashMap 的話,它的源碼就不難閱讀。LinkedHashMap 僅在 HashMap 的基礎之上,又將各個節點放進了一個雙向鏈表中。每次增加和刪除一個元素的時候,被操作的元素會被移到到鏈表的末尾。Android 中的 LruCahce 就是在 LinkedHashMap 基礎之上進行了一層拓展,不過 Android 中的 LruCache 的實現具有一些很巧妙的地方值得我們學習。

2.2 LruCache 源代碼分析

從上面的分析中我們知道了選擇 LinkedHashMap 作為底層數據結構的原因。下面我們分析其中的一些方法。這個類的實現還有許多的細節考慮得非常周到,非常值得我們借鑒和學習。

2.2.1 緩存的最大可用空間

在 LruCache 中有兩個字段 size 和 maxSize. maxSize 會在 LruCache 的構造方法中被賦值,用來表示該緩存的最大可用的空間:

int cacheSize = 4 * 1024 * 1024; // 4MiB,cacheSize 的單位是 KBLruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { protected int sizeOf(String key, Bitmap value) { return value.getByteCount();
 }
}};

這里我們使用 4MB 來設置緩存空間的大小。我們知道 LruCache 的原理是指定了空間的大小之后,如果繼續插入元素時,空間超出了指定的大小就會將那些“可以被移除”的元素移除掉,以此來為新的元素騰出空間。那么,因為插入的類型時不確定的,所以具體被插入的對象如何計算大小就應該交給用戶來實現。

在上面的代碼中,我們直接使用了 Bitmap 的 getByteCount() 方法來獲取 Bitmap 的大小。同時,我們也注意到在最初的例子中,我們并沒有這樣去操作。那樣的話一個 Bitmap 將會被當作 1KB 來計算。

這里的 sizeOf() 是一個受保護的方法,顯然是希望用戶自己去實現計算的邏輯。它的默認值是 1,單位和設置緩存大小指定的 maxSize 的單位相同:

protected int sizeOf(K key, V value) { return 1;
}

這里我們還需要提及一下:雖然這個方法交給用戶來實現,但是在 LruCache 的源碼中,不會直接調用這個方法,而是

private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value);
 } return result;
}

所以,這里又增加了一個檢查,防止參數錯誤。其實,這個考慮是非常周到的,試想如果傳入了一個非法的參數,導致了意外的錯誤,那么錯誤的地方就很難跟蹤了。如果我們自己想設計 API 給別人用并且提供給他們自己可以覆寫的方法的時候,不妨借鑒一下這個設計。

2.2.2 LruCache 的 get() 方法

下面我們分析它的 get() 方法。它用來從 LruCahce 中根據指定的鍵來獲取對應的值:

/**
 * 1). 獲取指定 key 對應的元素,如果不存在的話就用 craete() 方法創建一個。
 * 2). 當返回一個元素的時候,該元素將被移動到隊列的首位;
 * 3). 如果在緩存中不存在又不能創建,就返回n ull
 */public final V get(K key) { if (key == null) { throw new NullPointerException("key == null");
 }
 V mapValue; synchronized (this) { // 在這里如果返回不為空的話就會將返回的元素移動到隊列頭部,這是在 LinkedHashMap 中實現的
 mapValue = map.get(key); if (mapValue != null) { // 緩存命中
 hitCount++; return mapValue;
 } // 緩存沒有命中,可能是因為這個鍵值對被移除了
 missCount++;
 } // 這里的創建是單線程的,在創建的時候指定的 key 可能已經被其他的鍵值對占用
 V createdValue = create(key); if (createdValue == null) { return null;
 } // 這里設計的目的是防止創建的時候,指定的 key 已經被其他的 value 占用,如果沖突就撤銷插入
 synchronized (this) {
 createCount++; // 向表中插入一個新的數據的時候會返回該 key 之前對應的值,如果沒有的話就返回 null
 mapValue = map.put(key, createdValue); if (mapValue != null) { // 沖突了,還要撤銷之前的插入操作
 map.put(key, mapValue);
 } else {
 size += safeSizeOf(key, createdValue);
 }
 } if (mapValue != null) {
 entryRemoved(false, key, createdValue, mapValue); return mapValue;
 } else {
 trimToSize(maxSize); return createdValue;
 }
}

這里獲取值的時候對當前的實例進行了加鎖以保證線程安全。當用 map 的 get() 方法獲取不到數據的時候用了 create() 方法。因為當指定的鍵值對找不到的時候,可能它本來就不存在,可能是因為緩存不足被移除了,所以,我們需要提供這個方法讓用戶來處理這種情況,該方法默認返回 null. 如果用戶覆寫了 create() 方法,并且返回的值不為 null,那么我們需要將該值插入到哈希表中。

插入的邏輯也在同步代碼塊中進行。這是因為,創建的操作可能過長而且是非同步的。當我們再次向指定的 key 插入值的時候,它可能已經存在值了。所以當調用 map 的 put() 的時候如果返回不為 null,就表明對應的 key 已經有對應的值了,就需要撤銷插入操作。最后,當 mapValue 非 null,還要調用 entryRemoved() 方法。每當一個鍵值對從哈希表中被移除的時候,這個方法將會被回調一次。

最后調用了 trimToSize() 方法,用來保證新的值被插入之后緩存的空間大小不會超過我們指定的值。當發現已經使用的緩存超出最大的緩存大小的時候,“最近最少使用” 的項目將會被從哈希表中移除。

那么如何來判斷哪個是 “最近最少使用” 的項目呢?我們先來看下 trimToSize() 的方法定義:

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

顯然,這里是使用了 LinkedHashMap 的 eldest() 方法,這個方法的返回值是:

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

也就是 LinkedHashMap 的頭結點。那么為什么要移除頭結點呢?這不符合 LRU 的原則啊,這里分明是直接移除了頭結點。實際上不是這樣,魔力發生在 get() 方法中。在 LruCache 的 get() 方法中,我們調用了 LinkedHashMap 的 get() 方法,這個方法中又會在拿到值的時候調用下面的方法:

void afterNodeAccess(Node<K,V> e) { // move node to last
 LinkedHashMapEntry<K,V> last; if (accessOrder && (last = tail) != e) {
 LinkedHashMapEntry<K,V> p =
 (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
 p.after = null; if (b == null)
 head = a; else
 b.after = a; if (a != null)
 a.before = b; else
 last = b; if (last == null)
 head = p; else {
 p.before = last;
 last.after = p;
 }
 tail = p;
 ++modCount;
 }
}

上述內容就是Android中如何使用LruCache內存緩存框架,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

陆良县| 阿合奇县| 沙坪坝区| 威信县| 临朐县| 宣威市| 湘潭县| 外汇| 澄江县| 徐州市| 清水县| 张家港市| 曲周县| 大安市| 隆安县| 靖边县| 鄯善县| 沽源县| 齐齐哈尔市| 西吉县| 且末县| 宜川县| 华宁县| 怀集县| 和平县| 崇义县| 休宁县| 鹿泉市| 噶尔县| 浮梁县| 奎屯市| 永州市| 辽中县| 墨玉县| 大田县| 门源| 宜宾县| 克山县| 拉萨市| 沙田区| 阳信县|