Glide源码解析2:数据加载

问题

上文已经简单解释了一个Request的生成过程,本文目标如下

  1. Glide在load中传入的数据最后是如何转成显示的Drawable的
  2. 传入的数据如何在ImageView上显示

注意由于代码分支过多,会删减一些源码便于理解

流程解析

Request.begin

书接上文,SingleRequest的begin被调用之后

//SingleRequest.java
public void begin() {
    //这里的方法按照注释来说是如在我们调用了notifyDataSetChanged之后,即开启了同一个request,就可以直接使用之前所加载过的resource
    if (status == Status.COMPLETE) {
        onResourceReady(resource, DataSource.MEMORY_CACHE);
        return;
    }
    
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        //我们所设置的width和hegith在合法范围内,则表示size已经准备就绪
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        //如果不合法,这里会通过ViewTarget去拿如ImageView的size
        target.getSize(this);
    }
    
    //在执行完上面的onSizeReady后正常就是RUNNING状态,此时会在这里告诉ViewTarget去直接使用我们设置的加载中的Drawable
    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
      && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
}
  
public void onSizeReady(int width, int height) {
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    //把当前的status状态由WAITING_FOR_SIZE改为RUNNING
    status = Status.RUNNING;
    
    //如果设置了sizeMultiplier,则会按照我们的要求对宽高进行缩放
    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
    
    //engine在Glide实例初始化时创建,其load方法
    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);
}

上面的SingleRequest代码总结一下就是

  1. 一个Request启动后,会先计算得到它最终所呈现的宽高,后调用Engine的load方法继续加载
  2. 设置的placeholder加载中图片会在这一步呈现到界面上
//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) {

    //通过传入的model、宽高、Drawable.class、参数等合并成一个key
    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) {
      //这里是从内存缓存里去找是否已经存了Resource,我们在本问之后会说这里
      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);
      }
    }

    //若从缓存中拿到了,直接回调onResourceReady,这里的cb是SingleRequest
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
}

上面的代码看着就很恐怖的感觉,但是大部分都是参数列表,其实就是先从缓存里找有没有现成的资源,没有就继续调用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) {

    //jobs可以理解成存储EngineJob的列表,先看有没有现成的同样请求的EngineJob
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    //第一次请求肯定是没有的,我们就不管这个if内部了
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      return new LoadStatus(cb, current);
    }

    //创建了一个EngineJob<Drawable>
    //这个对象里含有一堆线程池,主要用来给各个任务提供线程池去加载,并统一由它接受资源返回,并回调出来
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    //创建了一个DecodeJob<Drawable>
    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里的线程池启动一个decodeJob
    engineJob.start(decodeJob);

    return new LoadStatus(cb, engineJob);
  }
  
  //EngineJob.java
  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    //如果我们的任务需要从硬盘中拉取数据,则使用diskCacheExecutor线程池
    GlideExecutor executor =
        decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
  
  //获取应该使用哪个线程池进行调用
    private GlideExecutor getActiveSourceExecutor() {
        return useUnlimitedSourceGeneratorPool
            ? sourceUnlimitedExecutor
            : (useAnimationPool ? animationExecutor : sourceExecutor);
    }

上述代码其实就是通过EngineJob启动DecodeJob,EngineJob中含有的几个线程池简单介绍一下

  1. animationExecutor gif加载帧使用的线程池,在GifFrameLoader(Glide的gif相关加载类)中有设置useAnimationPool为true的情况,我们在使用时也可以通过类似方法进行指定,其线程数与cpu相关
  2. sourceUnlimitedExecutor Glide源码内没有使用的地方,是一个无限容量的线程池,且没有核心线程,网上理解是用于同时加载超大量图片的情况
  3. diskCacheExecutor 硬盘相关缓存操作的线程池,只有一个线程,保证文件串行操作
  4. sourceExecutor 取可用cpu数为核心线程,最大值为4,主要是这个线程池用于进行DecodeJob的任务

这里稍微提一下Glide中是如何指定使用animationExecutor加载DecodeJob的

  //GifFrameLoader.java
  生成一个gif帧的加载请求
  private static RequestBuilder<Bitmap> getRequestBuilder(
      RequestManager requestManager, int width, int height) {
    return requestManager
        .asBitmap()
        .apply(
            diskCacheStrategyOf(DiskCacheStrategy.NONE)
                .useAnimationPool(true)
                .skipMemoryCache(true)
                .override(width, height));
  }

