Glide 4.11.0 图片加载流程深入分析源码

从Glide图片加载流程深入分析源码

不知道有没有小伙伴跟我一样,使用Glide已经有好多年,平时看源码比较零碎,对Glide的源码只能算一知半解,面试遇到分析Glide的源码,无从说起……

那今天就从0开始,对Glide源码进行一个整体的梳理,更进一步的了解Glide的原理。

注意:此文章基于Glide 4.11.0版本

众所周知,我们使用Gilde加载图片时,最常用的一行代码:

Glide.with(context).load(url).into(imageView)

这里就只先分析此行代码的主线流程,省略了其他的配置方法。

1. Glide.with()

首先从Glide的with方法开始,它是我们最开始调用的方法,有多个重载:

with.png

无论是哪个重载方法,内部都是return了一行 getRetriever().get() 代码:

@NonNull
public static RequestManager with(@NonNull Activity activity) {
  return getRetriever(activity).get(activity);
}

先调用 getRetriever 返回了一个 RequestManagerRetriever 对象,然后在调用 RequestManagerRetrieverget 方法返回一个RequestManager 对象

RequestManagerRetrieverget方法,也是有多个重载:

get.png

其中最大的区别就是在于context和非context的参数的方法,实现各不相同:

如果是非Context的参数,比如 activity、fragment、view,其中fragmentview都可以找到一个所属的Activity对象,那么最后就会调用到get(Activity activity),并内部调用fragmentGet方法:

public RequestManager get(@NonNull Activity activity) {
  if (Util.isOnBackgroundThread()) {
    return get(activity.getApplicationContext());
  } else if (activity instanceof FragmentActivity) {
    return get((FragmentActivity) activity);
  } else {
    assertNotDestroyed(activity);
    frameWaiter.registerSelf(activity);
    android.app.FragmentManager fm = activity.getFragmentManager();
    return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}

fragmentGet方法中,创建了一个Fragment和RequestManager:

private RequestManager fragmentGet(
    @NonNull Context context,
    @NonNull android.app.FragmentManager fm,
    @Nullable android.app.Fragment parentHint,
    boolean isParentVisible) {
  RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    Glide glide = Glide.get(context);
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    // ...
  }
  return requestManager;
}

这里稍微提一下RequestManagerFragment,getRequestManagerFragment创建的RequestManagerFragment是Glide用来进行图片加载的生命周期管理的,最终通过FragmentManager添加到Activity上,它本身什么都不展示,只是一个空的Fragment,仅仅是通过Fragment的生命周期来管理图片加载的请求,避免内存泄漏问题,可以看getRequestManagerFragment方法的实现:

private RequestManagerFragment getRequestManagerFragment(
    @NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint) {
  RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    current = pendingRequestManagerFragments.get(fm);
    if (current == null) {
      current = new RequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      pendingRequestManagerFragments.put(fm, current);
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

回到主线,我们到这里就从RequestManagerRetriever中获得了一个RequestManager对象,从而就可以开始第二步动作:load

2. Glide.with().load()

当我们获得一个RequestManager后,就可以接着调用.load()的方法,现在的调用就就到了Glide.with().load(),可以看到load也是有多个重载方法,可以支持各种数据的load,包括file、string的url,uri等等……

load.png

load内部是通过调用asDrawable方法,asDrawable再调用了as方法,创建了一个RequestBuilder并返回:

asDrawable()

public RequestBuilder<Drawable> asDrawable() {
  return as(Drawable.class);
}

as()

public <ResourceType> RequestBuilder<ResourceType> as(
    @NonNull Class<ResourceType> resourceClass) {
  return new RequestBuilder<>(glide, this, resourceClass, context);
}

Glide.with().load().into()

这就是我们调用load方法之后得到RequestBuilder的由来,得到RequestBuilder之后,按照逻辑顺序调用RequestBuilderinto方法,也是主线逻辑里面最重要的部分:

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
  // ...
  // 构建Request请求
  Request request = buildRequest(target, targetListener, options, callbackExecutor);

  Request previous = target.getRequest();
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
  // ...
    return target;
  }

  requestManager.clear(target);
  target.setRequest(request);
  // 执行加载请求
  requestManager.track(target, request);

  return target;
}

这里省略了不相关的代码,into方法中,主要是构建了一个Request对象,然后调用requestManager.track(target, request)开始加载图片,继续跟踪到requestManager.track方法,位于RequestManager.java中:

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
  targetTracker.track(target);
  requestTracker.runRequest(request);
}

可以看到request继续传递到requestTracker.runRequest(request)中,可以继续跟踪看看:

