Glide原理分析


一个图片加载库应该具备的功能

  • 图片下载
  • 各种格式图片编解码
  • 图片显示
  • 缓存
  • 图像处理:圆角,色调,调整大小等等

现在分析下Glide是如何实现这个图片加载库的,先来看一下Glide的主要模型

Glide内部模型

Target

Glide可以将一个Resource加载到Target中,并在加载过程中通知相关生命周期事件.

生命周期基本上是下面这个步骤:

  1. onLoadStarted
  2. onResourceReady / onLoadFailed
  3. onLoadCleared

但是这些步骤也不是绝对的.

如果resource在内存中或者model对象为null时,onLoadStarted不会被调用.

如果target不会被cleared,onLoadCleared也不会被调用.

// R表示 target可以显示的resource类型,比如Target是ImageView ,Resource类型是Bitmap
public interface Target<R> extends LifecycleListener {

  int SIZE_ORIGINAL = Integer.MIN_VALUE;

  void onLoadStarted(@Nullable Drawable placeholder);
  void onLoadFailed(@Nullable Drawable errorDrawable);
  // R在这里使用
  void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);
  void onLoadCleared(@Nullable Drawable placeholder);

  //获取target的size
  void getSize(@NonNull SizeReadyCallback cb);
  void removeCallback(@NonNull SizeReadyCallback cb);
  void setRequest(@Nullable Request request);
  @Nullable
  Request getRequest();
}

使用得最多的就是ImageViewTarget了.ImageViewTarget是一个抽象类,它定义了一个抽象方法用于设置具体的资源:

protected abstract void setResource(@Nullable Z resource);

它的子类就可以实现各自的资源设置方法,比如setBitmapResource(),setDrawable()之类的.

对于ViewTarget来说,会使用View#setTag()View#getTagId()方法在RecyclerView或者其他ViewGroup下存储一些信息,解决复用问题.

ViewTarget中有一个方法getSize(),利用了ViewTreeObserver.OnPreDrawListener时机去获取尺寸.

目前源码中很多Target已经废弃,不推荐继承了,原因是因为onLoadCleared(),在使用资源的时候如果不clear()很容易导致问题.

Request

表示 加载Resource到Target的过程.

RequestBuilder: 可以自定义各种属性,相当于Request的配置信息;

RequestManager: 创建和管理Request

Resource

在Glide中比较常见的是:

  • Bitmap
  • Drawable
  • File

一个资源接口,用于包装资源以便于“池化”和重用.

// Z是被包装的资源类
public interface Resource<Z> {
  @NonNull
  Class<Z> getResourceClass();
  @NonNull
  Z get();
  int getSize();
  void recycle();
}

比如BitmapResource:

public class BitmapResource implements Resource<Bitmap>, Initializable {
  private final Bitmap bitmap;
  private final BitmapPool bitmapPool;

  @Nullable
  public static BitmapResource obtain(@Nullable Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
    if (bitmap == null) {
      return null;
    } else {
      return new BitmapResource(bitmap, bitmapPool);
    }
  }

  public BitmapResource(@NonNull Bitmap bitmap, @NonNull BitmapPool bitmapPool) {
    this.bitmap = Preconditions.checkNotNull(bitmap, "Bitmap must not be null");
    this.bitmapPool = Preconditions.checkNotNull(bitmapPool, "BitmapPool must not be null");
  }

  @NonNull
  @Override
  public Class<Bitmap> getResourceClass() {
    return Bitmap.class;
  }

  @NonNull
  @Override
  public Bitmap get() {
    return bitmap;
  }

  @Override
  public int getSize() {
    return Util.getBitmapByteSize(bitmap);
  }

  @Override
  public void recycle() {
    bitmapPool.put(bitmap);
  }

  @Override
  public void initialize() {
    bitmap.prepareToDraw();
  }
}

使用了BitmapPool进行“池化”和回收.

Model

不知道怎么描述,可以是下面这些:

  • 定义的实体类,比如UserInfo,这其中包含了图片url
  • 一个简单的url
  • File
  • Uri
  • 资源ID

Data

一般都是InputStream,也可以是File,也可以是byte[].

Model/Data/Resource

ModelLoader : 从Model 获取 Data

DataFetcher: 使用Data,以传递给其他模块进行下一步处理

ResourceDecoder: 将 Data 解码成 Resource

ResourceDecoder

将 Data 解码 成 Resource , 比如将InputStream解码成Bitmap

ResourceEncoder

从Resource中取出Data,然后写入到一些持久化的数据存储中

比如 从Bitmap从取出字节流 ,写入到本地文件中.

Transformation

对Resource进行变换处理,即通常说的图片处理:

  • CenterInside
  • CenterCrop
  • CircleCrop
  • FitCenter
  • Rotate
  • RoundedCorners
  • GranularRoundedCorners

ResourcTranscoder在概念上的区别主要是:

  • Transformation不改变Resource的类型
  • ResourceTranscoder改变资源的类型

ResourceTranscoder

将一种Resource转换成另一种Resource.

比如将Bitmap转换成Drawable,将Bitmap转换成byte[]等等.

Registry

Glide内部组件管理,像上面的ModelLoader,Encoder,Decoder在Registry中都有各自实现的Registry以进行注册和管理.

比如ResourceDecoderRegistry.


看完了模型定义,再来看图片库的功能.

下载

Android端目前网络请求基本上都是使用的OkHttp,我们使用Glide的时候一般也会使用OkHttp作为网络库.