既然是通过EncodeJob里的线程池来启动DecodeJob,那么DecodeJob其实就是一个Runnable,注意从下面的方法开始,就不再是在我们调用的ui线程执行了

我们来看一下它的run方法

//DecodeJob.java
    public void run() {
        //省略其它代码
        runWrapped();
    }
  
    private void runWrapped() {
        //runReason默认是INITIALIZE状态
        switch (runReason) {
          case INITIALIZE:  
            //当前的步骤从INITIALIZE跳到下一步
            stage = getNextStage(Stage.INITIALIZE);
            //根据当前步骤获取对应的Generator,可以理解为接下来要执行的任务的封装
            currentGenerator = getNextGenerator();
            //执行currentGenerator的startNext方法
            runGenerators();
            break;
          case SWITCH_TO_SOURCE_SERVICE:
            runGenerators();
            break;
          case DECODE_DATA:
            decodeFromRetrievedData();
            break;
          default:
            throw new IllegalStateException("Unrecognized run reason: " + runReason);
        }
    }
  
    //根据当前状态获取下一步
    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:
            return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
          case SOURCE:
          case FINISHED:
            return Stage.FINISHED;
          default:
            throw new IllegalArgumentException("Unrecognized stage: " + current);
        }
    }

获取下一步的代码比较容易理解,下面总结一下

  1. 我们设置了可以缓存最终数据Resource,则通过ResourceCacheGenerator获取
  2. 有设置缓存源数据DATA,则通过DataCacheGenerator获取
  3. 没有缓存,通过SourceGenerator获取

我们当前是无缓存的,所以currentGenerator为SourceGenerator,执行它的startNext方法

//SourceGenerator.java
@Override
public boolean startNext() {
    //dataToCache默认没有值,只在数据解码完成后由onDataReady方法赋值
    //所以这个条件的代码时和缓存相关的,我们暂时不去管,之后的文章说缓存时再去考虑
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
    
    //这里的sourceCacheGenerator与上面一样也是与缓存相关,暂时不用管
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;
    
    loadData = null;
    boolean started = false;
    //这里的意思是遍历List<LoadData>,用每个loadData对象来尝试解析传入的model数据
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
}

上面的代码就是SourceGenerator的执行过程:遍历List,通过loadData对象去解析model数据。而loadData这个对象就是解析model数据的关键

LoadData的获取

我们先看一下LoadData如何获取的,它通过helper.getLoadData得到列表,而helper则是在DecodeJob初始化时实例化的decodeHelper对象,且每个DecodeJob里都带有一个decodeHelper对象。

//DecodeHelper.java
//此方法的作用
//model->获取List<ModelLoader>->获取List<LoaderData>
List<LoadData<?>> getLoadData() {
    //每个DecodeHelper只会初始化一次
    if (!isLoadDataSet) {
      isLoadDataSet = true;
      loadData.clear();
      //注意这里,glideContext对于Glide就类似ApplicationContext对于Application一样的存在
      //所以这里就是到整个Glide共享的Registry中通过model(我们传入的数据)获取可用的modelLoader列表
      List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
      for (int i = 0, size = modelLoaders.size(); i < size; i++) {
        ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
        //通过modelLoader去获取LoadData
        LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
        if (current != null) {
          loadData.add(current);
        }
      }
    }
    return loadData;
}

上面的getModelLoaders(model)就不把方法贴出来了,内容其实比较单一,就是通过model的类型去找对应的ModelLoader,且限定条件为ModelLoader的handles方法返回true才可以。

而ModelLoader的工厂方法列表都在Glide初始化时会创建,下面贴出一小部分

//Glide.java
//这是Glide的构造函数
Glide(
      @NonNull Context context,
      @NonNull Engine engine,
      //..省略一大堆参数
      int minHardwareDimension) {
      registry
            //输入为File,输出为InputStream的modelLoader
          .append(File.class, InputStream.class, new FileLoader.StreamFactory())
            //输入为resId,输出同上
          .append(int.class, InputStream.class, resourceLoaderStreamFactory)
          //输入为url,输出同上
          .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
          //输入为File,输出为ByteBuffer
          .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
          //输入为String,输出为InputStream
          .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
          //...省略一大堆append
  }

