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 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 Resource decodeFromData(DataFetcher> fetcher,
Data data, DataSource dataSource) throws GlideException {
try {
if (data == null) {
return null;
}
//解码数据
Resource result = decodeFromFetcher(data, dataSource);
return result;
} finally {
fetcher.cleanup();
}
}
private Resource decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
return runLoadPath(data, dataSource, path);
}
以上代码一步步深入,其实最重要是最后一个方法,首先获取了LoadPath,上一篇文章提到,该对象主要功能就是解码和转码数据,那么,进入DecodeHelper看下是如何生成该对象的。(可参考代码中的注释)
//DecodeHelper.java
LoadPath getLoadPath(Class dataClass) {
return glideContext
.getRegistry()
.getLoadPath(dataClass, resourceClass, transcodeClass);
}
//获取LoadPath
public LoadPath getLoadPath(
@NonNull Class dataClass, @NonNull Class resourceClass,
@NonNull Class transcodeClass) {
LoadPath result =
loadPathCache.get(dataClass, resourceClass, transcodeClass);
if (loadPathCache.isEmptyLoadPath(result)) {
return null;
} else if (result == null) {
//1. 获取解码器和转码器,并存放在DecodePath中
List> 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 List> getDecodePaths(
@NonNull Class dataClass, @NonNull Class resourceClass,
@NonNull Class transcodeClass) {
List> decodePaths = new ArrayList<>();
List> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);
//获取所有可能解码器和转码器
for (Class registeredResourceClass : registeredResourceClasses) {
List> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
for (Class registeredTranscodeClass : registeredTranscodeClasses) {
//1. 获取解码器
List> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);
//2. 获取转码器
ResourceTranscoder transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
//3. 把解码器和转码器都冯导DecodePath中
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
DecodePath 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 load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder asDrawable() {
return as(Drawable.class);
}
public RequestBuilder as(Class resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
三、转码和解码
接下来,我们就具体看下,Glide是如何解码的。
首先,回到最初的解码入口
private Resource decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());
return runLoadPath(data, dataSource, path);
}
private Resource runLoadPath(Data data, DataSource dataSource,
LoadPath path) throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);
try {
//调用LoadPath的load方法
return path.load(
rewinder, options, width, height, new DecodeCallback(dataSource));
} finally {
rewinder.cleanup();
}
}
在获取到LoadPath后,调用了它的load方法,在经过层层调用后,最后会调用以下方法,限于篇幅,中间部分就省略了,不影响流程的理解和分析。
public Resource decode(DataRewinder rewinder, int width, int height,
@NonNull Options options, DecodeCallback callback) throws GlideException {
//解码
Resource decoded = decodeResource(rewinder, width, height, options);
//转码
Resource transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
可以看到,先是解码了数据,然后再转码。以加载网络图片为例子,即现将数据解码成为Bitmap,再转码成为Drawable。
最后
在解码到一个可用于显示的资源后,将会通过回调,将数据回传给ImageView进行显示。
当然,在这里没有详细去分析整个解码和转码的过程,这个过程其实也是比较复杂,特别是Glide对于数据的缓存/复用,以及Bitmap复用,用来避免大量申请和释放内存导致的内存抖动等等,是非常值得去学习的,这也算是另外的话题了,有机会深入学习后,再专门写一篇文章吧(又给自己挖了个坑 -_-! 希望可以早日填坑,哈哈 )。
以上,感谢阅读,欢迎指正。