public void runRequest(@NonNull Request request) {
  requests.add(request);
  if (!isPaused) {
    request.begin();
  } else {
    // ...
  }
}

RequestTracker.java类里面,runRequest调用了request.begain()方法

由于Request是一个接口,这里的request对象在构建的时候其实是构建了它的实现类SingleRequest,位于RequestBuilder.java:

private Request obtainRequest(
    Object requestLock,
    Target<TranscodeType> target,
    RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> requestOptions,
    RequestCoordinator requestCoordinator,
    TransitionOptions<?, ? super TranscodeType> transitionOptions,
    Priority priority,
    int overrideWidth,
    int overrideHeight,
    Executor callbackExecutor) {
return SingleRequest.obtain(
      context,
      glideContext,
      requestLock,
      model,
      transcodeClass,
      requestOptions,
      overrideWidth,
      overrideHeight,
      priority,
      target,
      targetListener,
      requestListeners,
      requestCoordinator,
      glideContext.getEngine(),
      transitionOptions.getTransitionFactory(),
      callbackExecutor);
}

那么在SingleRequestbegin方法里面,判断语句有好几个,千万要迷路,这里的重点是onSizeReady

public void begin() {
  synchronized (requestLock) {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    // ...
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }
  // ...
  }
}

继续跟踪onSizeReady,里面使用engine对象调用了load方法:

public void onSizeReady(int width, int height) {
  stateVerifier.throwIfRecycled();
  synchronized (requestLock) {
    // ...
    loadStatus =
        engine.load(
            glideContext,
            model,
            requestOptions.getSignature(),
            this.width,
            this.height,
            requestOptions.getResourceClass(),
            transcodeClass,
            priority,
            requestOptions.getDiskCacheStrategy(),
            requestOptions.getTransformations(),
            requestOptions.isTransformationRequired(),
            requestOptions.isScaleOnlyOrNoTransform(),
            requestOptions.getOptions(),
            requestOptions.isMemoryCacheable(),
            requestOptions.getUseUnlimitedSourceGeneratorsPool(),
            requestOptions.getUseAnimationPool(),
            requestOptions.getOnlyRetrieveFromCache(),
            this,
            callbackExecutor);
    // ...
  }
}

继续分析Engine的load方法,这里加载请求就揭开了Gilde的缓存机制:

public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map<Class<?>, Transformation<?>> transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

  // 构建加载资源的key
  EngineKey key =
      keyFactory.buildKey(
          model,
          signature,
          width,
          height,
          transformations,
          resourceClass,
          transcodeClass,
          options);

  EngineResource<?> memoryResource;
  synchronized (this) {
    // 从内存中加载
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

    if (memoryResource == null) {
    // 内存中没有就等待或者是开始新的加载
      return waitForExistingOrStartNewJob(
          glideContext,
          model,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          options,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache,
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }

  cb.onResourceReady(
      memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  return null;
}

首先Glide是 loadFromMemory 从内存中加载资源,这里的内存细分一下,分别是 活动资源内存缓存,Glide是优先从活动资源中获取资源,获取不到再从内存缓存中获取,如果内存缓存也获取不到,loadFromMemory 就返回null:

private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  // 检查是否从内存缓存中读取
  if (!isMemoryCacheable) {
    return null;
  }

  // 从活动缓存中读取
  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;
  }
  return null;
} 

这里从内存缓存中读取到了资源之后,还把资源加入到活动缓存,这样做的目的是为了下一次获取数据更快(先从活动资源获取数据):

private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    activeResources.activate(key, cached);
  }
  return cached;
}

现在回过头来看 waitForExistingOrStartNewJob 方法,这个方法是从 loadFromMemory 没有加载到数据时才调用,waitForExistingOrStartNewJob 里面构建了 engineJobdecodeJob 的对象,engineJob 对象用来启动了一个 decodeJobengineJob 很简单,就是维护了线程池,进行线程的调度,这里在start 里面执行了 decodeJob

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
  executor.execute(decodeJob);
}

由此看来,既然是线程调度,decodeJob 是被执行的部分,那DecodeJob类肯定是实现了 Runnable 接口,并将主要的逻辑写到了 run 方法里面,跟踪代码一看,果不其然:

class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable {
      // ...
}

避免干扰,我将run方法里面的注释和不相关的逻辑给删除掉了:

public void run() {
  // ...
  try {
    // ...
    // 主要代码,我们继续跟踪这个方法
    runWrapped();
  } catch (CallbackException e) {
    throw e;
  } catch (Throwable t) {
    // ...
  } finally {
    // ...
  }
}

继续跟踪runWrapped方法:

private void runWrapped() {
  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);
  }
}