从上面可推,当我们输入为一个string类型的网址时,得到就是所有输入为String的modelLoader;当我们输入为资源id时,得到的就是所有输入为int的modelLoader。这就是modelLoader的来源

回到本节的开始,获取了List后会调用每个ModelLoader的buildLoadData方法

List<LoadData<?>> getLoadData() {
      for (int i = 0, size = modelLoaders.size(); i < size; i++) {
        ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
        //调用每个modelLoader的buildLoadData
        LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
        if (current != null) {
          loadData.add(current);
        }
      }
    }
}

这里我们以传入了一个String类型的图片网络地址为例,入参为String的modelLoader在glide初始化时加入的有

  1. StringLoader
  2. DataUrlLoader

而他们的handles方法分别返回了

//DataUrlLoader.java
public boolean handles(@NonNull Model model) {
    //它只处理String开头为data:image的字符串
    return model.toString().startsWith("data:image");
}

//StringLoader.java
@Override
public boolean handles(@NonNull String model) {
    //我都处理
    return true;
}

所以对于我们的实例情况,最后List只有StringLoader一个符合要求,但是打断点确认这里其实是有三个StringLoader对象的,到Glide初始化的地方确认一下刚刚好是三个,输出分别是InputStream、ParcelFileDescriptor、AssetFileDescriptor
断点

        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(
            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
//StringLoader.java
public LoadData<Data> buildLoadData(
      @NonNull String model, int width, int height, @NonNull Options options) {
    //把model转换成uri对象
    Uri uri = parseUri(model);
    if (uri == null || !uriLoader.handles(uri)) {
      return null;
    }
    //调用uriLoader的buildLoadData方法,上面是三个StringLoader的uriLoader都不同
    return uriLoader.buildLoadData(uri, width, height, options);
}

对于uriLoader来说,其实是与StringLoader同级的,这里相当于StringLoader先把String转化为uri,后续再转接到其它输入为Uri的ModelLoader来处理,这里不再赘述,看下图即可,从左往右为输入,从右往左为输出

graph LR
StringLoader-->HttpUrlLoader
HttpUrlLoader-->HttpGlideUrlLoader
HttpGlideUrlLoader-->HttpUrlLoader
HttpUrlLoader-->StringLoader

所以最后其实是调用到HttpGlideUrlLoader中进行model处理

//HttpGlideUrlLoader.java
public LoadData<InputStream> buildLoadData(
      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
    GlideUrl url = model;
    //LoadData传入了model与HttpUrlFetcher对象
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

对于LoadData而言,它只是一个存储类而已,存储这个ModelLoader的数据封装工具(对于实例来说就是HttpUrlFetcher)

ModelLoader的执行

回到SourceGenerator中调用LoadData的位置

//SourceGenerator.java
public boolean startNext() {
    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;
        //慈湖会调用fetcher的loadData方法
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

这里会调用到HttpUrlFetcher中

//HttpUrlFetcher.java
public void loadData(
  @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    //通过loadData拿到最终的生成类InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    //回调到SourceGenerator的onDataReady中
      callback.onDataReady(result);
}

private InputStream loadDataWithRedirects(
  URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
    //http的重定向相关,最多只支持5次重定向
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      //如果上一次重定向和这一次的url相同,那么就是死循环了
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");
        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }
    
    //眼角有泪流出,终于看到了熟悉的方法HttpUrlConnection
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);
    
    urlConnection.setInstanceFollowRedirects(false);
    

    //开启连接
    urlConnection.connect();
    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
        //通过connection获取InputStream,和我们使用httpUrlConnection的方式相同,这里就不做解释了
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
        //重定向的情况
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
      // to disconnecting the url connection below. See #2352.
      cleanup();
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == INVALID_STATUS_CODE) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
}

由以上内容得知

  1. 我们的网络图片地址其实是通过SourceGenerator在HttpUrlConnect转换成InputStream的,这个InputStream就相当于我们数据的中间类
  2. 我们load进入的数据都可以通过查看输入类型找到对应的ModelLoader,从而来确认Glide内部是如何实现的(如传入的resId则通过ResourceLoader并转给UriLoader.StreamFactory来处理)
  3. 最终要展示到ImageView上肯定还是得通过Drawable来实现的,我们还需要找到InputStream如何转化为可用的Drawable
graph LR
Model-->InputStream
InputStream-->Drawable

InputStream后续如何处理

InputStream获取成功后,会回调到SourceGenerator的onDataReady方法

