前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Glide4源码解析系列] — 3.Glide数据解码与转码

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

作者头像
开发的猫
发布2020-04-01 17:47:12
8380
发布2020-04-01 17:47:12
举报
文章被收录于专栏:猫哥的专栏猫哥的专栏

一、简介

1. 写在前面的废话

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

2. 承前启后

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

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

二、解码器与转码器

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

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

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

代码语言:javascript
复制
  //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

代码语言:javascript
复制
  //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();
      }
    }
  }

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

代码语言:javascript
复制
  //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看下是如何生成该对象的。(可参考代码中的注释)

代码语言:javascript
复制
  //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==转码器。

代码语言:javascript
复制
  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是如何解码的。

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

代码语言:javascript
复制
  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方法,在经过层层调用后,最后会调用以下方法,限于篇幅,中间部分就省略了,不影响流程的理解和分析。

代码语言:javascript
复制
  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复用,用来避免大量申请和释放内存导致的内存抖动等等,是非常值得去学习的,这也算是另外的话题了,有机会深入学习后,再专门写一篇文章吧(又给自己挖了个坑 -_-! 希望可以早日填坑,哈哈 )。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 写在前面的废话
  • 2. 承前启后
  • 二、解码器与转码器
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档