OkHttpStreamFetcher中有:

  @Override
  public void loadData(
      @NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {

    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());

    // http header
    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
      String key = headerEntry.getKey();
      requestBuilder.addHeader(key, headerEntry.getValue());
    }
    Request request = requestBuilder.build();
    this.callback = callback;

    call = client.newCall(request);
    // 异步请求
    call.enqueue(this);
  }

请求成功之后,会得到一个InputStream:

  @Override
  public void onResponse(@NonNull Call call, @NonNull Response response) {
    // 成功回调
    responseBody = response.body();
    if (response.isSuccessful()) {
      // 图片大小
      long contentLength = Preconditions.checkNotNull(responseBody).contentLength();

      // 获取InputStream,并传递给其他模块处理
      stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
      callback.onDataReady(stream);
    } else {
      callback.onLoadFailed(new HttpException(response.message(), response.code()));
    }
  }

这里将InputStream包装到ContentLengthInputStream中.

然后就是对InputStream中的字节流进行解码了.

编解码

对字节流解码的操作在DecodeJob中,这里的调用链比较长,具体的代码就不贴出来了.

这里涉及的Decoder比较多,Glide会根据不同的图片格式使用不同的Decoder进行解码:

解码器 说明(—> 表示解码成)
StreamGifDecoder InputStream —> ByteArray —> GifDrawable
InputStreamBitmapImageDecoderResourceDecoder InputStream —> Bitmap
ByteBufferBitmapDecoder ByteBuffer —> Bitmap
ResourceBitmapDecoder Uri —> Bitmap
UnitDrawableDecoder Drawable —> Drawable
ByteBufferGifDecoder ByteBuffer —> GifDrawable
UnitBitmapDecoder Bitmap —> Bitmap
GifFrameResourceDecoder Gif Frame —> Bitmap
ParcelFileDescriptorBitmapDecoder ParcelFileDescriptor —> Bitmap
FileDecoder File —> File
SvgDecoder InputStream —> Svg
StreamBitmapDecoder InputStream —> Bitmap
ByteBufferBitmapImageDecoderResourceDecoder ByteBuffer —> Bitmap
VideoDecoder Video Frame —> Bitmap
ResourceDrawableDecoder Uri —> Drawable

解码完成之后,就是资源类型之间的转换了:

Transcoder 说明
BitmapDrawableTranscoder Bitmap —> BitmapDrawable
SvgDrawableTranscoder SVG. —> Picture
GifDrawableBytesTranscoder GifDrawable —> byte[]
BitmapBytesTranscoder Bitmap —> byte[]
DrawableBytesTranscoder Drawable —> byte[]

像一般的图片显示,用到的是StreamBitmapDecoder,而它的解码实际上通过Downsampler进行的,经过一系列的处理,比如获取图片宽高,缩放,旋转等,最后还是我们熟悉的API:

    public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
      return BitmapFactory.decodeStream(dataRewinder.rewindAndGet(), null, options);
    }

    public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
      return BitmapFactory.decodeFileDescriptor(
          dataRewinder.rewindAndGet().getFileDescriptor(), null, options);
    }

图片显示

其实就是上面说到的Target,目前最常见的就是ViewTarget,ImageViewTarget.

在Target中调用系统的API,比如setBitmapResource()等进行设置显示图片:

  @Override
  public void setDrawable(Drawable drawable) {
    view.setImageDrawable(drawable);
  }

以及生命周期的管理.

缓存

在查看缓存策略之前,先看一下数据源的定义:

public enum DataSource {


  LOCAL, // 数据可能是从本地设备获取的,即使是通过ContentProvider从其他远程源获取的 

  REMOTE, // 从远程获取的

  DATA_DISK_CACHE, // 从设备缓存获取的未修改数据

  RESOURCE_DISK_CACHE, // 设备缓存的修改数据

  MEMORY_CACHE, // 内存缓存
}

缓存策略定义在DiskCacheStrategy中,分为以下几种:

  • DiskCacheStrategy.ALL

    缓存远程Data和Resource,以及本地的Resource

  • DiskCacheStrategy.NONE

    不缓存

  • DiskCacheStrategy.DATA

    在Data解码之前直接写到磁盘缓存

  • DiskCacheStrategy.RESOURCE

    将解码后的Resourc写入到磁盘缓存

  • DiskCacheStrategy.AUTOMATIC

    自动选择

图像处理

Transformation中定义:

  • CenterInside
  • CenterCrop
  • CircleCrop
  • FitCenter
  • Rotate
  • RoundedCorners
  • GranularRoundedCorners

总结

  1. Glide软件模型比较清晰,代码结构也是严格按照这个模型来实现的;
  2. 图片加载的基本过程大同小异,但是期间也存在多处优化,比如内存占用方面的优化
  3. 生命周期的处理上,Glide自己用了回调参数去处理,其实如果集成了AndroidX Lifecycle的话,结构会更加清晰
  4. Glide结构虽然清晰,但是代码量其实很大的,很多细节之处并没有分析(分析起来估计得花不少时间)
  5. 后面会分析的有: Bitmap复用机制,图片缓存机制,编解码实际流程,不同类型图片的处理异同

文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
Flutter-从Widget导出图片 Flutter-从Widget导出图片
在Android中从View中导出图片,使用的是Canvas + Bitmap. 在Flutter中,同样支持这种功能,使用的是RepaintBoundary 使用RepaintBoundary包装WidgetRepaintBoundary
2020-11-12
下一篇 
GC与Reference GC与Reference
引用类型 SoftReference 普通的GC不会回收软引用,只有在即将发生OOM的时候(即最后一次Full GC),如果被引用的对象只有SoftReference指向的引用,才会被回收. WeakReference 当发生GC时,如果
2020-11-02
  目录