//SourceGenerator.java
public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
        //硬盘缓存相关
      dataToCache = data;
      //reschedule逻辑比较乱,但是捋顺了会发现作用其实只是把当前要做的任务通过EncodeJob切换到正确的线程上去
      //而这里需要做的任务就是进行硬盘缓存,缓存相关逻辑会专门写一篇文章处理
      cb.reschedule();
    } else {
        //我们先不管上面的逻辑,主要跟踪InputStream
        //这里的cb是DecodeJob
      cb.onDataFetcherReady(
          loadData.sourceKey,
          data,
          loadData.fetcher,
          loadData.fetcher.getDataSource(),
          originalKey);
    }
  }

接下来就是正式开始解码数据,即把InputStream转化为我们需要的内容(Drawable)

//DecodeJob.java
@Override
public void onDataFetcherReady(
  Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    if (Thread.currentThread() != currentThread) {
      //如上面的代码块所说,切换到正确的decode线程去
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      try {
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
}


private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    
    //这里的resource不是我们自己的resource,只是Glide对最终生成类的封装
    resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    if (resource != null) {
        //获得到了resource,我们的Drawable也包含在里面,这样就可以一直往外回调给ImageView设置Target了
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
}

private <Data> Resource<R> decodeFromData(
  DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
    Resource<R> result = decodeFromFetcher(data, dataSource);
    return result;
}

private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
      //又是这个decodeHelper...之前的LoadData也是从他们这里获取的,不过传入的key变成了我们最后要生成的Drawable.class,那么这里的LoadPath作用也会与LoadData类似
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    //LoadPath的执行
    return runLoadPath(data, dataSource, path);
  }

又要到decodeHelper中去处理类型转换相关的内容了,注意这里用断点的可以很方便的理解获取LoadPath的过程

  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
      @NonNull Class<Data> dataClass,
      @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {
      //先从缓存里找,我们没有缓存就不管
    LoadPath<Data, TResource, Transcode> result =
        loadPathCache.get(dataClass, resourceClass, transcodeClass);
    if (loadPathCache.isEmptyLoadPath(result)) {
        //这里并不是判空,只是对特殊的LoadPath进行验证,不用管
      return null;
    } else if (result == null) {
    //与之前的List<LoadData>一样,这里获取的是List<DecodePath>
      List<DecodePath<Data, TResource, Transcode>> decodePaths =
          getDecodePaths(dataClass, resourceClass, transcodeClass);
      if (decodePaths.isEmpty()) {
        result = null;
      } else {
        result =
            new LoadPath<>(
                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
      }
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    return result;
  }

关键的DecodePath的生成,直接生看源码可能会比较乱,所以这里我们举一个加载网络地址图片到ImageView的例子(不管缓存)

//使用例
        Glide.with(this)
            .load("https://xxxxxx.jpg")
            .diskCacheStrategy(DiskCacheStrategy.NONE)
            .skipMemoryCache(true)
            .into(iv_image)
//按照例子,输入的对象分别为
//dataClass=InputStream.class
//resourceClass=Object.class
//transcodeClass=Drawable.class
private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
  @NonNull Class<Data> dataClass,
  @NonNull Class<TResource> resourceClass,
  @NonNull Class<Transcode> transcodeClass) {
    List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
    List<Class<TResource>> registeredResourceClasses =
        decoderRegistry.getResourceClasses(dataClass, resourceClass);
    
    for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
        
      List<Class<Transcode>> registeredTranscodeClasses =
          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
    
      for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
    
        //通过decoderRegistry获取Decoders列表
        List<ResourceDecoder<Data, TResource>> decoders =
            decoderRegistry.getDecoders(dataClass, registeredResourceClass);
        //通过transcodeRegistry获取Transcoder列表
        ResourceTranscoder<TResource, Transcode> transcoder =
            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
        //通过获取的数据组装成一个DecodePath对象
        DecodePath<Data, TResource, Transcode> path =
            new DecodePath<>(
                dataClass,
                registeredResourceClass,
                registeredTranscodeClass,
                decoders,
                transcoder,
                throwableListPool);
        decodePaths.add(path);
      }
    }
    return decodePaths;
}

