[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 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  Resource decodeFromData(DataFetcher fetcher,
          Data data, DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      //解码数据
      Resource result = decodeFromFetcher(data, dataSource);
      return result;
    } finally {
      fetcher.cleanup();
    }
  }
  
  private  Resource decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
    return runLoadPath(data, dataSource, path);
  }

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

  //DecodeHelper.java
  
   LoadPath getLoadPath(Class dataClass) {
    return glideContext
           .getRegistry()
           .getLoadPath(dataClass, resourceClass, transcodeClass);
  }
  
  //获取LoadPath
  public  LoadPath getLoadPath(
      @NonNull Class dataClass, @NonNull Class resourceClass,
      @NonNull Class transcodeClass) {
    LoadPath result =
        loadPathCache.get(dataClass, resourceClass, transcodeClass);
    if (loadPathCache.isEmptyLoadPath(result)) {
      return null;
    } else if (result == null) {
      //1. 获取解码器和转码器,并存放在DecodePath中
      List> 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  List> getDecodePaths(
      @NonNull Class dataClass, @NonNull Class resourceClass,
      @NonNull Class transcodeClass) {
    List> decodePaths = new ArrayList<>();
    List> registeredResourceClasses =
        decoderRegistry.getResourceClasses(dataClass, resourceClass);
        
    //获取所有可能解码器和转码器
    for (Class registeredResourceClass : registeredResourceClasses) {
      List> registeredTranscodeClasses =
          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

      for (Class registeredTranscodeClass : registeredTranscodeClasses) {
        
        //1. 获取解码器
        List> decoders =
            decoderRegistry.getDecoders(dataClass, registeredResourceClass);
            
        //2. 获取转码器
        ResourceTranscoder transcoder =
            transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
            
        //3. 把解码器和转码器都冯导DecodePath中
        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
        DecodePath 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 load(@Nullable String string) {
    return asDrawable().load(string);
  }
  
  public RequestBuilder asDrawable() {
    return as(Drawable.class);
  }
  
  public  RequestBuilder as(Class resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

三、转码和解码

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

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

  private  Resource decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
    return runLoadPath(data, dataSource, path);
  }

  private  Resource runLoadPath(Data data, DataSource dataSource,
      LoadPath path) throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      //调用LoadPath的load方法
      return path.load(
          rewinder, options, width, height, new DecodeCallback(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

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

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

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

最后

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

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

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

你可能感兴趣的:([Glide4源码解析系列] — 3.Glide数据解码与转码)