开源框架--Glide源码阅读下

接上半部分开源框架–Glide源码阅读上,我们接着看 Glide 源码的 with 和 load。

3. 源码阅读

3.2 load

上半部分分析知道了with() 方法返回的是 RequestManager,下面看 RequestManager 的 load() 方法,

@Override
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

load() 中主要调用 asDrawable() 方法,创建一个 RequestBuilder 并返回。

3.3 into

(1)资源加载

看 RequestBuilder 中的 into() 方法。

// RequestBuilderjava
  @NonNull
  public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
  	// 代码1 我们使用的into方法中,会调用重载的into方法,且传入的是主线程
    return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
  }

  @NonNull
  @Synthetic
  <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      Executor callbackExecutor) {
    return into(target, targetListener, /*options=*/ this, callbackExecutor);
  }
  
  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
      // 代码2 view不能为null
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

	// 代码3 创建了 Request对象,最终实现是在子类 SingleRequest
    Request request = buildRequest(target, targetListener, options, callbackExecutor);

	// 代码4 这一段是在请求前判断上一个请求是否开始了,未开始,开始上个请求。
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      if (!Preconditions.checkNotNull(previous).isRunning()) {
      	// 代码5 
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    // 代码6 
    requestManager.track(target, request);

    return target;
  }

代码1,表明必须在主线程。
代码2,传入的view不能为空。
代码3,调用了 buildRequest() 方法构建了请求的 Request 对象,最终实现是在子类 SingleRequest。
代码4,在请求前判断上一个请求是否开始了,如果未开始,就开始上个请求。
代码5,后面看
代码6,在 RequestManager 的 track() 方法中,调用了 RequestTracker 的 runRequest() 方法

// RequestManager.java
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
	// 代码7 将 view 存放到一个弱引用的集合中
    targetTracker.track(target);
    // 代码8 
    requestTracker.runRequest(request);
  }

代码8,重点看一下 RequestTracker 的 runRequest() 方法

// RequestTracker.java
  private final Set<Request> requests =
      Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
  private final List<Request> pendingRequests = new ArrayList<>();

  private boolean isPaused;

  /** Starts tracking the given request. */
  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
  }

这里有两个队列 requests 执行队列,pendingRequests 等待队列。先将 request 添加到执行队列,然后判断当前是否是暂停状态,不是的话,就开始执行请求。否则从执行请求中移除,添加到等待队列。

看到这里暂停一下,返回看代码5,看 SingleRequest 的 begin() 方法,

// SingleRequest.java
  @Override
  public void begin() {
    synchronized (requestLock) {
      ... 省略其它代码
      // 判断用户是否设置了宽和高
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        target.getSize(this);
      }
      ... 
    }
  }

重点看这几行代码,这里判断用户是否设置了宽和高,没有设置的话就会自动测量 getSize(),测量保存宽和高后,又会回到这里调用 onSizeReady()。

// SingleRequest.java
@Override
  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);
              ...
    }
  }

在 onSizeReady() 中调用了 engine.load(),

// Engine.java
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;

    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);
      }
    }

    // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
    // deadlock.
    cb.onResourceReady(
        memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
    return null;
  }

这里的 key 是,例如一个 Activity 上有多个 ImageView,Glide 查询这张图片的时候,不是直接查询这张图片,而是查询活动缓存(运行时缓存),如果能查询到直接展示。查询不到,再去查询内存缓存(运行时缓存),查询的依据就是这个 key。

接着看是怎么从缓存中加载数据的。

// Engine.java
 @Nullable
  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;
  }

活动缓存 loadFromActiveResources(key),内存缓存 loadFromCache(key)。

为什么搞两个缓存?
内存缓存内部是用Lru管理,当添加元素时超过Lru的maxSize时,就会移除最早添加的,假如我们正在使用这个将要被移除的数据,这里就有问题了。所以弄了活动缓存,来保存当前正在使用的数据。

后面再看缓存机制。

