Glide框架解析
Glide框架基本使用
Glide框架原理
1. with()
Glide为什么不能在子线程中with?
子线程中不会去添加 生命周期管理机制,主线程才会添加一个 空白的Fragment去监听 Activity Fragment的变化。
每一个with()方法重载的代码都非常简单,都是调用调用getRetriever(activity).get(activity),返回一个RequestManager对象。
getRetriever(...).get(...)做了什么事情?
不论传入Activity、FragmentActivity、Fragment最终都会调用
supportFragmentGet()方法,而这两个方法最终流程都是一致的就是那就是会向当前的Activity当中添加一个隐藏的Fragment。
那么这里为什么要添加一个隐藏的Fragment呢?
答:因为Glide需要知道加载的生命周期。很简单的一个道理,如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,这时候Activity被用户关掉了,那么图片就应该取消加载,可是Glide并不知道Activity的生命周期,怎么办呢? 于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
既然有了Application,为什么不用registerActivityLifecycleCallbacks而是用隐藏的Fragment?
registerActivityLifecycleCallbacks是可以实现,并且我的小伙伴在自己的某些工程中也在使用,但是个人理解是这样的: registerActivityLifecycleCallbacks监控所有的Activity生命周期,然而当你使用Glide加载图片时,并不是所有的Activity都会用到Glide加载图片(大多数情况),所以呢,使用registerActivityLifecycleCallbacks存在资源浪费的现象。不仅如此,你监控了所有的activity怎么和Glide想要监控的Activity关联到一块去,虽然可以实现,但是这个办法真心不实用,既然Glide给了我们这么完美的解决方案我们就要学以致用,以后尽力用到自己的工程中去。
2.load()
我们可以知道.with()方法返回的是GlideRequests对象,GlideRequests是继承自RequestManager(负责管理和请求Glide的请求类),
返回 RequestBuilder 对象
3.into()
- 得到ImageViewTarget 显示图片的地方
return into(
// 得到ImageViewTarget 显示图片的地方
glideContext.buildImageViewTarget(view, transcodeClass));
- 构建一个请求
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Glide问题
1. Glide加载一个100x100的图片,是否会压缩后再加载?放到一个300x300的view上会怎样?
当我们调整ImageView大小事,Glide会为每个不同尺寸的ImageView缓存一张图片,也就是说不管你的这张图片有没有被加载过,只要ImageView的尺寸不一样,那么GLide就会重新加载一次,这时候,他会在加载ImageView之前从网络上重新下载,然后再缓存。 举个例子,如果一个页面的ImageView是300 * 300像素,而另一个页面中的ImageView是100 * 100像素,这时候想要让两个ImageView是同一张图片,那么Glide需要下载两次图片,并且缓存两张图片。
2.三级缓存原理
当 Android 端需要获得数据时比如获取网络中的图片,首先从内存中查找(按键查找),内存中没有的再从磁盘文件或
sqlite中去查找,若磁盘中也没有才通过网络获取
3.LRUCache 原理
LruCache是个泛型类,主要原理是:把最近使用的对象用强引用存储在LinkedHashMap中,当缓存满时,把最近最少使用的对象从内存中移除,并提供 get/put 方法完成缓存的获取和添加,LruCache是线程安全的,因为使用了 synchronized 关键字。
当调用 put()方法,将元素加到链表头,如果链表中没有该元素,大小不变,如果有,需调用
trimToSize方法判断是否超过最大缓存量,trimToSize()方法中有一个 while(true)死循环,如果缓存大小大于最大的缓存值,会不断删除LinkedHashMap中队尾的元素,即最少访问的,直到缓存大小小于最大缓存值。当调用
LruCache的 get 方法时,LinkedHashMap会调用recordAccess方法将此元素加到链表头部
4.Glide 跟Fresco对比
Glide:
- 多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
- 生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
- 高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
- 高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)
Fresco:
- 最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
- 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
- 适用于需要高性能加载大量图片的场景
5.假如让你自己写个图片加载框架,你会考虑哪些问题?
首先,梳理一下必要的图片加载框架的需求:
- 异步加载:线程池
- 切换线程:Handler,没有争议吧
- 缓存:LruCache、DiskLruCache
- 防止OOM:软引用、LruCache、图片压缩、Bitmap像素存储位置
- 内存泄露:注意ImageView的正确引用,生命周期管理
- 列表滑动加载的问题:加载错乱、队满任务过多问题
当然,还有一些不是必要的需求,例如加载动画等。
1.异步加载
线程池,多少个?
缓存一般有三级,内存缓存、硬盘、网络。
由于网络会阻塞,所以读内存和硬盘可以放在一个线程池,网络需要另外一个线程池,网络也可以采用Okhttp内置的线程池。
读硬盘和读网络需要放在不同的线程池中处理,所以用两个线程池比较合适。
2.切换线程
图片异步加载成功,需要在主线程去更新ImageView,
无论是RxJava、EventBus,还是Glide,只要是想从子线程切换到Android主线程,都离不开Handler。
3.缓存
我们常说的图片三级缓存:内存缓存、硬盘缓存、网络。
**内存缓存:**一般都是用
LruCacheGlide 默认内存缓存用的也是LruCache,只不过并没有用Android SDK中的LruCache,不过内部同样是基于LinkHashMap,所以原理是一样的。
为什么用LruCache?
LruCache 采用最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。
4.防止OOM
加载图片非常重要的一点是需要防止OOM,上面的LruCache缓存大小设置,可以有效防止OOM,但是当图片需求比较大,可能需要设置一个比较大的缓存,这样的话发生OOM的概率就提高了,那应该探索其它防止OOM的方法。
5.ImageView 内存泄露
Glide的做法是监听生命周期回调,看 RequestManager 这个类,在Activity/fragment 销毁的时候,取消图片加载任务,细节大家可以自己去看源码。