Glide 系列四:Glide 的网络模块到底在哪里?

988 阅读7分钟

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
         
    
  • 处理上一个请求 [支线]

    • 当新的请求来了,会去处理上一个请求

    image-20220820153925104

  • 从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()

      image-20220820155630125

  • 当暂停标志位为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() 中的关键函数:当用户没有设置宽高时,会去测量宽高并回调到此处

image-20220820160024447

  • 从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);
    
    • 补充图片访问流程:命中缓存后直接返回

      1. 优先查找活动缓存(运行时缓存)
      2. 再查找内存缓存(运行时缓存)
      3. 再查找磁盘缓存
    • 访问依据,就是这个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 后使用工厂设计模式构建

      image-20220820163713756

    • decodeJob :代表具体任务(Runnable ),在Glide 4.11 后使用工厂设计模式构建

      image-20220820163742875

  • 将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 是接口

      image-20220820165627647

  • 此时进入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 接口

    • 接口展示:歇逼了,找不到实现类啊,

      image-20220820170208781

  • 这个实现类在哪里:在Glide 构造函数中,有个注册机,里面就给我设置好了的

 //注册机:Glide.java 中的
 .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
 //我们在使用Glide 时,第二个参数是String 类型的URL ,注册机帮助进行了适配 
  • 有了注册机,那么就找到上面那个接口的实现类了

    image-20220820170946055

  • 注册机展示:第一个参数为用户传入的实参(就是我们自己的写入的URL 之类的),在运行的时候是通过第三个参数,去构造第二个参数类型的数据流(注册机的本质)

    image-20220820171441488

  • 进入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

    image-20220820172507552

  • 此时,进入HttpUrlFetcher.loadData()

在HttpUrlFetcher.loadData()

  • 关键代码:

     InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    
  • 通过loadDataWithRedirects 进入HttpUrlFetcher.loadDataWithRedirects ()

在HttpUrlFetcher.loadDataWithRedirects ()

  • 这里就是Glide 的网络访问部分了,最终是会返回InputStream

    image-20220820172926573

  • 总结:Glide 的源码十分绕,RxJava 是编写思路绕,代码较为简单;而Glide 构建思路比如轻松,但是代码逻辑是非绕;

使用Glide为什么要加入网络权限?

  • 等待队列/运行队列
  • 执行Request ---> 活动缓存 --->内存缓存 ---> jobs.get检测执行的任务有没有执行完成 ---> HttpUrlFetcher.HttpURLConnection