回到 engine.load() 方法,看没有缓存的情况,
当活动缓存和内存缓存都没有记录时,会调用 waitForExistingOrStartNewJob(),

 private <R> LoadStatus waitForExistingOrStartNewJob(
      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,
      EngineKey key,
      long startTime) {

	// // 查找有没有正在运行的任务
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

调用 jobs.get() 方法,先去检测当前任务有没有在执行。

这里的 EnginJob 是线程池的大管家, DecodeJob 是 Runnable。既然 DecodeJob 是 Runnable,当调用 engineJob.start(decodeJob) 方法时,看一下 DecodeJob 的 run 方法,在 run() 调用了 runWrapped(),

// DecodeJob.java
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);
    }
  }

  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);
    }
  }

  private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled
        && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
  }

当什么都没有配置时, runReason 默认是 INITIALIZE,执行 getNextGenerator() 的 SOURCE 这里,返回SourceGenerator。
在 runGenerators() 方法中,执行 SourceGenerator.startNext(),

// SourceGenerator.java
 @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
    // 主线流程代码
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        startNextLoad(loadData);
      }
    }
    return started;
  }

在 SourceGenerator 的 startNext() 中调用了 getLoadData() 方法,

List<LoadData<?>> getLoadData() {
    if (!isLoadDataSet) {
      isLoadDataSet = true;
      loadData.clear();
      List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
      //noinspection ForLoopReplaceableByForEach to improve perf
      for (int i = 0, size = modelLoaders.size(); i < size; i++) {
        ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
        LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
        if (current != null) {
          loadData.add(current);
        }
      }
    }
    return loadData;
  }

在 DecodeHelper 的 getLoadData() 方法中,调用了 接口 ModelLoader 的 buildLoadData() 方法,那么这个方法的实现是在哪个类?
这里在 Glide 的构造函数中有实现,在构造函数中,注册机中匹配了 GlideUrl,是 HttpGlideUrlLoader。

  Glide(
      @NonNull Context context,
      @NonNull Engine engine,
      @NonNull MemoryCache memoryCache,
      @NonNull BitmapPool bitmapPool,
      @NonNull ArrayPool arrayPool,
      @NonNull RequestManagerRetriever requestManagerRetriever,
      @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
      int logLevel,
      @NonNull RequestOptionsFactory defaultRequestOptionsFactory,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NonNull List<RequestListener<Object>> defaultRequestListeners,
      GlideExperiments experiments) {
      ...
       .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
	...
}

所以我们看一下 HttpGlideUrlLoader 类的 buildLoadData() 方法。

 @Override
  public LoadData<InputStream> buildLoadData(
      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
    // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
    // spent parsing urls.
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

在 HttpGlideUrlLoader 类的 buildLoadData() 方法中创建了 HttpUrlFetcher 对象。HttpUrlFetcher 才是 Glide 网络请求的地方,利用 HttpURLConnection 请求网络,根据网络相关知识知道底层是 socket,返回 InputStream。

// HttpUrlFetcher.java
 @Override
  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));
      }
    }
  }
// SourceGenerator.java
 private void startNextLoad(final LoadData<?> toStart) {
    loadData.fetcher.loadData(
        helper.getPriority(),
        new DataCallback<Object>() {
          @Override
          public void onDataReady(@Nullable Object data) {
            if (isCurrentRequest(toStart)) {
              onDataReadyInternal(toStart, data);
            }
          }

          @Override
          public void onLoadFailed(@NonNull Exception e) {
            if (isCurrentRequest(toStart)) {
              onLoadFailedInternal(toStart, e);
            }
          }
        });
  }

在 HttpUrlFetcher 的 loadData() 方法中调用 loadDataWithRedirects(),利用 HttpURLConnection 进行网络请求,返回 InputStream,然后通过 onDataReady() 接口回调给 SourceGenerator,再一层层往回调。

(2)图片解码

先是回调到 SourceGenerator 的 onDataReady(),然后调用 onDataReadyInternal(toStart, data),

 @Synthetic
  void onDataReadyInternal(LoadData<?> loadData, Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(
          loadData.sourceKey,
          data,
          loadData.fetcher,
          loadData.fetcher.getDataSource(),
          originalKey);
    }
  }

