type
status
date
slug
summary
tags
category
icon
password

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

  • 图片下载
  • 各种格式图片编解码
  • 图片显示
  • 缓存
  • 图像处理:圆角,色调,调整大小等等
现在分析下Glide是如何实现这个图片加载库的,先来看一下Glide的主要模型

Glide内部模型

Target

Glide可以将一个Resource加载到Target中,并在加载过程中通知相关生命周期事件.
生命周期基本上是下面这个步骤:
  1. onLoadStarted
  1. onResourceReady / onLoadFailed
  1. 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软件模型比较清晰,代码结构也是严格按照这个模型来实现的;
  1. 图片加载的基本过程大同小异,但是期间也存在多处优化,比如内存占用方面的优化
  1. 生命周期的处理上,Glide自己用了回调参数去处理,其实如果集成了AndroidX Lifecycle的话,结构会更加清晰
  1. Glide结构虽然清晰,但是代码量其实很大的,很多细节之处并没有分析(分析起来估计得花不少时间)
  1. 后面会分析的有: Bitmap复用机制,图片缓存机制,编解码实际流程,不同类型图片的处理异同
Gradle读取配置文件Context.getSystemService()实现解析
姜康
姜康
一个软件工程师
公告
type
status
date
slug
summary
tags
category
icon
password
🎉博客网站重新制作了🎉
👏欢迎更新体验👏