上面看起来一大段,其实也是和获取ModelLoader一样,通过输入类型与输出类型找到所需的Decoder,所以我们还是看看Glide初始化有添加那些Decorder

        //
        .append(
            //第一个参数是给Decorder分类用的,比如BUCKERT_BITMAP_DRAWABLE就是统一生成BitmapDrawable对象的Decorder
            Registry.BUCKET_BITMAP_DRAWABLE,
            //输入
            ByteBuffer.class,
            //输出
            BitmapDrawable.class,
            //正宗Decorder对象
            new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            InputStream.class,
            BitmapDrawable.class,
            //这个输入与输出都符合我们的要求
            new BitmapDrawableDecoder<>(resources, streamBitmapDecoder))
        .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            ParcelFileDescriptor.class,
            BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, parcelFileDescriptorVideoDecoder))
        /* GIFs */
        .append(
            Registry.BUCKET_GIF,
            InputStream.class,
            GifDrawable.class,
            new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
        .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)

综上可得

  1. 其实InputStream接下来会被Decorder转化成对应的Drawable对象
  2. LoadPath其实和LoadData一样,保存List用于后续遍历尝试解码

Decorder的处理

Decorder会存在LoadData内,由DecodeJob在解析出InputStream后遍历尝试解码

//Decorder.java
private <Data, ResourceType> Resource<R> runLoadPath(
      Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)
      throws GlideException {
    return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
  }
  
  
private Resource<Transcode> loadWithExceptionList(
  DataRewinder<Data> rewinder,
  @NonNull Options options,
  int width,
  int height,
  DecodePath.DecodeCallback<ResourceType> decodeCallback,
  List<Throwable> exceptions)
  throws GlideException {
    Resource<Transcode> result = null;
    //遍历LoadPath中的DecodePath列表,调用其decode函数处理
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch (GlideException e) {
        exceptions.add(e);
      }
      if (result != null) {
        break;
      }
    }
    
    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }
    
    return result;
}

//DecodePath.java
  public Resource<Transcode> decode(
      DataRewinder<DataType> rewinder,
      int width,
      int height,
      @NonNull Options options,
      DecodeCallback<ResourceType> callback)
      throws GlideException {
      //解码的关键方法decodeResource,按照实例的返回是一个Bitmap
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //解码完成之后回调到DecodeJob的onResourceDecoded
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    //实例会把Bitmap转码成BitmapDrawable
    return transcoder.transcode(transformed, options);
  }

在往里跟方法前先给一个通过断点调试得出的结论

  1. decodeResource会把InputStream转化成Bitmap
  2. trascode方法会把Bitmap转化成BitmapDrawable

所以这里我们分成两步

InputStream–>Bitmap
private Resource<ResourceType> decodeResource(
      DataRewinder<DataType> rewinder, int width, int height, @NonNull Options options)
      throws GlideException {
      return decodeResourceWithList(rewinder, width, height, options, exceptions);
  }
  
private Resource<ResourceType> decodeResourceWithList(
  DataRewinder<DataType> rewinder,
  int width,
  int height,
  @NonNull Options options,
  List<Throwable> exceptions)
  throws GlideException {
    Resource<ResourceType> result = null;
    for (int i = 0, size = decoders.size(); i < size; i++) {
        //遍历Decorder
        ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
        DataType data = rewinder.rewindAndGet();
        //和ModelLoader一样,通过handles判断是否能够处理这个类型的数据
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          //可以就去解码
          result = decoder.decode(data, width, height, options);
        }
        
        //解码成功之后,直接返回,不用再继续遍历了
        if (result != null) {
            break;
        }
    }
    
    //如果result还是null,那么Decorder将会全军覆没,哈哈哈哈哈
    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }
    return result;
}

那么我们其实就可以根据输入为InputStream的Decorder找出对应的Decorder就是我StreamBitmapDecoder哒!从而也可以得知StreamBitmapDecoder的输出就是Bitmap.class

//StreamBitmapDecorder.java
public Resource<Bitmap> decode(
      @NonNull InputStream source, int width, int height, @NonNull Options options)
      throws IOException {

    //这里是对InputStream的一层封装
    final RecyclableBufferedInputStream bufferedStream;
    final boolean ownsBufferedStream;
    if (source instanceof RecyclableBufferedInputStream) {
      bufferedStream = (RecyclableBufferedInputStream) source;
      ownsBufferedStream = false;
    } else {
      bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);
      ownsBufferedStream = true;
    }

    //这里也是对InputStream的一层封装
    ExceptionCatchingInputStream exceptionStream =
        ExceptionCatchingInputStream.obtain(bufferedStream);

    MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
    UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream);
    try {
        //最后其实是通过这个解码的
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally {
      exceptionStream.release();
      if (ownsBufferedStream) {
        bufferedStream.release();
      }
    }
  }