再回调到 DecodeJob 的 onDataFetcherReady() 方法

// DecodeJob.java 
 @Override
  public void onDataFetcherReady(
      Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);

    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
      // 主线
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }
 private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey(
          "Retrieved data",
          startFetchTime,
          "data: "
              + currentData
              + ", cache key: "
              + currentSourceKey
              + ", fetcher: "
              + currentFetcher);
    }
    Resource<R> resource = null;
    try {
    // 主线
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
    } else {
      runGenerators();
    }
  }
  private <Data> Resource<R> decodeFromData(
      DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      long startTime = LogTime.getLogTime();
      // 主线
      Resource<R> result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      return result;
    } finally {
      fetcher.cleanup();
    }
  }

  @SuppressWarnings("unchecked")
  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    // 主线
    return runLoadPath(data, dataSource, path);
  }
  private <Data, ResourceType> Resource<R> runLoadPath(
      Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)
      throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      // ResourceType in DecodeCallback below is required for compilation to work with gradle.
      // 主线
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

调用 decodeFromRetrievedData(),再调用 decodeFromData(),再调用 decodeFromFetcher(),再调用 runLoadPath(),然后调用 LoadPath 的 load() 方法。

// LoadPath.java
public Resource<Transcode> load(
      DataRewinder<Data> rewinder,
      @NonNull Options options,
      int width,
      int height,
      DecodePath.DecodeCallback<ResourceType> decodeCallback)
      throws GlideException {
    List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
    try {
      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
    } finally {
      listPool.release(throwables);
    }
  }
    @NonNull
  private Resource<ResourceType> decodeResourceWithList(
      DataRewinder<DataType> rewinder,
      int width,
      int height,
      @NonNull Options options,
      List<Throwable> exceptions)
      throws GlideException {
    Resource<ResourceType> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
      try {
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          // 主线
          result = decoder.decode(data, width, height, options);
        }
        // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
        // instead log and continue. See #2406 for an example.
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Failed to decode data for " + decoder, e);
        }
        exceptions.add(e);
      }

      if (result != null) {
        break;
      }
    }

    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }
    return result;
  }

在 LoadPath 的 load() 方法中调用 decodeResourceWithList(),然后又调用 decoder.decode(),

// InputStreamBitmapImageDecoderResourceDecoder
  @Override
  public Resource<Bitmap> decode(
      @NonNull InputStream stream, int width, int height, @NonNull Options options)
      throws IOException {
    ByteBuffer buffer = ByteBufferUtil.fromStream(stream);
    Source source = ImageDecoder.createSource(buffer);
    return wrapped.decode(source, width, height, options);
  }

decode() 接口是在 InputStreamBitmapImageDecoderResourceDecoder 实现的,将网络请求的结果 InputStream 转成了 Bitmap。
在解码成功后,回调 DecodeJob 的 notifyEncodeAndRelease() 接口回调到 Engine 通知完成,

// Engine.java
 @Override
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null && resource.isMemoryCacheable()) {
      activeResources.activate(key, resource);
    }

    jobs.removeIfCurrent(key, engineJob);
  }
// ActiveResources.java
  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

在 onEnginJobComplete() 中调用 activeResources.activate() 将资源存入活动缓存中,这里是弱引用。

(3)图片显示

解码完成后,接着看是怎么将 bitmap 设置给 ImageView 的。
在解码完成后回调到 SingleRequest 的 onResourceReady() 方法,

// SingleRequest.java
 private void onResourceReady(
      Resource<R> resource, R result, DataSource dataSource, boolean isAlternateCacheKey) {
    ...

    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener<R> listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);
        // 主线流程
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

在该方法中,通过 onResourceReady() 将数据回调给我们的 target,

