[Glide4源码解析系列] — 3.Glide数据解码与转码

《[Glide4源码解析系列] — 3.Glide数据解码与转码》 Glide

Glide4源码解析系列

[Glide4源码解析系列]–1.Glide初始化
[Glide4源码解析系列]–2.Glide数据模型转换与数据抓取
[Glide4源码解析系列]–3.Glide数据解码与转码

一、简介

1. 写在前面的废话

继上一篇文章[Glide4源码解析系列]–2.Glide数据模型转换与数据抓取之后,已经过去几个月的时间,期间由于学习其他东西和项目的原因(其实是懒癌发作~),本文被搁置了很久,期间还有网友私信问什么时候会把“解码与转码”部分写好,想起曾经信誓旦旦要将这个坑补好,终于愧疚地重新看了Glide源码,把剩下的部分补上,对默默等待的朋友表示歉意。

2. 承前启后

上一篇文章,分析了Glide利用其强大的数据转换思维,根据不同类型数据的模型和数据抓取器的组合,可以实现对几乎任意图片数据类型无缝转换。主要的加载流程如下,接下来我们重点来看其中数据的解码和转码过程。

model(数据源)–>data(转换数据)–>decode(解码)–>transformed(缩放)–>transcoded(转码)–>encoded(编码保存到本地)

二、解码器与转码器

上一篇文章,我们以从网络上加载一张图片为例子,分析了整个数据转换的过程,在最后,我们知道,Glide会现将网络获取的数据缓存到本地。最后通过DataCacheGenerator的startNext方法,启动了本地数据的解析流程,其实整个过程与前文分析的过程基本是一致的,不再细说。

这里,我们忽略该过程,而直接从不缓存的情况来看后面的解码过程,因为经过本地图片的数据抓取后,最后一样会来到解码/转码的步骤。

因此,仍然回到SourceGenerator中,在抓取到数据之后,如果不缓存的情况下,进入else分支:

  //SourceGenerator.java
  
  @Override
  public void onDataReady(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;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        //解码数据
        decodeFromRetrievedData();
      } finally {
        TraceCompat.endSection();
      }
    }
  }

如果仍在同一个线程中,进入最后的分支

  //DecodeJob.java
  
  private void decodeFromRetrievedData() {
    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);
    } else {
      runGenerators();
    }
  }
  
  private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher,
          Data data, DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      //解码数据
      Resource<R> result = decodeFromFetcher(data, dataSource);
      return result;
    } finally {
      fetcher.cleanup();
    }
  }
  
  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);
  }

以上代码一步步深入,其实最重要是最后一个方法,首先获取了LoadPath,上一篇文章提到,该对象主要功能就是解码和转码数据,那么,进入DecodeHelper看下是如何生成该对象的。(可参考代码中的注释)

  //DecodeHelper.java
  
  <Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
    return glideContext
           .getRegistry()
           .getLoadPath(dataClass, resourceClass, transcodeClass);
  }
  
  //获取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)) {
      return null;
    } else if (result == null) {
      //1. 获取解码器和转码器,并存放在DecodePath中
      List<DecodePath<Data, TResource, Transcode>> decodePaths =
          getDecodePaths(dataClass, resourceClass, transcodeClass);
      if (decodePaths.isEmpty()) {
        result = null;
      } else {
        //2. 转载解码器和转码器到LoadPath中
        result =
            new LoadPath<>(
                dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
      }
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    return result;
  }
  
  //对应上面的1,获取解码器和转码器
  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) {
        
        //1. 获取解码器
        List<ResourceDecoder<Data, TResource>> decoders =
            decoderRegistry.getDecoders(dataClass, registeredResourceClass);
            
        //2. 获取转码器
        ResourceTranscoder<TResource, Transcode> transcoder =
            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
            
        //3. 把解码器和转码器都冯导DecodePath中
        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        DecodePath<Data, TResource, Transcode> path =
            new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
                decoders, transcoder, throwableListPool);
        //4. 把DecodePath放到列表中
        decodePaths.add(path);
      }
    }
    return decodePaths;
  }

第一个方法中,又看到了一个熟悉的东西,那就是这个Registry,这个注册器就是Glide在初始化的时候,进行一系列解码器/转码器注册的东东,通过这个注册器就可以获取到可以解码dataClass这个数据类型的解码器(不清楚可以再看下第一篇文章)。

getDecodePaths方法中,分别获取了已经注册的解码器和转码器,并放到DecodePath中。

看下基本的解码/转码器包括哪些(在第一篇文章也有详细说明):

解码器功能
ByteBufferGifDecoder将ByteBuffer解码为GifDrawable
ByteBufferBitmapDecoder将ByteBuffer解码为Bitmap
ResourceDrawableDecoder将资源Uri解码为Drawable
ResourceBitmapDecoder将资源ID解码为Bitmap
BitmapDrawableDecoder将数据解码为BitmapDrawable
StreamBitmapDecoder将InputStreams解码为Bitmap
StreamGifDecoder将InputStream数据转换为BtyeBuffer,再解码为GifDrawable
GifFrameResourceDecoder解码gif帧
FileDecoder包装File成为FileResource
UnitDrawableDecoder将Drawable包装为DrawableResource
UnitBitmapDecoder包装Bitmap成为BitmapResource
VideoDecoder将本地视频文件解码为Bitmap
转码器功能
BitmapDrawableTranscoder将Bitmap转码为BitmapDrawable
BitmapBytesTranscoder将Bitmap转码为Byte arrays
DrawableBytesTranscoder将BitmapDrawable转码为Byte arrays
GifDrawableBytesTranscoder将GifDrawable转码为Byte arrays

重点来看StreamBitmapDecoder和BitmapDrawableTranscoder。

在Glide抓取到数据后,会转换成为==InputStream==,此时,通过类型模型转换的思想,从解码注册器中,找到可以解码InputStream的解码器,有StreamBitmapDecoder和StreamGifDecoder,我们知道最后最有==StreamBitmapDecoder==可以顺利解码器数据,成为一张Bitmap数据。

我们在Glide.with(this).load(url).into(iv_img);中知道(以下代码),我们的目标是获取一个Drawable,即==transcodeClass==实际上是==Drawable.class==,因此,通过匹配寻找,获取到==BitmapDrawableTranscoder==转码器。

  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }
  
  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }
  
  public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

三、转码和解码

接下来,我们就具体看下,Glide是如何解码的。

首先,回到最初的解码入口

  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 {
      //调用LoadPath的load方法
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

在获取到LoadPath后,调用了它的load方法,在经过层层调用后,最后会调用以下方法,限于篇幅,中间部分就省略了,不影响流程的理解和分析。

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    //解码
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //转码
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    return transcoder.transcode(transformed, options);
  }

可以看到,先是解码了数据,然后再转码。以加载网络图片为例子,即现将数据解码成为Bitmap,再转码成为Drawable。

最后

在解码到一个可用于显示的资源后,将会通过回调,将数据回传给ImageView进行显示。

当然,在这里没有详细去分析整个解码和转码的过程,这个过程其实也是比较复杂,特别是Glide对于数据的缓存/复用,以及Bitmap复用,用来避免大量申请和释放内存导致的内存抖动等等,是非常值得去学习的,这也算是另外的话题了,有机会深入学习后,再专门写一篇文章吧(又给自己挖了个坑 -_-! 希望可以早日填坑,哈哈 )。

以上,感谢阅读,欢迎指正。

    原文作者:开发的猫
    原文地址: https://www.jianshu.com/p/e4b17cef6370
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