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)過去幾個月的時間剃袍,期間由于學(xué)習(xí)其他東西和項(xiàng)目的原因(其實(shí)是懶癌發(fā)作~)犹赖,本文被擱置了很久榴鼎,期間還有網(wǎng)友私信問什么時候會把“解碼與轉(zhuǎn)碼”部分寫好,想起曾經(jīng)信誓旦旦要將這個坑補(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ò)上加載一張圖片為例子枢步,分析了整個數(shù)據(jù)轉(zhuǎn)換的過程沉删,在最后,我們知道醉途,Glide會現(xiàn)將網(wǎng)絡(luò)獲取的數(shù)據(jù)緩存到本地矾瑰。最后通過DataCacheGenerator的startNext方法,啟動了本地?cái)?shù)據(jù)的解析流程隘擎,其實(shí)整個過程與前文分析的過程基本是一致的殴穴,不再細(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);
}
}
此時,會調(diào)用一個回調(diào)接口忙厌,這個接口的實(shí)現(xiàn)就是DecodeJob凫岖,即啟動整個加載任務(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();
}
}
}
如果仍在同一個線程中慰毅,進(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í)最重要是最后一個方法,首先獲取了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;
}
第一個方法中呵哨,又看到了一個熟悉的東西,那就是這個Registry轨奄,這個注冊器就是Glide在初始化的時候孟害,進(jìn)行一系列解碼器/轉(zhuǎn)碼器注冊的東東,通過這個注冊器就可以獲取到可以解碼dataClass這個數(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==朝巫,此時,通過類型模型轉(zhuǎn)換的思想石景,從解碼注冊器中劈猿,找到可以解碼InputStream的解碼器拙吉,有StreamBitmapDecoder和StreamGifDecoder,我們知道最后最有==StreamBitmapDecoder==可以順利解碼器數(shù)據(jù)糙臼,成為一張Bitmap數(shù)據(jù)庐镐。
我們在Glide.with(this).load(url).into(iv_img);中知道(以下代碼)恩商,我們的目標(biāo)是獲取一個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。
最后
在解碼到一個可用于顯示的資源后摧莽,將會通過回調(diào)庙洼,將數(shù)據(jù)回傳給ImageView進(jìn)行顯示。
當(dāng)然范嘱,在這里沒有詳細(xì)去分析整個解碼和轉(zhuǎn)碼的過程送膳,這個過程其實(shí)也是比較復(fù)雜,特別是Glide對于數(shù)據(jù)的緩存/復(fù)用丑蛤,以及Bitmap復(fù)用叠聋,用來避免大量申請和釋放內(nèi)存導(dǎo)致的內(nèi)存抖動等等,是非常值得去學(xué)習(xí)的受裹,這也算是另外的話題了碌补,有機(jī)會深入學(xué)習(xí)后虏束,再專門寫一篇文章吧(又給自己挖了個坑 -_-! 希望可以早日填坑,哈哈 )厦章。
以上镇匀,感謝閱讀,歡迎指正袜啃。