下面就是真正解码成Bitmap的位置!

//Downsampler.java
public Resource<Bitmap> decode(
      InputStream is,
      int requestedWidth,
      int requestedHeight,
      Options options,
      DecodeCallbacks callbacks)
      throws IOException {

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
    //熟悉的类BitmapFactory!!!
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    bitmapFactoryOptions.inTempStorage = bytesForOptions;

    //默认解码发现在新版本已经变成了PREFER_ARGB_8888
    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
    PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE);
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    boolean isHardwareConfigAllowed =
        options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result =
          decodeFromWrappedStreams(
              is,
              bitmapFactoryOptions,
              downsampleStrategy,
              decodeFormat,
              preferredColorSpace,
              isHardwareConfigAllowed,
              requestedWidth,
              requestedHeight,
              fixBitmapToRequestedDimensions,
              callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);
    }
  }
  
private Bitmap decodeFromWrappedStreams(
  InputStream is,
  BitmapFactory.Options options,
  //省略一堆入参
  DecodeCallbacks callbacks)
  throws IOException {
    
    //这里是先得到源数据的宽高,用的是inJustDecodeBounds的方式
    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    
    //获得目标宽高
    int targetWidth =
        requestedWidth == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)
            : requestedWidth;
    int targetHeight =
        requestedHeight == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
            : requestedHeight;
    
    //解码出图片类型,如jpg、png、gif等
    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);

    //...
    
    //从bitmapPool中得到可复用的Bitmap,并存入options.inBitmap
    if (expectedWidth > 0 && expectedHeight > 0) {
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
    }
    
    //注意注意注意啦,关键的解码出Bitmap的地方,里面其实就是调用了BitmapFactory.decodeStream
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
    
    //...
    
    return rotated;
}

所以InputStream其实还是通过BitmapFactory.decodeStream解码出Bitmap的,并且也用到了inJustDecodeBounds、inBitmap等优化方式

Bitmap–>BitmapDrawable

接下来decode结束,到了Transcoder转码器发挥的时候了,还是一样,我们查看Glide初始化时注册的Transcoder,并且也可以找到对应的输入和输出

//Glide.java
        /* Transcoders */
        .register(Bitmap.class, BitmapDrawable.class, new BitmapDrawableTranscoder(resources))
        .register(Bitmap.class, byte[].class, bitmapBytesTranscoder)
        .register(
            Drawable.class,
            byte[].class,
            new DrawableBytesTranscoder(
                bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder))
        .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);

输入为Bitmap,输出为Drawable,选哪个不用我多说了吧

//BitmapDrawableTranscoder.java
public Resource<BitmapDrawable> transcode(
      @NonNull Resource<Bitmap> toTranscode, @NonNull Options options) {
      //这里最终其实就是生成了一个LazyBitmapDrawableResource,就是一个BitmapDrawable的生成类
    return LazyBitmapDrawableResource.obtain(resources, toTranscode);
}

//LazyBitmapDrawableResource.java
public BitmapDrawable get() {
    //它的get方法就可以获取到BitmapDrawable了
    return new BitmapDrawable(resources, bitmapResource.get());
}

展示到ImageView

在此次DecodeJob任务完成之后,会经过如下的调用链
0. DecodeJob#notifyEncodeAndRelease

  1. EncodeJob#onResourceReady
  2. CallResourceReady#run
  3. SingleRequest#onResourceReady
  4. ImageViewTarget#onResourceReady
  5. BitmapImageViewTarget#setResource
  6. view.setImageBitmap(resource)

经过一长串的调用之后终于写到了ImageView上,Glide数据加载部分就此结束

总结

  1. SingeRequest启动一个任务后,先确认最终所需加载的宽高
  2. 通过EngineJob里的线程池启动一个DecodeJob
  3. DecodeJob会获取一个ModelLoader,可以把一个url加载成InputStream
  4. 通过Decoder把InputStream加载成Bitmap
  5. 通过Transcoder把Bitmap转化成BitmapDrawable
  6. 最终通过setImageBitmap展示到ImageView上
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值