// ImageViewTarget.class
 @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }
 private void setResourceInternal(@Nullable Z resource) {
    // Order matters here. Set the resource first to make sure that the Drawable has a valid and
    // non-null Callback before starting it.
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
protected abstract void setResource(@Nullable Z resource);

在 onResourceReady() 方法中调用 setResourceInternal(resource) 方法,然后调用 setResource(resource) 方法。setResource() 是抽象方法,以 BitmapImageViewTarget 子类为例看一下,

//BitmapImageViewTarget.java
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
  // Public API.
  @SuppressWarnings("WeakerAccess")
  public BitmapImageViewTarget(ImageView view) {
    super(view);
  }

  /** @deprecated Use {@link #waitForLayout()} instead. */
  // Public API.
  @SuppressWarnings({"unused", "deprecation"})
  @Deprecated
  public BitmapImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  /**
   * Sets the {@link android.graphics.Bitmap} on the view using {@link
   * android.widget.ImageView#setImageBitmap(android.graphics.Bitmap)}.
   *
   * @param resource The bitmap to display.
   */
  @Override
  protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
  }
}

最终将数据给到我们的 view 显示图片。

(4)缓存机制

前面我们在看 Glide 查询图片的时候,不是直接查询图片,而是先查询活动缓存,如果能查询到直接展示。查询不到,再去查询内存缓存。

活动缓存 ActiveResource:

final class ActiveResources {
...
 final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
 private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
 @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }
  ...
  }

活动缓存,通过 WeakReference + 引用队列 + Map 哈希表的组合实现的,其具体代码实现原理是通过引用计数算法,记录当前界面中的活跃的资源应用,引用了一次,就添加1,没有引用了,就减少1,如果引用数为0时,就用数据结构中去除,并把资源放入内存缓存中。

为什么需要活动缓存呢?

如果图片正在被使用,则这个图片会被Glide加入到这个活动缓存中。

什么是正在被使用呢?比如进入某Activity展示一张图片,展示的时候会把这个图片加入到活动缓存中,退出Activity时,会从活动缓存中移除,移除的图片资源不会立即销毁,而是加入到内存缓存。

如果在加载图片的时候,发现活动缓存中没有,会在内存缓存进行查找。资源存在的话,Glide会把对应的资源从内存缓存中移除,加入到活动缓存中去。这样可以避免因为达到内存缓存最大值或者系统内存压力导致的内存缓存清理,从而释放掉活动资源中的图片(recycle)。这句话是什么意思?如果一张图片资源同时被活动缓存和内存缓存所引用的话,当内存缓存的数量达到所设定的最大值后,会根据LRU算法(在内存缓存中会介绍)移除图片资源,进行内存释放,如果所释放的图片资源也存在于活动资源的话,因为此图片资源正在使用,被释放后就会引起空指针异常的错误。

内存缓存 Memory Cache

public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
  private ResourceRemovedListener listener;

  /**
   * Constructor for LruResourceCache.
   *
   * @param size The maximum size in bytes the in memory cache can use.
   */
  public LruResourceCache(long size) {
    super(size);
  }

  @Override
  public void setResourceRemovedListener(@NonNull ResourceRemovedListener listener) {
    this.listener = listener;
  }

  @Override
  protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
    if (listener != null && item != null) {
      listener.onResourceRemoved(item);
    }
  }

  @Override
  protected int getSize(@Nullable Resource<?> item) {
    if (item == null) {
      return super.getSize(null);
    } else {
      return item.getSize();
    }
  }

  @SuppressLint("InlinedApi")
  @Override
  public void trimMemory(int level) {
    if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
      // Entering list of cached background apps
      // Evict our entire bitmap cache
      clearMemory();
    } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
        || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
      // The app's UI is no longer visible, or app is in the foreground but system is running
      // critically low on memory
      // Evict oldest half of our bitmap cache
      trimToSize(getMaxSize() / 2);
    }
  }
}

内存缓存默认使用LRU(缓存淘汰算法/最近最少使用算法),当资源从活动资源移除的时候,会加入此缓存。使用图片的时候会主动从此缓存移除,加入活动资源。

在LruCache的构造方法中,需要指定maxSize,这个是内存缓存的大小。

