
Glide4源碼解析系列
[Glide4源碼解析系列]--1.Glide初始化
[Glide4源碼解析系列]--2.Glide數(shù)據(jù)模型轉(zhuǎn)換與數(shù)據(jù)抓取
[Glide4源碼解析系列]--3.Glide數(shù)據(jù)解碼與轉(zhuǎn)碼
一、簡介
1. 寫在前面的廢話
繼上一篇文章[Glide4源碼解析系列]--2.Glide數(shù)據(jù)模型轉(zhuǎn)換與數(shù)據(jù)抓取之后,已經(jīng)過去幾個(gè)月的時(shí)間,期間由于學(xué)習(xí)其他東西和項(xiàng)目的原因(其實(shí)是懶癌發(fā)作~),本文被擱置了很久,期間還有網(wǎng)友私信問什么時(shí)候會把“解碼與轉(zhuǎn)碼”部分寫好,想起曾經(jīng)信誓旦旦要將這個(gè)坑補(bǔ)好,終于愧疚地重新看了Glide源碼,把剩下的部分補(bǔ)上,對默默等待的朋友表示歉意。
2. 承前啟后
上一篇文章,分析了Glide利用其強(qiáng)大的數(shù)據(jù)轉(zhuǎn)換思維,根據(jù)不同類型數(shù)據(jù)的模型和數(shù)據(jù)抓取器的組合,可以實(shí)現(xiàn)對幾乎任意圖片數(shù)據(jù)類型無縫轉(zhuǎn)換。主要的加載流程如下,接下來我們重點(diǎn)來看其中數(shù)據(jù)的解碼和轉(zhuǎn)碼過程。
model(數(shù)據(jù)源)-->data(轉(zhuǎn)換數(shù)據(jù))-->decode(解碼)-->transformed(縮放)-->transcoded(轉(zhuǎn)碼)-->encoded(編碼保存到本地)
二、解碼器與轉(zhuǎn)碼器
上一篇文章,我們以從網(wǎng)絡(luò)上加載一張圖片為例子,分析了整個(gè)數(shù)據(jù)轉(zhuǎn)換的過程,在最后,我們知道,Glide會現(xiàn)將網(wǎng)絡(luò)獲取的數(shù)據(jù)緩存到本地。最后通過DataCacheGenerator的startNext方法,啟動了本地?cái)?shù)據(jù)的解析流程,其實(shí)整個(gè)過程與前文分析的過程基本是一致的,不再細(xì)說。
這里,我們忽略該過程,而直接從不緩存的情況來看后面的解碼過程,因?yàn)榻?jīng)過本地圖片的數(shù)據(jù)抓取后,最后一樣會來到解碼/轉(zhuǎn)碼的步驟。
因此,仍然回到SourceGenerator中,在抓取到數(shù)據(jù)之后,如果不緩存的情況下,進(jìn)入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);
}
}
此時(shí),會調(diào)用一個(gè)回調(diào)接口,這個(gè)接口的實(shí)現(xiàn)就是DecodeJob,即啟動整個(gè)加載任務(wù)的對象。直接進(jìn)入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 {
//解碼數(shù)據(jù)
decodeFromRetrievedData();
} finally {
TraceCompat.endSection();
}
}
}
如果仍在同一個(gè)線程中,進(jìn)入最后的分支
//DecodeJob.java
private void decodeFromRetrievedData() {
Resource<R> resource = null;
try {
//解碼數(shù)據(jù)
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;
}
//解碼數(shù)據(jù)
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);
}
以上代碼一步步深入,其實(shí)最重要是最后一個(gè)方法,首先獲取了LoadPath,上一篇文章提到,該對象主要功能就是解碼和轉(zhuǎn)碼數(shù)據(jù),那么,進(jìn)入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. 獲取解碼器和轉(zhuǎn)碼器,并存放在DecodePath中
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
if (decodePaths.isEmpty()) {
result = null;
} else {
//2. 轉(zhuǎn)載解碼器和轉(zhuǎn)碼器到LoadPath中
result =
new LoadPath<>(
dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
}
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}
//對應(yīng)上面的1,獲取解碼器和轉(zhuǎn)碼器
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);
//獲取所有可能解碼器和轉(zhuǎn)碼器
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. 獲取轉(zhuǎn)碼器
ResourceTranscoder<TResource, Transcode> transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
//3. 把解碼器和轉(zhuǎn)碼器都馮導(dǎo)DecodePath中
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
DecodePath<Data, TResource, Transcode> path =
new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
decoders, transcoder, throwableListPool);
//4. 把DecodePath放到列表中
decodePaths.add(path);
}
}
return decodePaths;
}
第一個(gè)方法中,又看到了一個(gè)熟悉的東西,那就是這個(gè)Registry,這個(gè)注冊器就是Glide在初始化的時(shí)候,進(jìn)行一系列解碼器/轉(zhuǎn)碼器注冊的東東,通過這個(gè)注冊器就可以獲取到可以解碼dataClass這個(gè)數(shù)據(jù)類型的解碼器(不清楚可以再看下第一篇文章)。
在getDecodePaths方法中,分別獲取了已經(jīng)注冊的解碼器和轉(zhuǎn)碼器,并放到DecodePath中。
看下基本的解碼/轉(zhuǎn)碼器包括哪些(在第一篇文章也有詳細(xì)說明):
| 解碼器 | 功能 |
|---|---|
| ByteBufferGifDecoder | 將ByteBuffer解碼為GifDrawable |
| ByteBufferBitmapDecoder | 將ByteBuffer解碼為Bitmap |
| ResourceDrawableDecoder | 將資源Uri解碼為Drawable |
| ResourceBitmapDecoder | 將資源ID解碼為Bitmap |
| BitmapDrawableDecoder | 將數(shù)據(jù)解碼為BitmapDrawable |
| StreamBitmapDecoder | 將InputStreams解碼為Bitmap |
| StreamGifDecoder | 將InputStream數(shù)據(jù)轉(zhuǎn)換為BtyeBuffer,再解碼為GifDrawable |
| GifFrameResourceDecoder | 解碼gif幀 |
| FileDecoder | 包裝File成為FileResource |
| UnitDrawableDecoder | 將Drawable包裝為DrawableResource |
| UnitBitmapDecoder | 包裝Bitmap成為BitmapResource |
| VideoDecoder | 將本地視頻文件解碼為Bitmap |
| 轉(zhuǎn)碼器 | 功能 |
|---|---|
| BitmapDrawableTranscoder | 將Bitmap轉(zhuǎn)碼為BitmapDrawable |
| BitmapBytesTranscoder | 將Bitmap轉(zhuǎn)碼為Byte arrays |
| DrawableBytesTranscoder | 將BitmapDrawable轉(zhuǎn)碼為Byte arrays |
| GifDrawableBytesTranscoder | 將GifDrawable轉(zhuǎn)碼為Byte arrays |
重點(diǎn)來看StreamBitmapDecoder和BitmapDrawableTranscoder。
在Glide抓取到數(shù)據(jù)后,會轉(zhuǎn)換成為==InputStream==,此時(shí),通過類型模型轉(zhuǎn)換的思想,從解碼注冊器中,找到可以解碼InputStream的解碼器,有StreamBitmapDecoder和StreamGifDecoder,我們知道最后最有==StreamBitmapDecoder==可以順利解碼器數(shù)據(jù),成為一張Bitmap數(shù)據(jù)。
我們在Glide.with(this).load(url).into(iv_img);中知道(以下代碼),我們的目標(biāo)是獲取一個(gè)Drawable,即==transcodeClass==實(shí)際上是==Drawable.class==,因此,通過匹配尋找,獲取到==BitmapDrawableTranscoder==轉(zhuǎn)碼器。
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);
}
三、轉(zhuǎn)碼和解碼
接下來,我們就具體看下,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 {
//調(diào)用LoadPath的load方法
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally {
rewinder.cleanup();
}
}
在獲取到LoadPath后,調(diào)用了它的load方法,在經(jīng)過層層調(diào)用后,最后會調(diào)用以下方法,限于篇幅,中間部分就省略了,不影響流程的理解和分析。
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);
//轉(zhuǎn)碼
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
可以看到,先是解碼了數(shù)據(jù),然后再轉(zhuǎn)碼。以加載網(wǎng)絡(luò)圖片為例子,即現(xiàn)將數(shù)據(jù)解碼成為Bitmap,再轉(zhuǎn)碼成為Drawable。
最后
在解碼到一個(gè)可用于顯示的資源后,將會通過回調(diào),將數(shù)據(jù)回傳給ImageView進(jìn)行顯示。
當(dāng)然,在這里沒有詳細(xì)去分析整個(gè)解碼和轉(zhuǎn)碼的過程,這個(gè)過程其實(shí)也是比較復(fù)雜,特別是Glide對于數(shù)據(jù)的緩存/復(fù)用,以及Bitmap復(fù)用,用來避免大量申請和釋放內(nèi)存導(dǎo)致的內(nèi)存抖動等等,是非常值得去學(xué)習(xí)的,這也算是另外的話題了,有機(jī)會深入學(xué)習(xí)后,再專門寫一篇文章吧(又給自己挖了個(gè)坑 -_-! 希望可以早日填坑,哈哈 )。
以上,感謝閱讀,歡迎指正。