这里通过我的注释应该很容易理解这个switch语句了,至于获取图片成功后的 decodeFromRetrievedData,暂时停一下稍候分析,其他的两个case语句,不管是 INITIALIZE 初始化还是 SWITCH_TO_SOURCE_SERVICE 从磁盘缓存获取不到数据进行重试,都是要调用 runGenerators 来获取数据,我们继续跟踪看看这个方法:

private void runGenerators() {
  //...
  // 条件语句中的 currentGenerator.startNext() 才是重点
  while (!isCancelled
      && currentGenerator != null
      && !(isStarted = currentGenerator.startNext())) {

    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

    if (stage == Stage.SOURCE) {
      reschedule();
      return;
    }
  }
  // ...
}

首先看while的条件语句:

  • !isCancelled :如果没有取消请求
  • currentGenerator != null : currentGenerator对象不为空
  • !(isStarted = currentGenerator.startNext()) : currentGenerator.startNext()的执行结果会赋值给 isStarted

前两个不过多解释,主要是第三个语句:如果isStarted的值为false,表示没有执行成功,就会执行while语句体里面的内容,while语句体里面就会获取下一个 Stage,以及通过Stage 来获取对应的 Generator

private Stage getNextStage(Stage current) {
  switch (current) {
    case INITIALIZE:
      return diskCacheStrategy.decodeCachedResource()
          ? Stage.RESOURCE_CACHE
          : getNextStage(Stage.RESOURCE_CACHE);
    case RESOURCE_CACHE:
      return diskCacheStrategy.decodeCachedData()
          ? Stage.DATA_CACHE
          : getNextStage(Stage.DATA_CACHE);
    case DATA_CACHE:
      // Skip loading from source if the user opted to only retrieve the resource from cache.
      return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
    case SOURCE:
    case FINISHED:
      return Stage.FINISHED;
    default:
      throw new IllegalArgumentException("Unrecognized stage: " + current);
  }
}
private DataFetcherGenerator getNextGenerator() {
  switch (stage) {
    case RESOURCE_CACHE:
      return new ResourceCacheGenerator(decodeHelper, this);
    case DATA_CACHE:
      return new DataCacheGenerator(decodeHelper, this);
    case SOURCE:
      return new SourceGenerator(decodeHelper, this);
    case FINISHED:
      return null;
    default:
      throw new IllegalStateException("Unrecognized stage: " + stage);
  }
}

根据两个方法联合分析,getNextGenerator 执行结果:

  • 第一次返回 ResourceCacheGenerator
  • 第二次返回 DataCacheGenerator
  • 第三次的返回取决于 getNextStage 方法中 case DATA_CACHE返回 Stage.FINISHEDStage.SOURCEgetNextGenerator 方法就返回 SourceGenerator 或者是 null

那其实这三个Generator:ResourceCacheGeneratorDataCacheGeneratorSourceGenerator,都是实现自DataFetcherGenerator接口,目的是为了从不同的Generator中加载资源,如果加载成功,就将资源返回,如果加载不成功,就继续找下一个Generator加载

不管是从哪个Generator加载数据,都是通过刚才的while语句中的startNext来执行,不同的Generator内部实现不一样,但是都大同小异,都是加载数据,加载结果是通过一个叫做FetcherReadyCallback的接口进行回调,这里随便看一个,DataCacheGeneratorstartNext

public boolean startNext() {
  // ...
  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
    loadData =
        modelLoader.buildLoadData(
            cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
    if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
      started = true;
  // 加载资源重点代码
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

加载资源的重点代码是:loadData.fetcher.loadData(helper.getPriority(), this)

loadData.fetcher 获得的是一个DataFetcherDataFetcher是获取资源的接口,所以loadData具体的工作其实是交给它的实现类来完成,它的实现类有HttpUrlFetcherLocalUriFetcherFileFetcherAssetPathFetcher等等,用于支持各种资源的获取。

至于加载的细节,这里举两个例子:

  1. 从http url加载
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    callback.onDataReady(result);
  } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to load data for url", e);
    }
    callback.onLoadFailed(e);
  } finally {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
    }
  }
}
  1. 从文件加载
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
  try {
    data = opener.open(file);
    callback.onDataReady(data);
  } catch (FileNotFoundException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to open file", e);
    }
    callback.onLoadFailed(e);
  }
}

加载成功,通过callback.onDataReady(data)进行回调,加载不成功,则通过callback.onLoadFailed(e)进行回调。

以上就是Glide加载图片的主要流程代码分析,因为是省略了大部分的源码和注释,只保留了主线代码,所以还是要自己去跟踪一遍,才更能理解其中的原理,谢谢观看!

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

推荐阅读更多精彩内容