Glide加载过程源码导读

Glide加载过程源码导读:
咱们从最常用的使用方式开始走起,
即 Glide.with(this).load("https://xxxx.jpg").into(imageView);

Glide.with(this) 会根据传入的参数不同创建不同的 RequestManager 对象并返回
主要有以下几种情形:
1.Fragment:则使用 Fragment.getChildFragmentManager() 向Fragment中添加一个子Fragment(只创建一次,再次调用时会先查找该子Fragment是否已存在),用于监听该Fragment的生命周期

2.FragmentActivity/Activity:则使用 FragmentActivity.getSupportFragmentManager()或者Activity.getFragmentManager() 向 Activity 中添加一个Fragment(只创建一次,再次调用时会先查找该子Fragment是否已存在),用于监听该Activity的生命周期

3.View:会查找该view是属于哪一个Fragment或者Activity,然后再依据1和2的情况

4.Context:会判断是否是Activity,则执行第二种情形;如果不是则使用 Context.getApplicationContext() 创建一个和Application生命周期一致的RequestManager对象,该对象只会创建一次保存在 RequestManagerRetriever.applicationManager变量中

5.如果在子线程调用 with方法则会直接创建或返回和Application生命周期一致的RequestManager对象

接下来看看 RequestManager.load() 方法中做了些什么,
首先调用 RequestManager.asDrawable() 方法返回一个 RequestBuilder 对象
接着执行 RequestBuilder.load() 该方法内部调用了 RequestBuilder.loadGeneric() 方法,loadGeneric方法中主要记录了加载时传入的 model

public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
}
// 在load中调用了 asDrawable() 方法,该方法最终执行到下面的语句,
// 即:创建了一个 RequestBuilder 对象,而传入的 resourceClass 为 Drawable.class
return new RequestBuilder<>(glide, this, resourceClass, context);

// 然后执行 RequestBuilder.load(Object model) 方法,该方法直接调用到下面的RequestBuilder.loadGeneric
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    if (isAutoCloneEnabled()) {
      return clone().loadGeneric(model);
    }
    // 只是简单记录了 model
    this.model = model;
    // 并设置  isModelSet = true
    isModelSet = true;
    // 然后返回 this
    return selfOrThrowIfLocked();
}

接下来执行 RequestBuilder.into(ImageView view) 方法,该方法内部
首先,创建了一个 Request, 不考虑error,thumbnail 的情况下,最终会创建一个 SingleRequest 对象,该对象保存了请求相关的 context,model,transcodeClass,requestOptions,target,targetListener,requestListeners等相关信息,error,thumbnail 的情形下也只是对多个 SingleRequest 进行了组合以形成一个更为复杂的 Request;
然后,执行 RequestManager.track(target, request); 在track方法中最终调用 Request.begin()方法启动本次请求;

// ,该方法内部又调用了下面的方法
private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target, 
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
    // 此处 callbackExecutor 主要用于图片加载完成后通知加载结果
    // 调用 into(ImageView view)方法时传入的是Executors.mainThreadExecutor(),即在主线程显示图片
    ....
    // 创建 Request, 不考虑error,thumbnail 的情况下,最终会创建一个 SingleRequest 对象,
    // 该对象保存了请求相关的 context,model,transcodeClass,requestOptions,target,targetListener,requestListeners等相关信息
    // error,thumbnail 的情形下也只是对多个 SingleRequest 进行了组合以形成一个更为复杂的 Request
    Request request = buildRequest(target, targetListener, options, callbackExecutor);
    // 获取与target绑定的上一次请求,并做一些比较,判断是否是相同的请求
    Request previous = target.getRequest();
    ....
    requestManager.clear(target);
    // 将request绑定到target上,
    // 如果是CustomViewTarget,则主要使用了 View.setTag(int key, final Object tag)方法
    target.setRequest(request);
    // 在track方法中最终调用 Request.begin()方法启动本次请求
    requestManager.track(target, request);
    return target;
  }

接下来看 SingleRequest.begin() 方法,
首先,获取 target 的宽高
然后,在 SingleRequest.onSizeReady()方法中调用 engine.load() 方法

public void begin() {
    synchronized (requestLock) {
      ....
      status = Status.WAITING_FOR_SIZE;
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        // 如果宽高有效,则开始加载,所以真正的加载是在 onSizeReady 方法中
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        // 先获取宽高信息,获取结果会通过 onSizeReady 回调通知
        target.getSize(this);
      }
      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) {
        // 开始加载回调通知
        target.onLoadStarted(getPlaceholderDrawable());
      }
    }
  }

// 在 SingleRequest.onSizeReady 中调用 Engine.load 方法开始加载
public void onSizeReady(int width, int height) {
    synchronized (requestLock) {
      ....
      // 调用 Engine.load 方法开始加载
      loadStatus = engine.load(....);
        ....
    }
  }

