Glide 的缓存机制
Glide 的缓存机制,需要了解 LruCache 和 DiskLruCache,可以参考以下两篇博文
https://blog.csdn.net/guolin_blog/article/details/28863651
下面我们通过写一个阉割版的Glide来了解一下Glide的缓存机制
首先看一下Glide的一行代码经典用法
private final String IMAGE_URL = "http://p1.pstatp.com/large/166200019850062839d3";
Glide.with(this)
.load(IMAGE_URL)
.into(imageView);
在 with()函数中,Glide 使用一个没有界面的空白Fragment 实现了对生命周期(Activity Fragmet等的生命周期)的监听,Glide的缓存机制是在into()函数中实现的
Glide 的缓存主要分为内存中的缓存和本地磁盘中的缓存,我们通过RequestTargetEngine实现生命周期的监听以及网络加载成功后对图片资源的回调,先看代码
private ActiveCache activeCache; // 活动缓存
private MemoryCache memoryCache; // 内存缓存
private DiskLruCacheImpl diskLruCache; //磁盘缓存
public void into(ImageView imageView) {
this.imageView = imageView;
Tool.checkNotEmpty(imageView); // 检测:是否是空
Tool.assertMainThread(); // 检测:非主线程 抛出异常
// 触发:缓存机制
Value value = cacheAction();
if (value != null) {
imageView.setImageBitmap(value.getBitmap()); //显示缓存额图片
}
}
其中 ActiveCache MemoryCache DiskLruCacheImpl 分别代表了活动缓存、内存缓存、磁盘缓存
ActiveCache 是通过一个HashMap来实现对图片资源的存储,Map的键是将图片的url地址解码后返回的一个只包含16进制字符的唯一的字符串,Value里包含了图片的位图
public class ActiveCache {
private Map<String, Value> mapList = new HashMap<>();
MemoryCache 使用了Lru算法,实现比较简单直接使用了android的LruCache,重写了sizeOf()方法
public class MemoryCache extends LruCache<String, Value> {
DiskLruCacheImpl 直接继承了 DiskLruCache,也使用了Lru算法,接下来我们看下 cacheAction()函数,该函数实现了缓存机制
private Value cacheAction() {
// TODO 第一步:判断活动缓存是否有资源,如果有资源就返回,否则就继续往下找
Value value = activeCache.get(key);
if (null != value) {
Log.d(TAG, "cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>");
return value;
}
// TODO 第二步:判断内存缓存是否有资源,如果有资源 剪切(内存 ---> 活动)然后返回, 否则就继续往下找
value = memoryCache.get(key);
if (null != value) {
Log.d(TAG, "cacheAction: 本次加载的是在(内存缓存)中获取的资源>>>");
//移动操作 将资源从内存缓存剪切到活动缓存
activeCache.put(key, value); //把内存缓存中的元素,加入到活动缓存中...
memoryCache.remove(key); //移除内存缓存
return value;
}
// TODO 第三步:从磁盘缓存中你去找,如果找到了,把磁盘缓存的元素 加入到 活动缓存中...
value = diskLruCache.get(key);
if (null != value) {
Log.d(TAG, "cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>");
//把磁盘缓存中的元素加入活动缓存中....这里不是剪切是复制
activeCache.put(key, value);
return value;
}
// TODO 第四步:真正去加载外部资源 HTTP / 本地io
//note:只有本地资源才会直接返回一个 Value对象,如果从网络获取loadResource()方法将返回null
//最终通过异步回调来得到Value对象,也就是在 responseSuccess 方法中返回
value = new LoadDataManager().loadResource(path, this, glideContext);
if (value != null) {
return value;
}
return null; // 有意这样做的,为了后需好判断
}
资源加载顺序:活动缓存(ActiveCache)--->内存缓存(MemoryCache)--->磁盘缓存(DiskLruCache)--->外部资源(Http网络加载/本地io)
通过cacheAction()函数可以看出,Glide会首先从活动缓存 ActiveCache 中去获取图片资源,之后会从内存缓存 MemoryCache 中去获取,然后从本地磁盘DiskLruCache中获取
如果以上都无法获取资源将会从外部加载资源,一般通过Http进行异步网络加载,加载成功后通过接口将资源回调回来,这里我们会有一个疑问....
问题:我们既然已经有了实现了Lru算法的 内存缓存MemoryCache,为什么还有再加一个活动缓存 ActiveCache?
内存缓存是通过 Lru算法实现的,也就是最近最少使用的资源将会被移除,会有这么一种情况,被移除的资源恰好是我们页面中正在显示的图片资源,此时如果我们的资源是从内存缓存MemoryCache中获取的将会发生异常(因为正在显示的资源被移除了),所以每次显示的图片都是先从 活动缓存中 ActiveCache中获取的,活动缓存是用一个Map来存储的没有使用LRU算法,所以被显示的资源并不会移除,接下来看下loadResource()函数是怎么加载资源的
public Value loadResource(String path, ResponseListener responseListener, Context context) {
this.path = path;
this.responseListener = responseListener;
this.context = context;
// 加载 网络图片 本地存储图片 ......
Uri uri = Uri.parse(path);
//从网络加载图片
if ("HTTP".equalsIgnoreCase(uri.getScheme()) || "HTTPS".equalsIgnoreCase(uri.getScheme())) {
// 线程池
// 异步
new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()).execute(this); // 一执行后 run 函数执行 异步操作
}
// SD本地资源直接返回Value,本地资源不需要异步线程,所以可以自己返回
// ....
return null;
}
我们来看一下线程池中的 run()函数
private String path;//图片url地址
private ResponseListener responseListener;//回调接口
@Override
public void run() {
InputStream inputStream = null;
HttpURLConnection httpURLConnection = null; //HttpURLConnection内部已经是Okhttp,因为太高效了
try {
URL url = new URL(path);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000);
final int responseCode = httpURLConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 200
inputStream = httpURLConnection.getInputStream();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// TODO 省略Bitmap 做缩放,做比例,做压缩的代码,重点只关注缓存
// ......
//note:注意资源的回调要在主线程中完成,这里通过 post一个runnable的
//方法将异步线程中的资源切换到主线程 Looper.getMainLooper()-->UI线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Value value = new Value();
value.setBitmap(bitmap);
// 回调成功
responseListener.responseSuccess(value);
}
});
} else {
// 失败 切换主线程
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// 回调失败
responseListener.responseException(new IllegalStateException("请求失败,请求码:" + responseCode));
}
});
}
}catch (Exception e) {
e.printStackTrace();
} finally {//finally中关闭资源
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "run: 关闭 inputStream.close(); e:" + e.getMessage());
}
}
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
}
相关接口及方法的回调
/**
* 外置资源成功回调
* @param value
*/
@Override
public void responseSuccess(Value value) {
if (null != value) {
saveCache(key, value);//如果加载成功就将资源保存在缓存中
Log.d(TAG, "本次加载的是在(网络)中获取的资源>>>");
imageView.setImageBitmap(value.getBitmap());//显示图片
}
}
外部资源一旦加载成功,我们需要将加载成功后的资源保存到磁盘缓存中,这样下次再次加载同一资源就可以直接从磁盘缓存(内存缓存)中获取了
/**
* 外置资源加载成功后 保存到磁盘缓存
*/
private void saveCache(String key, Value value) {
Log.d(TAG, "saveCache: >>>>>>>>>>>>>>>>>>>>>>>>>> 加载外置资源成功后 ,保存到缓存中, key:" + key + " value:" + value);
value.setKey(key);
if (diskLruCache != null) {
diskLruCache.put(key, value); //保存到磁盘缓存中....
//activeCache.put(key, value);//是否保存到活动缓存*控制
}
}
最后我们看一下该缓存机制的执行情况:
场景一:这个是第一次安装打开app后,通过日志看出:当第一次点击加载图片,图片资源是从(网络)中获取的,第二次再次点击加载按钮,图片是从(磁盘缓存)中获取的第三次点击加载按钮,图片是从(活动缓存)中获取,之后无论点击多少次,只要不退出加载页面,图片都会从活动缓存中加载
2021-01-20 16:55:17.008 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 16:55:17.899 32265-32265/com.example.customglide D/RequestTargetEngine: saveCache: >>>>>>>>>>>>>>>>>>>>>>>>>> 加载外置资源成功后 ,保存到缓存中, key:ac037ea49e34257dc5577d1796bb137dbaddc0e42a9dff051beee8ea457a4668 value:com.example.customglide.resource.Value@91c4ee1
2021-01-20 16:55:19.482 32265-32265/com.example.customglide D/RequestTargetEngine: 本次加载的是在(网络)中获取的资源>>>
2021-01-20 16:55:40.127 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:40.272 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>
2021-01-20 16:55:45.562 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:45.564 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.231 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.233 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.392 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.394 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 16:55:48.539 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{962e060 #0 Fragment_Activity_NAME}
2021-01-20 16:55:48.540 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
场景二:退出当前加载页面(没有杀死app),然后再次进入加载页面,第一次点击加载按钮,图片是从内存缓存中获取的资源,因为虽然退出了app加载页面,但是app进程并没有被杀死 所以内存缓存中的资源还在,当我们退出加载页面时,Glide会监听到该Activity已经destroy,此时Glide会释放活动缓存中的资源,所以当我们再次打开加载页面第一次点击加载按钮时将会从(内存缓存)中加载资源,之后无论点击几次(加载页面不退出的情况下)都将从活动缓存中获取
2021-01-20 17:00:08.035 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经停止中 ....
2021-01-20 17:00:08.044 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 进行释放操作 缓存策略释放操作等 释放 活动缓存的所有资源 >>>>>> ....
2021-01-20 17:00:25.686 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2null
2021-01-20 17:00:25.688 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(内存缓存)中获取的资源>>>
2021-01-20 17:00:25.691 32265-32265/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 17:00:31.091 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b7a2ae #0 Fragment_Activity_NAME}
2021-01-20 17:00:31.093 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:00:34.590 32265-32265/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b7a2ae #0 Fragment_Activity_NAME}
2021-01-20 17:00:34.592 32265-32265/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
场景三:直接杀死app进程后再重新打开,进入加载页面第一次点击加载按钮时,Glide将从磁盘缓存中加载,因为当我们杀死app进程的时候,内存缓存也将被释放,此时只有磁盘缓存中还保留之前从网络加载的资源,之后无论点击几次(加载页面不退出的情况下)都将从活动缓存中获取
2021-01-20 17:04:23.759 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(磁盘缓存)中获取的资源>>>
2021-01-20 17:04:23.801 498-498/com.example.customglide D/RequestTargetEngine: glideInitAction: Glide生命周期之 已经开启了 初始化了....
2021-01-20 17:04:28.125 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:28.127 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:04:29.829 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:29.830 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
2021-01-20 17:04:30.961 498-498/com.example.customglide D/RequestManager: RequestManager: fragment2FragmentActivityFragmentManager{8b4bc01 #0 Fragment_Activity_NAME}
2021-01-20 17:04:30.962 498-498/com.example.customglide D/RequestTargetEngine: cacheAction: 本次加载的是在(活动缓存)中获取的资源>>>
本文地址:https://blog.csdn.net/lollo01/article/details/112878607