Glide.with(content) .load(url) .into(imageView);
with绑定生命周期,load指定加载资源,into指明加载目标。
- with()方法的重载种类非常多,既可以传入Activity,也可以传入Fragment或者是Context,实际上只有两种情况而已,即传入Application类型的参数,和传入非Application类型的参数。
- Application:自动就是和应用程序的生命周期是同步的。
- 非Application:不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的:在 Activity 上创建一个透明的
ReuqestManagerFragment
加入到FragmentManager
中,通过添加的 Fragment 感知Activty\Fragment
的生命周期。因为添加到 Activity 中的 Fragment 会跟随Activity 的生命周期。在RequestManagerFragment
中的相应生命周期方法中通过liftcycle
传递给在lifecycle
中注册的LifecycleListener。
因为Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
Glide的缓存机制,主要分为2种缓存,一种是内存缓存,一种是磁盘缓存。
之所以使用内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。
之所以使用磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。
正式因为有着这两种缓存的结合,才构成了Glide极佳的缓存效果。
内存缓存: LruResourceCache(memory)+
弱引用 activeResources
Map
<Key, WeakReference
<EngineResource
>>> activeResources
正在使用的资源,当 acquired变量大于 0,说明图片正在使用,放到 activeResources
弱引用缓存中,经过 release()
后,acquired=0,说明图片不再使用,会把它放进 LruResourceCache
中。
Glide 默认内存缓存用的也是LruCache,只不过并没有用Android SDK中的LruCache,不过内部同样是基于LinkHashMap,使用的为LruResourceCache,
原理是一样的。LruCache 采用最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。
磁盘缓存: DiskLruCache
,这里分为 Source(原始图片)和 Result(转换后的图片)
第一次获取图片,肯定网络取,然后存 active\disk 中,再把图片显示出来,第二次读取相同的图片,并加载到相同大小的 imageview
中,会先从 memory 中取,没有再去 active 中获取。如果 activity 执行到 onStop
时,图片被回收,active 中的资源会被保存到memory 中,active中的资源被回收。当再次加载图片时,会从 memory 中取,再放入 active 中,并将 memory中对应的资源回收。
之所以需要 activeResources
,它是一个随时可能被回收的资源,memory 的强引用频繁读写可能造成内存激增频繁 GC
,而造成内存抖动。资源在使用过程中保存在 activeResources
中,而 activeResources
是弱引用,随时被系统回收,不会造成内存过多使用和泄漏。
Glide 内存缓存最大空间(maxSize)=每个进程可用最大内存0.4(低配手机是 每个进程可用最大内存0.33)
磁盘缓存大小是 250MB int DEFAULT_DISK_CACHE_SIZE = 250 1024 1024;
读取一张图片的时候,获取顺序:
Lru算法缓存--> 弱引用缓存--> 磁盘缓存(如果设置了的话)
当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,并将该图片放入WeakReference中,如果LruCache中没有,则去WeakReference中寻找,如果WeakReference中有,则从WeakReference中取出图片使用,如果WeakReference中也没有图片,则从磁盘缓存/网络中加载图片。
注:图片正在使用时存在于 activeResources 弱引用map中。
将图片缓存的时候,写入顺序:
弱引用缓存 --> Lru算法缓存--> 磁盘缓存中
当图片不存在的时候,先从网络下载图片,然后将图片存入弱引用中,glide会采用一个acquired(int)变量用来记录图片被引用的次数,
当acquired变量大于0的时候,说明图片正在使用中,也就是将图片放到弱引用缓存当中;
如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用方法来释放资源,首先会将缓存图片从弱引用中移除,然后再将它put到LruResourceCache当中。
这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。
关于LruCache
最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。
LruCache 内部用LinkHashMap存取数据,在双向链表保证数据新旧顺序的前提下,设置一个最大内存,往里面put数据的时候,当数据达到最大内存的时候,将最老的数据移除掉,保证内存不超过设定的最大值。
关于LinkedHashMap
LinkHashMap 继承HashMap,在 HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)
当我们调整imageview的大小时,Picasso会不管imageview大小是什么,总是直接缓存整张图片,而Glide就不一样了,它会为每个不同尺寸的Imageview缓存一张图片,也就是说不管你的这张图片有没有加载过,只要imageview的尺寸不一样,那么Glide就会重新加载一次,这时候,它会在加载的imageview之前从网络上重新下载,然后再缓存。
举个例子,如果一个页面的imageview是300 * 300像素,而另一个页面中的imageview是100 * 100像素,这时候想要让两个imageview像是同一张图片,那么Glide需要下载两次图片,并且缓存两张图片。
public LoadStatus load() {
// 根据请求参数得到缓存的键
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
}
看到了吧,缓存Key的生成条件之一就是控件的长宽。
因为Glide 在加载资源的时候,如果是在 Activity、Fragment 这一类有生命周期的组件上进行的话,会创建一个透明的 RequestManagerFragment 加入到FragmentManager 之中,感知生命周期,当 Activity、Fragment 等组件进入不可见,或者已经销毁的时候,Glide 会停止加载资源。但是如果,是在非生命周期的组件上进行时,会采用Application 的生命周期贯穿整个应用,所以 applicationManager 只有在应用程序关闭的时候终止加载。
概括来说,图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作。
首先,梳理一下必要的图片加载框架的需求:
当然,还有一些不是必要的需求,例如加载动画等。
异步加载需要线程池个数:
缓存一般有三级,内存缓存、硬盘、网络。
由于网络会阻塞,所以读内存和硬盘可以放在一个线程池,网络需要另外一个线程池,网络也可以采用Okhttp内置的线程池。
读硬盘和读网络需要放在不同的线程池中处理,所以用两个线程池比较合适。
Glide 必然也需要多个线程池,看下源码是不是这样
public final class GlideBuilder {
...
private GlideExecutor sourceExecutor; //加载源文件的线程池,包括网络加载
private GlideExecutor diskCacheExecutor; //加载硬盘缓存的线程池
...
private GlideExecutor animationExecutor; //动画线程池
Glide使用了三个线程池,不考虑动画的话就是两个。
图片异步加载成功,需要在主线程去更新ImageView,
无论是RxJava、EventBus,还是Glide,只要是想从子线程切换到Android主线程,都离不开Handler。
看下Glide 相关源码:
class EngineJob implements DecodeJob.Callback,Poolable {
private static final EngineResourceFactory DEFAULT_FACTORY = new EngineResourceFactory();
//创建Handler
private static final Handler MAIN_THREAD_HANDLER =
new Handler(Looper.getMainLooper(), new MainThreadCallback());
Glide设计及流程
以上为Glide的总体设计图。
整个库分为RequestManager(请求管理器)、Engine(数据获取引擎)、Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache(本地缓存)、Transformation(图片处理)、Encoder(编码处理)、Registry(图片类型以及解析器配置)、Target(目标)等模块。
简单流程:Glider收到加载及显示资源任务,创建Request并将它交给RequestManager,Request启动Engine去数据源获取资源,得到资源后通过Transformation处理后交给Target.Glide依赖DiskLRUCache、GifDecoder等开源库去完成本地缓存和Gif图片解密工作;
Glide核心
为Bitmap 维护一个BitmapPool对象池, 对象池的主要目的是通过减少大对象的分配以重用来提高性能!
优点:
加载速度快,框架体积小,4、5百kb
缺点:
以上为Picasso的总体设计图。
整个库分为Dispatcher、RequestHandler以及Downloader、PicassoDrawable等模块。
简单流程:Picasso收到加载显示图片任务后,创建Request并将它交给Dispatcher,Dispatcher分发任务到具体RequestHandler,任务通过MemoryCache及Handler(数据获取接口)获取图片,图片获取成功后通过PicassoDrawable显示到Target中;
上面Data的File system部分,Picasso没有自定义本地缓存的接口,默认使用http的本地缓存,API19以上使用okhttp,一下使用UrlConnection,所以如果需要自定义本地缓存就需要自定义Downloader;
优点
缺点:加载速度没有其他框架快;
特点:只缓存一个全尺寸的图片,根据需求的大小在压缩转换;
Fresco设计及流程
以上为Fresco的总体设计图
整个库分为UI:DraweeView(View控件)、Drawable(图片数据)、DraweeController(图片控制器)、DraweeHiierarchy(图片体系);Core:DataSource(数据源)、ImagePipeline(图像管道)、Producer(生产者)、ProducerFacotry(生产工厂)、Subcriber(订阅)、Supplier(供应者)、Consumer(消费者);IO/Data:MemoryCache(内存缓存)、Network、DiskCache(磁盘缓存)、Recourse(本地资源)
简单流程:从上面的结构可以看出,fresco主要采用了工厂+建造者的模式实现功能,逻辑划分比较清楚;Fresco框架整体是一个MVC模式,DrawableView--->View用来显示顶层视图、DrawableController--->Control控制加载图片的配置 事件的分发、DrawableHierarchy--->Model 用于存储和描述图片信息,同时也封装了一些图片的显示和视图层级的方法;ImagePipeline模块负责从网络、本地文件系统、本地资源加载图片
优点:
缺点:
参考:https://www.jianshu.com/p/069d8400ebab
https://www.cnblogs.com/billshen/p/13306285.html
https://liuyangbajin.blog.csdn.net/article/details/103193470
https://www.jianshu.com/p/1ab5597af607