我们可以看见LruCache中封装了LinkedHashMap,并且在创建LinkedHashMap对象的时候,在构造器的第三个参数设置了true,表示会根据最近最少使用的算法进行排序。

磁盘缓存 DiskLruCache

当在内存缓存中,没有找到图片资源时,Glide会在磁盘中查找相关的图片资源,关于磁盘存储的实现,其实是使用了另外一个开源框架:DiskLruCache,Glide通过在 SourceGenerator 的 cacheData 函数执行磁盘缓存的查询。

// SourceGenerator.java
 private void cacheData(Object dataToCache) {
        long startTime = LogTime.getLogTime();

        try {
            Encoder<Object> encoder = this.helper.getSourceEncoder(dataToCache);
            DataCacheWriter<Object> writer = new DataCacheWriter(encoder, dataToCache, this.helper.getOptions());
            this.originalKey = new DataCacheKey(this.loadData.sourceKey, this.helper.getSignature());
            this.helper.getDiskCache().put(this.originalKey, writer);
            if (Log.isLoggable("SourceGenerator", 2)) {
                Log.v("SourceGenerator", "Finished encoding source to cache, key: " + this.originalKey + ", data: " + dataToCache + ", encoder: " + encoder + ", duration: " + LogTime.getElapsedMillis(startTime));
            }
        } finally {
            this.loadData.fetcher.cleanup();
        }

        this.sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(this.loadData.sourceKey), this.helper, this);
    }

缓存机制小结:

Glide 使用了三级缓存机制,包括活动缓存、内存缓存和磁盘缓存。

  1. 内存缓存:
    • Glide 使用 LruCache 作为内存缓存,缓存解码后的图片。
    • 内存缓存分为活动缓存(Active Resources)和内存缓存(Memory Cache)。
    • 活动缓存存储当前正在使用的图片,内存缓存存储最近使用过的图片。
  2. 磁盘缓存:
    • Glide 使用 DiskLruCache 作为磁盘缓存,缓存原始图片数据和转换后的图片数据。
    • 磁盘缓存分为原始数据缓存(Resource Cache)和转换数据缓存(Data Cache)。

4. 其它

4.1 ​Glide如何清除缓存?

Glide提供了clearMemory() 和 clearDiskCache() 方法来清除内存缓存和磁盘缓存。

4.2 Glide如何加载大图?

Glide支持加载大图,并且可以根据ImageView的大小自动调整图片的分辨率,避免内存溢出。如果需要加载超大图片,可以使用Glide.with(context).asBitmap().load(imageUrl).into(imageView)
来避免自动解码为Drawable,从而减少内存占用。

4.3 Glide 线程池

线程池主要有这几种:

  • AnimationExecutor:加载动画相关,禁止访问网络;如果CPU核心数大于4,就是2个线程,否则是一个线程,核心线程数和最大线程数相同;
  • diskCacheExecutor:从磁盘加载图片,禁止访问网络;线程数为1,核心线程数和最大线程数相同;
  • sourceExecutor:可以访问网络,线程数跟CPU核心数有关,不大于4,核心线程数和最大线程数相同;
  • newUnlimitedSourceExecutor:用于网络请求图片,没有核心线程,只有非核心线程,类似CacheThreadPoll;

在加载图片时,Glide首先创建了一个DecodeJob对象(实现了Runnable),运行在子线程以加载、变化及缓存图片。然后,创建了一个EngineJob实现并注册了DecodeJob的回调接口,这样就可以得到处理后的图片,并将图片展示任务通过Handler发送到了主线程。
EngineJob 线程池调度器,发起请求是在DecodeJob的run方法中,DecodeJob实现了Runnable接口;

4.4 使用 Glide,为什么会出现内存溢出问题?

尽量在 with 的时候,传入有生命周期的作用域(非Application作用域),尽量避免使用 Application 作用域,因为Application 作用域不会对页面绑定生命周期机制,导致回收不及时,无法执行释放操作等。

4.5 Glide内存管理?

