編輯:關於Android編程
Android開發難免會遇到加載網絡圖片問題,而加載網絡圖片難免會遇到多次重復請求加載同一張圖片問題,這麼一來就導致多次加載網絡,不僅浪費資源而且給用戶體驗感覺加載圖片慢。從Android3.1開始,API中多了一個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
一下代碼是圖片緩存類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類來作為圖片緩存了。
效果 (關於gif怎麼生成的,我先錄手機的屏幕得到mp4文件,然後用這個網址:https://cloudconvert.com/mp4-to-gif 進行的mp4轉
本文實例講述了Android控件之TabHost用法。分享給大家供大家參考。具體如下:以下通過TabHost實現android選項卡。main.xml布局文件:<&
一、簡述 最近項目組打算引入weex,並選定了一個頁面進行試水。頁面很簡單,主要是獲取數據渲染頁面,並可以跳轉到指定的頁面。跟之前使用RN 相比,weex 確實要簡單很
在工作中,曾多次碰到ScrollView嵌套ListView的問題,網上的解決方法有很多種,但是雜而不全。我試過很多種方法,它們各有利弊。 在這裡我將會從使用Sc