原来面试的时候写精通Glide,这样问我这样答
前言
这几天在很多地方看到有聊Glide的,想起前年还是去年的时候面试的时候也有问到Glide相关的,正好今天趁着周五来聊一聊面试中Glide的那些东西
- 1 、图片库对比
- 2 、LRUCache 原理
- 3丶LruCache 底层实现原理
- 4 、图片加载原理
- 5 、自己去实现图片库,怎么做?
- 6 、Glide 源码解析
- 7 、Glide 使用什么缓存
- 8 、Glide 内存缓存如何控制大小?
- 9丶三级缓存原理?
- 10丶如何设计一个大图加载框架?
面试整理学习路线(禁止偷偷拿走O(∩_∩)O)
请查看完整的PDF版
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
可以点击关于我联系我获取完整PDF
(VX:mm14525201314)
一丶LRUCache 原理
LruCache
是个泛型类,主要原理是:把最近使用的对象用强引用存储在 LinkedHashMap
中,当缓存满时,把最近最少使用的对象从内存中移除,并提供 get/put 方法完成缓存的获取和添加LruCache
是线程安全的,因为使用了 synchronized 关键字。
当调用 put()方法,将元素加到链表头,如果链表中没有该元素,大小不变,如果没有,需调用 trimToSize
方法判断是否超过最大缓存量,trimToSize()
方法中有一个 while(true)死循环,如果缓存大小大于最大的缓存值,会不断删除 LinkedHashMap
中队尾的元素,即最少访问的,直到缓存大小小于最大缓存值。当调用 LruCache
的 get 方法时,LinkedHashMap
会调用recordAccess
方法将此元素加到链表头部
二、Glide 源码解析
1)Glide.with(context)创建了一个 RequestManager
,同时实现加载图片与组件生命周期绑定:
在 Activity 上创建一个透明的 ReuqestManagerFragment
加入到 FragmentManager
中,通过添加的 Fragment 感知Activty\Fragment
的生命周期。因为添加到 Activity 中的 Fragment 会跟随Activity 的生命周期。在 RequestManagerFragment
中的相应生命周期方法中通过 liftcycle
传递给在 lifecycle
中注册的 LifecycleListener
2)RequestManager.load(url)
创建了一个 RequestBuilder<T>
对象 T 可以是 Drawable
对象或是 ResourceType
等
3 ) RequestBuilder.into(view)
-->into(glideContext.buildImageViewTarget(view, transcodeClass))
返 回 的 是 一 个DrawableImageViewTarget
, Target 用 来 最 终 展 示 图 片 的 ,buildImageViewTarget
-->ImageViewTargetFactory.buildTarget()
根据传入 class 参数不同构建不同的 Target 对象,这个 Class 是根据构建 Glide 时是否调用了 asBitmap()
方法,如果调用了会构建出BitmapImageViewTarget
,否则构建的是GlideDrawableImageViewTarget
对象。
-->GenericRequestBuilder.into(Target)
, 该 方 法 进 行 了 构 建 Request , 并 用RequestTracker.runRequest()
-->GenericRequest.begin()
Request request = buildRequest(target); // 构建 Request 对象, target.setRequest(request); lifecycle.addListener(target); requestTracker.runRequest(request); // 判断 Glide 当前是不是处于暂停状态
onSizeReady()
--> `Engine.load(signature, width, height, dataFetcher, loadProvider,transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this)
a)先构建
EngineKey;
b)loadFromCache
从 缓 存 中 获 取
EngineResource, 如 果 缓 存 中 获 取 到 cache 就 调 用
cb.onResourceReady(cached);
c)如果缓存中不存在调用
loadFromActiveResources从 active中获取,如果获取到就调用
cb.onResourceReady(cached);
d)如果 active 中也不存在,调用
EngineJob.start(EngineRunnable), 从而调用
decodeFromSource()/decodeFromCache()-->如果是调 用
decodeFromSource()-->
ImageVideoFetcher.loadData()-->
HttpUrlFetcher()调 用
HttpUrlConnection进 行 网 络 请 求 资 源 --> 得 于
InputStream()后 , 调 用
decodeFromSourceData()-->
loadProvider.getSourceDecoder().decode() 方 法 解 码-->
GifBitmapWrapperResourceDecoder.decode()-->
decodeStream()先从流中读取 2 个字节判断是 GIF 还是普通图,若是 GIF 调用
decodeGifWrapper()来解码,若是普通静图则调用
decodeBitmapWrapper()来解码-->
bitmapDecoder.decode()`
三丶Glide 使用什么缓存?
1) 内存缓存: LruResourceCache(memory)+
弱引用 activeResources
Map
<Key, WeakReference
<EngineResource
<?>>> activeResources
正在使用的资源,当 acquired变量大于 0,说明图片正在使用,放到 activeResources
弱引用缓存中,经过 release()
后,acquired=0,说明图片不再使用,会把它放进 LruResourceCache
中
2)磁盘缓存: DiskLruCache
,这里分为 Source(原始图片)和 Result(转换后的图片)
第一次获取图片,肯定网络取,然后存 active\disk 中,再把图片显示出来,第二次读取相同的图片,并加载到相同大小的 imageview
中,会先从 memory 中取,没有再去 active 中获取。如果 activity 执行到 onStop
时,图片被回收,active 中的资源会被保存到memory 中,active中的资源被回收。当再次加载图片时,会从 memory 中取,再放入 active 中,并将 memory中对应的资源回收。
之所以需要 activeResources
,它是一个随时可能被回收的资源,memory 的强引用频繁读写可能造成内存激增频繁 GC
,而造成内存抖动。资源在使用过程中保存在 activeResources
中,而 activeResources
是弱引用,随时被系统回收,不会造成内存过多使用和泄漏。
四丶Glide 内存缓存如何控制大小?
Glide 内存缓存最大空间(maxSize)=每个进程可用最大内存0.4(低配手机是 每个进程可用最大内存0.33)
磁盘缓存大小是 250MB int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
五丶LruCache 底层实现原理:
LruCache
中 Lru
算法的实现就是通过 LinkedHashMap
来实现的。LinkedHashMap
继承于HashMap
,它使用了一个双向链表来存储 Map 中的 Entry 顺序关系,对于 get、put、remove 等操作,LinkedHashMap
除了要做 HashMap
做的事情,还做些调整 Entry 顺序链表的工作。
LruCache
中将 LinkedHashMap
的顺序设置为 Lru
顺序来实现 LRU 缓存,每次调用 get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用 put 插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
六丶三级缓存原理
当 Android 端需要获得数据时比如获取网络中的图片,首先从内存中查找(按键查找),内存中没有的再从磁盘文件或 sqlite
中去查找,若磁盘中也没有才通过网络获取
七丶如何设计一个大图加载框架
图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作
- 封装参数: 从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;
- 解析路径: 图片的来源有多种,格式也不尽相同,需要规范化;
- 读取缓存: 为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;
- 查找文件/下载文件: 如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;
- 解码: 这一步是整个过程中最复杂的步骤之一,有不少细节,下个博客会说;
- 变换: 解码出Bitmap之后,可能还需要做一些变换处理(圆角,滤镜等);
- 缓存: 得到最终bitmap之后,可以缓存起来,以便下次请求时直接取结果;
- 显示: 显示结果,可能需要做些动画(淡入动画,crossFade等)
请查看完整的PDF版
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
可以点击关于我联系我获取完整PDF
(VX:mm14525201314)