[Glide4原始碼解析系列] — 3.Glide資料解碼與轉碼

Glide
Glide4原始碼解析系列
ofollow,noindex">[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複用,用來避免大量申請和釋放記憶體導致的記憶體抖動等等,是非常值得去學習的,這也算是另外的話題了,有機會深入學習後,再專門寫一篇文章吧(又給自己挖了個坑 -_-! 希望可以早日填坑,哈哈 )。
以上,感謝閱讀,歡迎指正。