(1)OOM 防止

  • Glide 图片采样:Glide 针对较大的图片,会根据当前 UI 的显示大小与实际大小的比例,进行采样计算从而减小图片在内存中的占用。一般而言图片的大小=图片宽 * 图片高 * 每个像素占用的字节数。对于资源文件夹下的图片:图片的高 = 原图高 * (设备的 dpi / 目录对应的 dpi)

  • onLowMemory/onTrimMemory:当内存过低的时候会调用 onLowMemory(),在 onlowMemory() 中 Glide 会将一些缓存的内存进行清除,方便进行内存回收。当 onTrimMemory() 被调用的时候,如果 level 是系统资源紧张,Glide. 会将 Lru 缓存和 Bitmap 重用池相关的内容进行回收,其他的原因调用 onTrimMemory(),Glide 会将缓存的内容减小到配置缓存最大内容的 1/2。

  • 弱引用:Glide 通过 RequestManager 管理图片请求,而 RequestManager 内部是通过 RequestTracker 和 TargetTracker 来完成的。它们持有的方式都是弱引用。

  • 生命周期绑定:减少加载到内存的图片大小,及时清理不必要的对象引用,从而减少 OOM 的概率。

(2)内存抖动的处理:

  • BitmapPool 对 Bitmap 进行对象重用。在对图片进行解码的时候通过设置 BitmapFactory.Options.inBitmap() 来达到内存重用的目的。

  • Glide 通过重用池技术,将一些常用的对应进行池化,比如图片加载相关的 EnginJob和DecodeJob 等一下需要大量重复使用创建的对象,通过对象重用池进行对象重用。

4.6 Glide如何加载不同类型的资源

Glide 通过 RequestManager.as() 方法确定当前请求 Target 最终需要的资源类型。通过 load 方法确定需要加载的 model 资源类型,资源的加载过程经历 ModelLoader 的 model 加载匹配,解码器解码,转码器的转换,这几个过程构建一个 LoadPath。而每一个 LoadPath 又包含很多的 DecodePath,它的主要作用是将 ModelLoader 加载出来的数据进行解码,转换。Glide 会遍历所有可能解析出对应数据的 LoadPath 直到数据真正解析成功。

4.7 Glide 如何加载GIf

将 Gif 解码成多张图片进行无限轮播,每帧切换都是一次图片加载请求,再加载到新的一帧数据之后会对旧的一帧数据进行清除,然后再继续下一帧数据的加载请求,以此类推,使用 Handler 发送一个延迟消息实现连续播放。

4.8 滚动卡顿问题?

不是Glide图片加载的问题,因为图片加载完一次后,就完成了,卡顿是因为不断地request。
屏幕停止滚动开始加载图片,手指在屏幕上或观星滑动时停止加载图片。

总结

Glide是一个功能强大、性能优异的图片加载库,其核心原理包括缓存机制、生命周期管理、图片解码与转换等。

它的整体流程可以概括为:

  1. 先进行 Glide 构建;
  2. 构建完成后,给每一个 RequestManager 绑定一个空白的 Fragment 来管理生命周期;
  3. 然后构建 Request 对象;
  4. 在 Request 对象(包括请求的宽、高、采样等)构建完后,开始进行资源请求,在请求之前先检测缓存,Engine 缓存机制的检测中会先检测活动缓存 ActiveResoure,然后再检测内存缓存 LruResoureCache。如果命中上面两级内存缓存,就会直接回调返回;
  5. 如果内存缓存没有,在 EngineJob 构建一个新的异步任务;
  6. 在执行 Request 之前,先检测 DiskCache 中有没有本地磁盘缓存;
  7. 如果没有磁盘缓存,就通过网络进行请求,返回输入流 InputStream;
  8. 解析输入流 InputStream 进行采样压缩,最终拿到 Bitmap;
  9. 对 Bitmap 进行转换成 Drawable;
  10. 构建磁盘缓存 DiskCache;
  11. 构建内存缓存 ActiveResoures;
  12. 最后回到 ImageViewTarget(子类 DrawableImageViewTarget)显示图片。

你可能感兴趣的:(开源,glide)