Glide 系列四:Glide 的网络模块到底在哪里?
本文概述:
-
传言
- 曾有一位开发者花费一周时间,找不到Glide 的网络模块在哪里;
- Glide 相较于RxJava,其整体设计思路并不复杂,但是使用了大量的接口,导致代码非常绕;而RxJava 代码较为简单,但是设计思路非常复杂;
-
本文以Glide 源码流程详细分析了,网络模块到底在哪里;
Glide 使用
- 基本使用
Glide.with(this).load(URL).into(imageView);
- 拆分使用过程
RequestManager requstManger = Glide.with(this);
RequestBuilder requestBuilder = requstManger.load(URL);
requestBuilder.into(imageView);
使用Glide ,出现内存泄漏,是为了什么?
-
作用域问题:
- 尽量在with的时候,传入有生命周期的作用域(非Application作用域),尽量避免使用了Application作用域,因为Application作用域不会对页面绑定生命周期机制,就回收不及时释放操作等....
源码分析:
Glide.with:
-
最终返回RequestManager 对象
- 关键代码:
public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { // Application 作用域 } else { // 非Application 作用域 } }
requestManager.load(URL )
- 最终返回RequestBuilder
ImageView 是怎么显示的?
requestBuilder.into(ImageView)
核心问题:
- 知道图片是怎么在imageView 控件上显示的?
整体流程分析:Glide 的网络访问模块在哪里?
编写说明:
- 按照into 源码执行顺序,分为主线与支线
- 跳过了许多细节,因为Glide 的源码实在是太深了;
在MainActivity 中
- 从requestBuilder.into(imageView); 通过into,进入RequestBuilder.into()
在RequestBuilder.into()
-
函数签名展示:
@NonNull public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
-
检查是否为主线程,控件是否为空 [支线]
Util.assertMainThread(); Preconditions.checkNotNull(view);
-
根据控件属性(scaleType)定义,决定requestOptions 走哪里去 [支线]
- 使用Switch-case 实现,没有设置控件的scaleType 属性,走默认流程:case FIT_END
- 并且在这段代码中使用了clone() 操作,这是原型设计模式
switch (view.getScaleType()) { case FIT_END://默认流程 requestOptions = requestOptions.clone().optionalFitCenter(); break;
-
构建ImageViewTarget (显示图片) [主线]
-
后续源码一定会回调到这个地方 ,伏笔一
- 因为这个ImageViewTarget 是用来显示图片的 ;
-
代码展示
//构建出ImageViewTarget return into( glideContext.buildImageViewTarget(view, transcodeClass), ) ;
-
-
从return into,通过into 再次进入RequestBuilder.into()
在RequestBuilder.into()
-
函数签名展示
private <Y extends Target<TranscodeType>> Y into(
-
存在Request 接口:为了扩展性,需要面向于高层 [主线]
- Request 接口的子类:SingleRequest
//源码: Request request = buildRequest(target, targetListener, options, callbackExecutor); //接口 public interface Request { //伏笔二: Request request = new SingleRequest
-
处理上一个请求 [支线]
- 当新的请求来了,会去处理上一个请求
-
从requestManager.track(target, request); 通过track 进入RequestManager.track()
在RequestManager.track()
-
函数签名展示:
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
-
从requestTracker.runRequest(request); 通过runRequest 进入RequestTracker.runRequest
在RequestTracker.runRequest
-
函数签名展示:
public void runRequest(@NonNull Request request) {
-
将当前请求添加到执行队列中: [主线]
requests.add(request);
-
当暂停标志位为false ,执行请求 [主线]
if (!isPaused) { request.begin(); }
-
通过begin 进入SingleRequest.begin()
-
-
当暂停标志位为true ,将请求添加到等待队列中 [主线]
else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); }
在SingleRequest.begin()
-
函数签名展示:
@Override public void begin() {
- 因为我们在之前分析过了,Request 的子类为SingleRequest 所以可以大胆向下走
- 注意:在阅读源码时,一定要去埋下伏笔,不然源码根本不好找,遇到接口实现类不清楚,那么就往前走
-
begin() 中的关键函数:当用户没有设置宽高时,会去测量宽高并回调到此处
- 从onSizeReady(overrideWidth, overrideHeight); 通过onSizeReady 进入SingleRequest.onSizeReady()
在SingleRequest.onSizeReady()
-
函数签名展示:
@Override public void onSizeReady(int width, int height) {
-
加了锁的:多线程并发
private final Object requestLock; synchronized (requestLock) {
-
通过引擎加载:
loadStatus = engine.load(
-
从engine.load( 通过load 进入Engine.load()
在Engine.load() [主线]
-
存在Enginekey
EngineKey key = keyFactory.buildKey(
-
存在缓存机制
EngineResource<?> memoryResource; synchronized (this) { memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
-
补充图片访问流程:命中缓存后直接返回
- 优先查找活动缓存(运行时缓存)
- 再查找内存缓存(运行时缓存)
- 再查找磁盘缓存
-
访问依据,就是这个key
-
这个key 是怎么构造的?
- 根据图片的宽、高、签名为每一张图片创建一把唯一的key
- 构造代码
EngineKey key = keyFactory.buildKey( model, signature, width, height, transformations, resourceClass, transcodeClass, options);
-
-
通过loadFromMemory 进入Engine.loadFromMemory()
Engine.loadFromMemory() 缓存机制
-
函数签名:
@Nullable private EngineResource<?> loadFromMemory(
-
优先查找活动缓存(运行时一级缓存):若命中,则直接返回
EngineResource<?> active = loadFromActiveResources(key); if (active != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return active; }
-
再查找内存缓存(运行时二级缓存):若命中,则直接返回
EngineResource<?> cached = loadFromCache(key); if (cached != null) { if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return cached; }
-
备注:
-
当任一运行时缓存命中时,会回退至Engine.load() 执行
//命中缓存,回调给外界去显示图片 cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
-
当运行时缓存无一命中,返回null
return null;
-
此时在Engine.load() 中则会执行waitForExistingOrStartNewJob
if (memoryResource == null) { return waitForExistingOrStartNewJob(
-
通过waitForExistingOrStartNewJob 进入Engine.waitForExistingOrStartNewJob
-
-
-
为什么要设计两级运行时缓存
-
内存缓存由LRUcache 管理
-
业务场景:maxSize = 3
- 当新增数据时,超过maxSize ,就会移除掉内存缓存中最长未使用的图片,如果这张图片正在被Activity 使用,就会出现崩溃;
-
为了解决这个崩溃:设计了活动缓存
- 将所有正在显示的图片,放到活动缓存中;也就是Activity 真正显示的图片必须访问活动缓存
-
在Engine.waitForExistingOrStartNewJob()
-
查找有无正在执行的任务
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); }
-
EngineJob :线程池大管家,在Glide 4.11 后使用工厂设计模式构建
-
decodeJob :代表具体任务(Runnable ),在Glide 4.11 后使用工厂设计模式构建
-
-
将decodeJob (具体任务)交给engineJob (线程池)执行
engineJob.start(decodeJob);
-
具体执行逻辑
public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }
-
-
decodeJob (具体任务) 具体是怎么执行的?
- 通过DecodeJob 跳转至DecodeJob.run()
在DecodeJob.run()
-
代码展示:
public void run() { …… runWrapped(); …… }
-
进入DecodeJob.runWrapped();
在DecodeJob.runWrapped();
-
函数签名展示:
private void runWrapped() {
-
拿到缓存策略机制:通过switch-case
-
没有配置就走默认
switch (runReason) { case INITIALIZE: stage = getNextStage(Stage.INITIALIZE);
- 默认缓存策略机制
currentGenerator = getNextGenerator(); //返回这个:伏笔二 new SourceGenerator(decodeHelper, this);
-
这样走的
private DataFetcherGenerator getNextGenerator() { switch (stage) { …… case SOURCE://走这个 return new SourceGenerator(decodeHelper, this);
-
执行获取到的缓存策略:runGenerators(); 进入DecodeJob.runGenerators();
-
在DecodeJob.runGenerators();
-
函数签名:
private void runGenerators() {
-
注意,此时我们走的是默认的缓存流程
while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) {
-
currentGenerator 指的是SourceGenerator ,因为startNext 是接口
-
-
此时进入SourceGenerator.startNext()
在SourceGenerator.startNext()
-
关键代码:helper.getLoadData()
while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++);
-
通过getLoadData() 进入DecodeHelper.getLoadData()
在DecodeHelper.getLoadData()
-
结论:最终返回根据用户传入参数,拿到的真实对象
- 比如说,我们传入URL ,那么getLoadData 拿到HttpUrlFetcher 对象
-
关键代码:modelLoader.buildLoadData
LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
-
通过buildLoadData 进入ModelLoader 接口的buildLoadData 接口
-
接口展示:歇逼了,找不到实现类啊,
-
-
这个实现类在哪里:在Glide 构造函数中,有个注册机,里面就给我设置好了的
//注册机:Glide.java 中的
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
//我们在使用Glide 时,第二个参数是String 类型的URL ,注册机帮助进行了适配
-
有了注册机,那么就找到上面那个接口的实现类了
-
注册机展示:第一个参数为用户传入的实参(就是我们自己的写入的URL 之类的),在运行的时候是通过第三个参数,去构造第二个参数类型的数据流(注册机的本质)
-
进入HttpGlideUrlLoader.buildLoadData()
在HttpGlideUrlLoader.buildLoadData()
-
关键代码:Glide 的网络访问模块
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
-
总结:
- HttpGlideUrlLoader :只是装饰对象
- HttpUrlFetcher :才是真实对象
-
此时,向上回退至SourceGenerator.startNext()
回退至SourceGenerator.startNext()
-
此时拿到了HttpUrlFetcher 对象
loadData = helper.getLoadData().get(loadDataListIndex++);
-
将拿到的HttpUrlFetcher 作为参数传入startNextLoad(loadData);
- 此时进入SourceGenerator.startNextLoad();
在SourceGenerator.startNextLoad();
-
准备执行
private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData(
-
进入loadData :发现这个居然又是接口!!!
- 幸好知道它的实现类是HttpUrlFetcher
-
此时,进入HttpUrlFetcher.loadData()
在HttpUrlFetcher.loadData()
-
关键代码:
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
-
通过loadDataWithRedirects 进入HttpUrlFetcher.loadDataWithRedirects ()
在HttpUrlFetcher.loadDataWithRedirects ()
-
这里就是Glide 的网络访问部分了,最终是会返回InputStream
-
总结:Glide 的源码十分绕,RxJava 是编写思路绕,代码较为简单;而Glide 构建思路比如轻松,但是代码逻辑是非绕;
使用Glide为什么要加入网络权限?
- 等待队列/运行队列
- 执行Request ---> 活动缓存 --->内存缓存 ---> jobs.get检测执行的任务有没有执行完成 ---> HttpUrlFetcher.HttpURLConnection