Engine.load()方法
首先,从内存中加载(loadFromMemory),如果内存中没有,再启动异步加载
异步加载最终调用 EngineJob.start(DecodeJob decodeJob) 方法内部会根据加载阶段选择合适的 executor 执行 decodeJob:
加载阶段主要分为:
1.从磁盘缓存的已经过采样和转码的 (downsampled/transformed) 文件加载
2.从磁盘缓存的源文件(没做过任何修改的) 加载
3.从最原始的文件来源(网络、磁盘文件等)加载

public <R> LoadStatus load(.... ResourceCallback cb ....) {
     // 这里的 ResourceCallback 参数传入的是 SingleRequest 对象
    ....
    // 根据请求的相关信息构建缓存的 key 
    EngineKey key =keyFactory.buildKey(....);
    EngineResource<?> memoryResource;
    synchronized (this) {
      // 先查询 内存中是否已存在本次请求的资源
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
      if (memoryResource == null) {
          // 如果内存中不存在本次请求的资源,则开启异步加载
        return waitForExistingOrStartNewJob(....);
      }
    }
    // 如果内存中 已存在 本次请求的资源,则直接通知回调接口
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE, false);
    return null;
  }

// Engine.waitForExistingOrStartNewJob()
private <R> LoadStatus waitForExistingOrStartNewJob(.... ResourceCallback cb ....) {
    // 这里的 ResourceCallback 参数传入的是 SingleRequest 对象
    ....
    // 根据 key 查询是否已经存在 EngineJob ,
    // 如果已存在则不用开启新的 EngineJob 去加载相同的资源了,
    // 只需要将回调接口添加到 EngineJob 以便于加载成功后通知
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      return new LoadStatus(cb, current);
    }
    // 创建一个 EngineJob 对象
    EngineJob<R> engineJob = engineJobFactory.build(key ....);
    // 创建 DecodeJob 对象,真正的异步加载(从磁盘/网络等)都是在 DecodeJob 中完成的,
    // DecodeJob 实现 Runnable ,所以可以在 executor 中执行
    // EngineJob 是实现了 DecodeJob.Callback 的,后续 DecodeJob 加载流程中会用于切换 executor,或者接收加载成功/失败的回调
    DecodeJob<R> decodeJob = decodeJobFactory.build(.... key .... engineJob);
    ....
    // 将 engineJob 加入 jobs 
    jobs.put(key, engineJob);
    // 将回调接口添加到 EngineJob 以便于加载成功后通知
    engineJob.addCallback(cb, callbackExecutor);
    // EngineJob.start 方法内部会根据加载阶段选择合适的 executor 执行 decodeJob
    // 加载阶段主要分为:
    // 1.从磁盘缓存的已经过采样和转码的 (downsampled/transformed) 文件加载
    // 2.从磁盘缓存的源文件(没做过任何修改的) 加载
    // 3.从最原始的文件来源(网络、磁盘文件等)加载
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
  }

接下来看 DecodeJob.run 方法
DecodeJob 会根据磁盘缓存策略,尝试不同的加载来源
默认磁盘缓存策略下 DiskCacheStrategy.decodeCachedResource() 和 DiskCacheStrategy.decodeCachedData() 都返回 true,
也即 先尝试从磁盘缓存的已经过采样和转码的 (downsampled/transformed) 文件加载,
如果缓存中没有,再尝试从磁盘缓存的源文件(没做过任何修改的) 加载,
如果缓存中依然没有,再尝试从最原始的文件来源(网络、磁盘文件等)加载。

public void run() {
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      // 加载的逻辑在 runWrapped 方法中
      runWrapped();
    } catch (Throwable t) {
    } finally {
        // DataFetcher 是真正用来从磁盘或网络加载文件的,
        // 其内部可能打开了一些 InputStream 等资源,
        // 所以必须调用其 cleanup 方法给其一个关闭资源的机会
        if (localFetcher != null) {
            localFetcher.cleanup();
        }
    }
}

// DecodeJob.runWrapped 方法
private void runWrapped() {
    // DecodeJob创建时,runReason 的初始值是 RunReason.INITIALIZE
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
}

// DecodeJob.getNextStage 方法
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        // 默认配置中 decodeCachedResource 返回 true
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE
            : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        // 默认配置中 decodeCachedData 返回 true
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE
            : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // RequestBuilder 可以配置 onlyRetrieveFromCache,其默认值为 false
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
}

// DecodeJob.getNextGenerator
private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        // ResourceCacheGenerator 负责从磁盘缓存的已经过采样和变换的 (downsampled/transformed) 文件加载
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        // DataCacheGenerator 负责从磁盘缓存的源文件(没做过任何修改的) 加载
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        // SourceGenerator 负责从最原始的文件来源(网络、用户手机中的文件等)加载
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
}

DecodeJob 的执行流程比较绕,我绘制了一个执行流程图如下:


glide_decode_job.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容