[Glide4源码解析系列] — 3.Glide数据解码与转码
![](https://img.haomeiwen.com/i2789400/fcc72af8394a0819.png)
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<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看下是如何生成该对象的。(可参考代码中的注释)
//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==转码器。
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是如何解码的。
首先,回到最初的解码入口
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方法,在经过层层调用后,最后会调用以下方法,限于篇幅,中间部分就省略了,不影响流程的理解和分析。
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复用,用来避免大量申请和释放内存导致的内存抖动等等,是非常值得去学习的,这也算是另外的话题了,有机会深入学习后,再专门写一篇文章吧(又给自己挖了个坑 -_-! 希望可以早日填坑,哈哈 )。
以上,感谢阅读,欢迎指正。