type
status
date
slug
summary
tags
category
icon
password
一个图片加载库应该具备的功能
- 图片下载
- 各种格式图片编解码
- 图片显示
- 缓存
- 图像处理:圆角,色调,调整大小等等
现在分析下Glide是如何实现这个图片加载库的,先来看一下Glide的主要模型
Glide内部模型
Target
Glide可以将一个
Resource
加载到Target
中,并在加载过程中通知相关生命周期事件.生命周期基本上是下面这个步骤:
- onLoadStarted
- onResourceReady / onLoadFailed
- 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 获取 DataDataFetcher
: 使用Data,以传递给其他模块进行下一步处理ResourceDecoder
: 将 Data 解码成 ResourceResourceDecoder
将 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
总结
- Glide软件模型比较清晰,代码结构也是严格按照这个模型来实现的;
- 图片加载的基本过程大同小异,但是期间也存在多处优化,比如内存占用方面的优化
- 生命周期的处理上,Glide自己用了回调参数去处理,其实如果集成了AndroidX Lifecycle的话,结构会更加清晰
- Glide结构虽然清晰,但是代码量其实很大的,很多细节之处并没有分析(分析起来估计得花不少时间)
- 后面会分析的有: Bitmap复用机制,图片缓存机制,编解码实际流程,不同类型图片的处理异同
- 作者:姜康
- 链接:https://jiangkang.tech/article/feff3823-f751-4767-8777-6c3977858b57
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章