Glide作為當(dāng)下火爆的圖片加載框架之一,相信大部分小伙伴們都有使用過的經(jīng)歷,當(dāng)然我也是經(jīng)常使用到。至于它的詳細(xì)用法我們今天就不一一介紹了,相信在網(wǎng)上已經(jīng)有很多優(yōu)秀的文章可以參考,沒有使用過的小伙伴們可以去自己搜索查看下。好的,我們直接進(jìn)入正題,我們接下來來分析Glide的緩存機(jī)制,并參考Gilde的緩存機(jī)制手寫一個(gè)自己的圖片緩存框架。
口說無憑,通過查閱資料:https://muyangmin.github.io/glide-docs-cn/doc/caching.html#glide里的緩存Glide的緩存分為:
1.活動(dòng)資源(Active Resources) -當(dāng)前頁(yè)面正在顯示的圖片資源
2. 內(nèi)存緩存(Memory cache) -內(nèi)存緩存
3. 資源類型(Resource) -其實(shí)指的就是磁盤緩存
4. 數(shù)據(jù)來源(Data) -網(wǎng)絡(luò)、文件等數(shù)據(jù)源
一般圖片加載的過程中(如果沒有禁止使用緩存),應(yīng)用會(huì)按照1,2,3,4的順序來依次進(jìn)行數(shù)據(jù)加載。需要注意一點(diǎn)是內(nèi)存緩存還對(duì)應(yīng)著一個(gè)Bitmap的復(fù)用池,當(dāng)內(nèi)存緩存需要舍棄圖片時(shí),如果圖片是復(fù)用對(duì)象,是不會(huì)被立即回收掉的,而是會(huì)被暫時(shí)放在復(fù)用池當(dāng)中。接下來我們就站在巨人的肩膀上,開始我們的構(gòu)建我們自己的圖片緩存框架,緩存結(jié)構(gòu)包括:內(nèi)存緩存,磁盤緩存,網(wǎng)絡(luò)緩存。
在開始之前我們需要做些知識(shí)的儲(chǔ)備:
1. Java弱引用 與 引用隊(duì)列
2. LruCache的用法
3. DiskLruCache的用法
4. Bitmap復(fù)用機(jī)制
完成準(zhǔn)備工作之后,接下來我們就進(jìn)入正題。第一步根據(jù)需求,我們來定義一個(gè)接口,方便對(duì)方法進(jìn)行管理
package com.example.learnglide;
import android.graphics.Bitmap;
/**
* 創(chuàng)建者:qinyafei
* 創(chuàng)建時(shí)間: 2019/6/20
* 描述: 圖片緩存相關(guān)接口
* 版本信息:1.0.0
**/
public interface IImgaeCache {
/**
* 初始化方法,因?yàn)橐龃疟P緩存,所以我們?cè)谶@里傳入文件路徑
*
* @param dir
*/
void init(String dir);
/**
* 將圖片放入內(nèi)存緩存
*
* @param key
* @param bitmap
*/
void putSrc2MemoryCache(String key, Bitmap bitmap);
/**
* 從內(nèi)存緩存中取得圖片
*
* @param key
* @return
*/
Bitmap getBitmapFromMemory(String key);
/**
* 從復(fù)用池中獲取圖片數(shù)據(jù)
*
* @param width 所需圖片寬度
* @param height 所需圖片高度
* @param simpleSize 縮放大小
* @return
*/
Bitmap getSrcFromReuseableBimtmapPool(int width, int height, int simpleSize);
/**
* 將圖片數(shù)據(jù)放入磁盤緩存
* @param key
* @param bitmap
*/
void putBitmap2DiskCache(String key, Bitmap bitmap);
/**
* 從磁盤中獲取圖片,要考慮復(fù)用問題
* @param reuseable
* @return
*/
Bitmap getSrcFromDiskCache(String key,Bitmap reuseable);
/**
* 清除緩存
*/
void clearCache();
}
新建一個(gè)接口的實(shí)現(xiàn)類MyImageCache ,類采用單例模式。具體代碼如下
package com.example.learnglide;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.LruCache;
import com.example.learnglide.disk.DiskLruCache;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 創(chuàng)建者:qinyafei
* 創(chuàng)建時(shí)間: 2019/6/20
* 描述:
* 版本信息:1.0.0
**/
public class MyImageCache implements IImgaeCache {
private static final int CACHE_SIZE = 10 * 1024 * 1024;
private static MyImageCache instance;
private Context context;
private LruCache<String, Bitmap> memoryCache;// 內(nèi)存緩存
private DiskLruCache diskLruCache;// 磁盤緩存
private BitmapFactory.Options options = new BitmapFactory.Options();
public static Set<WeakReference<Bitmap>> reuseablePool;// 復(fù)用池
ReferenceQueue referenceQueue; // 當(dāng)弱引用被棄用時(shí),會(huì)放到此隊(duì)列中
Thread gcThread;// 手動(dòng)回收線程
boolean shutDown;
private MyImageCache(Context context) {
this.context = context.getApplicationContext();// 為避免內(nèi)存泄漏
}
public static MyImageCache getInstance(Context context) {
if (null == instance) {
synchronized (MyImageCache.class) {
if (null == instance) {
instance = new MyImageCache(context);
}
}
}
return instance;
}
@Override
public void init(String dir) {
// 構(gòu)建一個(gè)線程安全的復(fù)用池,考慮到圖片加載多在子線程中進(jìn)行
reuseablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
intiReferenceQueue();
memoryCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
/**
* @return 圖片占用內(nèi)存大小
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//19之前必需同等大小,才能復(fù)用 inSampleSize=1
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {
// 如果是可復(fù)用的,不直接回收,而是暫時(shí)放于復(fù)用池中
reuseablePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue));
} else {
oldValue.recycle();
}
}
};
try {
diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 初始化引用隊(duì)列 && 回收線程
*/
private void intiReferenceQueue() {
if (null == referenceQueue) {
referenceQueue = new ReferenceQueue<Bitmap>();
//開啟一個(gè)回收線程,在檢測(cè)到有可回收對(duì)象時(shí),
// 手動(dòng)回收,避免了GC的等待時(shí)間,讓內(nèi)存處理更高效
gcThread = new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown) {
try {
Reference<Bitmap> reference = referenceQueue.remove();
Bitmap bitmap = reference.get();
if (null != bitmap && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
gcThread.start();
}
}
@Override
public void putSrc2MemoryCache(String key, Bitmap bitmap) {
memoryCache.put(key, bitmap);
}
@Override
public Bitmap getBitmapFromMemory(String key) {
return memoryCache.get(key);
}
@Override
public Bitmap getSrcFromReuseableBimtmapPool(int width, int height, int simpleSize) {
// api 11 之前是不存在Bitmap服用的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
Bitmap reuseable = null;
Iterator<WeakReference<Bitmap>> iterator = reuseablePool.iterator();
while (iterator.hasNext()) {
Bitmap bitmap = iterator.next().get();
if (null != bitmap) {
if (isBitmapCanResueable(bitmap, width, height, simpleSize)) {
reuseable = bitmap;
iterator.remove();
break;
} else {
iterator.remove();
}
}
}
return reuseable;
}
return null;
}
@Override
public void putBitmap2DiskCache(String key, Bitmap bitmap) {
DiskLruCache.Snapshot snapshot = null;
OutputStream os = null;
try {
snapshot = diskLruCache.get(key);
//如果緩存中已經(jīng)有這個(gè)文件 不理他
if (null == snapshot) {
//如果沒有這個(gè)文件,就生成這個(gè)文件
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (null != editor) {
os = editor.newOutputStream(0);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
editor.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot) {
snapshot.close();
}
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public Bitmap getSrcFromDiskCache(String key, Bitmap reuseable) {
DiskLruCache.Snapshot snapshot = null;
Bitmap bitmap = null;
try {
snapshot = diskLruCache.get(key);
if (null == snapshot) {
return null;
}
InputStream is = snapshot.getInputStream(0);
options.inMutable = true;
options.inBitmap = reuseable;
bitmap = BitmapFactory.decodeStream(is, null, options);
if (null != bitmap) {
memoryCache.put(key, bitmap);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot) {
snapshot.close();
}
}
return bitmap;
}
@Override
public void clearCache() {
memoryCache.evictAll();
}
/**
* 檢查圖片是否可以進(jìn)行復(fù)用
*
* @param bitmap
* @param w
* @param h
* @param inSampleSize
* @return
*/
private boolean isBitmapCanResueable(Bitmap bitmap, int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
}
if (inSampleSize >= 1) {
w /= inSampleSize;
h /= inSampleSize;
}
int byteCount = w * h * getPixelsCount(bitmap.getConfig());
return byteCount <= bitmap.getAllocationByteCount();
}
/**
* 獲取每個(gè)像素的字節(jié)數(shù)目
*
* @param config
* @return
*/
private int getPixelsCount(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
}
return 2;
}
}
至此有關(guān)緩存的代碼就書寫完成了,接下來定義一個(gè)類來使用這些緩存,代碼如下:
public class ImageLoadUtil {
/**
* 加載圖片
* @param context
* @param key
* @param width
* @param height
* @param simpleSize 縮放比例
* @return
*/
public static Bitmap loadImage(Context context, String key, int width, int height, int simpleSize) {
Bitmap bitmap = MyImageCache.getInstance(context).getBitmapFromMemory(key);// 從內(nèi)存中找
if (null == bitmap) {
Bitmap reuseable = MyImageCache.getInstance(context).getSrcFromReuseableBimtmapPool(width, height, simpleSize);
bitmap = MyImageCache.getInstance(context).getSrcFromDiskCache(key, reuseable);
if (null == bitmap) { //如果磁盤中也沒緩存,就從網(wǎng)絡(luò)下載,這里使用本地圖片模擬網(wǎng)絡(luò)圖片
Log.e("Img_Test", "從網(wǎng)絡(luò)加載: ");
bitmap = resizeBitmap(context, R.drawable.test, 80, 80, false, reuseable);
MyImageCache.getInstance(context).putBitmap2DiskCache(key, bitmap);
MyImageCache.getInstance(context).putSrc2MemoryCache(key, bitmap);
}else {
Log.e("Img_Test", "從磁盤加載: ");
}
}else {
Log.e("Img_Test", "從內(nèi)存加載: ");
}
return bitmap;
}
}
大功告成,如有什么錯(cuò)誤和不足的地方,還望各位指正。最后附上Demo地址:GitHub - qinyafeiii/LearnGlide: 參照Glide,手寫圖片緩存框架