Glide 4.11.0版本代码剖析
主线:
Glide.with(MainActivity.this).load(url).into(imageView);
总体认识这几个方法都实现了哪些功能:
with
通过一个空白的Fragment去管理生命周期,最终返回RequestManager对象load
构建出RequestBuilder对象给后面into方法使用into
实现了缓存机制
Glide利用一个空白的Fragment去监听Activity的生命周期,然后可以根据生命周期的变化,对Glide加载图片的BitMap进行回收处理。
SupportRequestManagerFragment:
空白的Fragment,androidX
RequestManagerFragment:
空白的Fragment,android.app
RequestManagerRetriever:
通过这个类的get方法返回RequestManager对象
RequestManager:
对图片加载进行管理,比如:页面关闭时对图片BitMap进行销毁
根据Glide.with(参数
)传入的参数
来决定作用域,不同的作用域生命周期不同。
不会创建一个空白的Fragment来监听生命周期
会创建一个空白的Fragment来监听生命周期
同样不会创建一个空白的Fragment来监听生命周期
将空白的Fragment绑定到RequestMananger上,RequestManagerRetriever.java
代码如下:
private RequestManager supportFragmentGet(){
//省略非核心代码
//获取到空白的Fragment
SupportRequestManagerFragment current =
getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
//将RequestManager绑定到空白的Fragment上,空白的Fragment监听到Activity生命周期时,
//空白Frag同时会执行RequestManager的与生命周期同名的方法
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
Glide glide = Glide.get(context);
//通过工厂,创建RequestManager对象
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
//将RequestManager绑定到空白Fragment,空白Fragment生命周期发生改变时,
//RequestManager中会执行与生命周期同名的方法
current.setRequestManager(requestManager);
}
return requestManager;
}
/**
* 通过是Tag或者是Map集合获取到空白Fragment
**/
private SupportRequestManagerFragment getSupportRequestManagerFragment(
@NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
//通过Tag获取到空白Fragment
SupportRequestManagerFragment current =
(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//从缓存的Map集合中获取空白Fragment
current = pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
//通过Tag和Map缓存集合都没有获取到,则创建一个空白的Fragment
current = new SupportRequestManagerFragment();
//将创建的空白Fragment缓存到Map集合
pendingSupportRequestManagerFragments.put(fm, current);
//将空白Fragment添加到我们的Activity或Fragment中去
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
//发送Handler消息,在主线程中移除Map缓存集合中的空白Fragment
//主要目的是保证主线程中,FragmentManager通过Tag获取到的空白Fragment非空
handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
/**
* 接收发送过来的消息,移除Map缓存集合中的空白Fragment
**/
public boolean handleMessage(Message message) {
switch (message.what) {
case ID_REMOVE_FRAGMENT_MANAGER:
android.app.FragmentManager fm = (android.app.FragmentManager)
//从Map缓存集合中移除空白Fragment android.app包下的Fragment
removed = pendingRequestManagerFragments.remove(fm);
break;
case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER:
FragmentManager supportFm = (FragmentManager) message.obj;
//从Map缓存集合中移除空白Fragment AndroidX下的Fragment
removed = pendingSupportRequestManagerFragments.remove(supportFm);
break;
}
return handled;
}
在绑定空白Fragment的时候,首先需要获取到空白的Fragment,可以通过Tag标签获取到已经添加到Activity上的空白Fragment,也可以通过Map缓存集合中获取到创建的空白Fragment,然后将空白的Fragment绑定到RequestManager身上。
注意:
RequestManager实现了LifeCycleListener接口, 是在构造函数中以Handler发送消息的方式注册到ActivityFragmentLifeCycle中去的。所以,当页面生命周期发生变化的时候,会执行RequestManager中的与生命周期同名的方法。
当添加到Activity中的空白Fragment在创建的时候,会创建一个ActivityFragmentLifecycle作为参数传入,在Activity生命周期发生变化的时候,里面的空白Fragment就会监听到Activity生命周期的变化,同时会执行相同的生命周期方法,此时会执行我们传入的ActivityFragmentLifecycle对象的与生命周期同名的方法,而这些方法在执行的时候,就会遍历一个存储了LifeCycleListener接口的集合,然后执行集合中每一个接口实现类的与生命周期同名的方法。
解释说明:
Activity
空白Fragment
ActivityFragmentLifecycle
private final Set lifecycleListeners = Collections.newSetFromMap(new WeakHashMap());
LifeCycleListener
LifeCycleListener只实现了3个方法,分别是onStart、onStop和onDestroy,当页面执行与以上几个方法同名的生命周期方法时,就会回调到注册进入LifeCycleListener接口集合的实现类的onStart、onStop和onDestroy方法。
这里只用onStart()生命周期方法作为入口举例,生命周期的回调具体流程如下:
MainActivity.onStart()
SupportRequestManagerFragment.onStart()
ActivityFragmentLifeCycle.onStart()
RequestManager.onStart()
创建出RequestBuilder对象,供into方法使用。
流程如下:
load
(“https://img-blog.csdnimg.cn/5c2b98c8029f4ff5ac3e178d2c0a0940.png”) //加载网络图片url,不过这里并没有真正去加载asDrawable()
@RequestManager.javaas(Drawable.class)
@RequestManager.javanew RequestBuilder<>(glide, this, resourceClass, context)
@RequestManager.javainto的源码走向,这是一个极其复杂的流程,需要查阅Glide的into方法
时序图
,才能完整走通整个流程。
尝试走通into方法的整个流程:
该方法最终返回BitmapImageViewTarget对象
BitmapImageViewTarget
创建了一个Executor(并且这个Executor中包含了一个主线程的handler),这个Executor在把EngineJob当做线程池执行一个DecodeJob任务时会用到,然后就会执行DecodeJob的run方法,用来从从磁盘缓存中取出图片文件
SingleRequest
SingleRequest
,所以这里其实是调用的SingleRequest.begin()
BitmapImageViewTarget
,但是是在其父类ImageViewTarget
中完成的该方法处理,ImageViewTarget.onLoadStarted()
构建出EngineKey,这个对象中包含了图片的宽、高、签名等信息,通过这些信息可以作为找到存储图片的唯一标识
活动缓存和内存缓存中都没有查找到图片资源,则需要通过线程池从磁盘缓存中去查询图片资源
SourceGenerator
对象,也就是说currentGenerator = SourceGenerator
SourceGenerator.startNext()
helper.getLoadData().get(loadDataListIndex++)
; @SourceGenerator
modelLoader.buildLoadData
(model, width, height, options); @SourceGenerator
HttpGlideUrlLoader.buildLoadData()
helper.getLoadData().get(loadDataListIndex++)
得到的LoadData
类中的DataFetcher fetcher
参数,最终的实现类是HttpUrlFetcher
HttpUrlFetcher
对象所以最终执行的是HttpUrlFetcher.loadData()
这里面就会通过HttpUrlConnection去访问网络,最终返回图片的InputStream流
将返回的InputStream流回调回去,这里需要一层层回调上去,解析然后展示在控件上
通过HttpUrlConnection访问网络图片地址,拿到图片的InputStream后一层层往上回调流程如下:
通过解码,最终:decoded = Resource
最终返回Bitmap,就将InputStream最终转换成Bitmap了,可以在控件中展示
decoded = Resource
callback.onResourceDecode
//解释一下,这里有点懵逼了
将资源添加到活动缓存
activeResources.activate(key, resource);//将资源添加到活动缓存
线程池执行任务CallResourceReady,会运行里面的run方法
这里的target就是BitmapImageViewTarget对象,他的父类ImageViewTarget中包含了这个方法
可以通过下面这张图了解into在整个过程中,做了哪些处理:
这张图参考博客地址:
http://www.javashuo.com/article/p-vigwftpx-kg.html
活动缓存activeResources的存在,是因为如果App中内存缓存LruCacheResources中存储了过多的图片资源,会导致App占用的内存急剧增大,所以LruCacheResources中会利用LRU算法清理最近最不常用的图片资源,这个过程中可能会清理掉当前页面展示的图片的内存资源,从到导致App崩溃无法正常使用。所以,在内存缓存和App中间,会存在一个活动缓存ActiveResources,用来存放当前正在使用的图片的内存资源,这个活动缓存没有大小限制,随着页面的关闭而清空,清空之间将活动缓存中的图片资源添加到LruCacheResources内存缓存中去。
知识拓展:
Lru内存缓存和活动缓存都属于内存缓存,App被杀死后,内存缓存都会被清除;磁盘缓存属于硬盘缓存,会通过Lru算法进行资源清理。
磁盘缓存和Lru内存缓存都采用了Lru算法,所以我们来了解一些其中的Lru算法机制。
LruCache是一个Lru算法的类,里面通过使用LinkedHashMap来实现Lru算法机制,LinkedHashMap使用案例如下:
/**
* 测试Lru算法,最近最少使用原则
**/
private static void testLruCache() {
//int initialCapacity, float loadFactor, boolean accessOrder
//参数1:集合初始容量 参数2:加载因子,达到容量的多少后会扩容
//参数3:是否进行使用后排序,使用过则拍在最后面,排在前面的是最近最少使用的
LinkedHashMap linkedHashMap = new LinkedHashMap(0, 0.75f, true);
//依次按照1,2,3的顺序添加到集合;最先添加的元素最先获取到,最后添加的元素最后获取到
System.out.println("最先添加的元素最先获取到,最后添加的元素最后获取到");
linkedHashMap.put(1, "1");
linkedHashMap.put(2, "2");
linkedHashMap.put(3, "3");
System.out.println(linkedHashMap.toString());
//最先添加的元素被使用一次,会放在集合中最后面,就会最后获取到该元素
System.out.println("最先添加的元素被使用一次,就会最后获取到该元素");
linkedHashMap.get(1);
System.out.println(linkedHashMap.toString());
System.out.println();
}
打印日志:
> Task :javalib:JavaTest.main()
最先添加的元素最先获取到,最后添加的元素最后获取到
{1=1, 2=2, 3=3}
最先添加的元素被使用一次,就会最后获取到该元素
{2=2, 3=3, 1=1}
所谓资源封装,就是要封装获取图片资源的key,还有图片资源bitmap本身;当我们需要从活动缓存、Lru内存缓存和磁盘缓存中获取缓存的图片资源时,就需要通过这个唯一的key去查找bitmap图片资源。
活动缓存是一个Map集合,key就是我们上面资源封装出来的那个key,value就是包含了bitmap的图片资源;需要包含添加和删除操作,根据key、value添加图片资源,根据key从活动缓存中移除图片资源;同时还需要包含一个接口回调函数,因为在空白Fragment监听到页面销毁的生命周期方法时,会将当前页面正在使用的图片资源从活动缓存中删除,然后将图片资源移动到Lru内存缓存中。
内存缓存只需要直接继承自LruCache,无需实现LruCache实现的容器LinkedHashMap,还有里面包含的put和get方法,但是需要重写sizeOf()方法,重写计算集合中每一个缓存资源bitmap的大小。
DiskLruCache是由Jake Warton开发的,存在与git上的项目。
这个类同样也是继承自LruCache类,只不过数据不保存在缓存,而是通过io流的形式,保存到磁盘。
注意,我们是通过图片的url作为key,并且当做缓存资源文件的名称,但是这个key必须要加密,否则就是带有图片url的那种斜杠和冒号之类的格式,在文件路径中这属于不合格的格式。所以需要转换将这个数据进行转换,然后将bitmap数据存储在磁盘缓存文件中;然后我们就可以根据图片的key获取到磁盘中缓存文件中图片的bitmap数据。
几种情况下,缓存资源的加载过程分析:
1.在使用Glide的过程中,什么情况下会导致内存溢出,说说原因?
在子线程中使用Glide或者with方法传入的参数是Application的上下文时,都会存在内存溢出的风险。因为在这两种情况下,Glide的生命周期与App的生命周期相同,当我们在页面销毁的时候,有图片还没有加载完成,就会存在内存泄露的风险。因为没有创建空白的Fragment监听页面生命周期,对未完成的任务进行处理。
我们平时使用Glide的with方法时,传入的参数应该是非Application作用域的上下文,可以传入当前Activity的上下文。这种情况下,Glide会为我们创建一个空白的Fragment,他可以监听到Activity或者是Fragment的生命周期,然后在页面销毁的时候,会通过这个空白的Fragment,来完成资源的销毁工作,或者是对下载的图片不予处理等。
2.Glide源码中的缓存,为什么要有两个,一个活动缓存,一个内存缓存?
注意:
活动缓存也叫活动资源、活跃缓存、前台缓存;内存缓存也叫LRU缓存。内存缓存(LRU缓存):
是用来存放我们打开App后,最近最常使用的图片资源的内存缓存集合。为了防止App加载的图片资源过多,导致占用的系统内存资源过大问题,采用了LRU算法,根据缓存最大值对最近最少使用到的资源进行清理。但是在清理资源的过程中,可能会清理到我们当前页面展示过的资源,此时会导致应用崩溃,所以引出了活动缓存。活动缓存(活动资源、活跃资源、前台缓存):
是用来对正在使用的图片资源进行存储的内存缓存集合,这是一个没有采用过LRU算法的集合,所以不存在容量限制,不会对当前页面展示的资源进行清理。当我们的页面要展示图片时,先从活动缓存中去查找是否有图片资源,有则直接使用,没有则会去查找内存缓存,当内存缓存中存在我们的图片资源时,则将他移动到活动缓存,并从内存缓存中移除,然后再从活动缓存中取出使用;当我们的页面销毁时,需要将图片资源从活动缓存中移动到内存缓存中,同时删除活动缓存中的图片资源。