Glide4.8原始碼拆解(三)Registry和資料轉換流程
前言
Registry是Glide中非常重要的知識,可以把它理解成連結各個核心功能模組的集中營或者掛載中心,這一章節就來分解它是如何建立和運作的:
本章要討論的內容:
- Registry的基本構成;
- 各個模組的功能和介紹;
- 資料的轉換流程;
從Registry開始
Registry是一個元件管理類,它的主要用途是擴充套件和替換Glide元件,這些元件包括載入,編碼,解碼等邏輯;Registry內部支援的模組型別如下: Registry.java
private final ModelLoaderRegistry modelLoaderRegistry;
private final EncoderRegistry encoderRegistry;
private final ResourceDecoderRegistry decoderRegistry;
private final ResourceEncoderRegistry resourceEncoderRegistry;
private final DataRewinderRegistry dataRewinderRegistry;
private final TranscoderRegistry transcoderRegistry;//Resource轉換模組註冊
private final ImageHeaderParserRegistry imageHeaderParserRegistry;//檔案頭解析模組註冊
複製程式碼
Registry並不是承當所有模組的註冊工作,而是把各個模組分配的不同的Registry當中; 主要模組的功能:
- ModelLoaderRegistry ://資料載入模組註冊
- EncoderRegistry://所有對資料進行編碼模組的註冊
- ResourceDecoderRegistry://處理過的解碼模組註冊
- ResourceEncoderRegistry://處理過的編碼模組註冊
- DataRewinderRegistry : //資料流重置起點模組註冊
- TranscoderRegistry: // Resource進行轉換模組註冊
- ImageHeaderParserRegistry //圖片頭解析模組註冊
Glide自身充當對外呼叫的門戶,Registry提供了一下入口方法來實現各個模組的註冊和呼叫;主要方法如下:
註冊相關方法:
- append() //尾步追加
- prepend() //頭部插入
- register() //註冊,相當於append()
- replace() //替換掉相同條件的所有模組
操作相關的方法:
- getLoadPath()//獲取載入路徑
- getDecodePaths() //獲取解析路徑
- getRegisteredResourceClasses()//獲取所有匹配的ResourceClasses;
- isResourceEncoderAvailable()//ResourceEncoder是否可用;
- getResultEncoder()//獲取Encoder;
- getRewinder()//獲取Rewinder;
- getModelLoaders()//獲取ModelLoader;
總結:Registry通過內部Registry分別管理不同型別的元件,Registry提供統一的入口方法來實現註冊和獲取;
下面對各個模組基本介紹:
模組簡要分析
ModelLoader
ModelLoader
是通過ModelLoaderRegistry
進行管理,ModelLoader
需要接受兩個泛型型別<Model,Data>
,ModelLoader
本身是一個工廠介面,主要工作是將複雜資料模型轉通過DataFetcher轉換成需要的Data,LoadData
是ModelLoader的內部類,是對DataFetcher和Key的封裝實體,ModelLoader
的建立用ModelLoaderFactory
,一個基本的ModelLoader
建立應該是這個樣子的: 參考HttpGlideUrlLoader
第一步:自定義類實現自ModelLoader,重寫BuildLoadData()方法和handles()方法;
public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
@Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;
public HttpGlideUrlLoader() {
this(null);
}
public HttpGlideUrlLoader(@Nullable ModelCache<GlideUrl, GlideUrl> modelCache) {
this.modelCache = modelCache;
}
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
@Override
public boolean handles(@NonNull GlideUrl model) {
return true;
}
}
複製程式碼
buildLoadData()
需要建立LoadData,需要傳入Key和DataFetcher,handles()
返回值代表是否接受當前model型別的,true代表接受,所以一般都是true;
第二步:自定義Fetcher實現DataFecher,重寫loadData()、cleanup()、cancel()、getDataClass()、getDataSource()方法;
public class HttpUrlFetcher implements DataFetcher<InputStream> {
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
}
}
}
//清理工作
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
urlConnection = null;
}
//取消請求
@Override
public void cancel() {
isCancelled = true;
}
//返回Data型別
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
//返回DataSource
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
複製程式碼
第三步:建立Factory類,重寫build()、teardown()方法,在build()方法中返回真正的MolderLoader物件;
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new HttpGlideUrlLoader(modelCache);//建立ModelLoader
}
@Override
public void teardown() {
// Do nothing.
}
}
複製程式碼
接下來,就可以在Registry中註冊ModelLoader了;在第一章我們簡單說過模組的配置可以用Annotation和Manifest兩種型別,在registerComponents()
方法中,可以拿到Registry
,這樣就可以呼叫registry的註冊相關方法;
@Override
public void registerComponents(Context context, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory(context));
}
複製程式碼
最後,說一下泛型<Model,Data>接受的範圍,Model代表使用者輸入的型別,理論上可以用任意資料型別,主要的輸入點在Glide.with().load(model)
; Data理論上也可以是任意資料型別,但基於後續流程的支援,一般都是File
,InputStream
和ByteBuffer
;
ResourceDecoder
ResourceDecoder
是一個解析Resource的介面,接受兩個泛型<T,Z>
,定義了兩個方法handles()
和decode
,參考一個簡單的BitmapDecoder
:
public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
private final Downsampler downsampler;
private final ArrayPool byteArrayPool;
public StreamBitmapDecoder(Downsampler downsampler, ArrayPool byteArrayPool) {
this.downsampler = downsampler;
this.byteArrayPool = byteArrayPool;
}
@Override
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
return downsampler.handles(source);
}
@Override
public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
@NonNull Options options)
throws IOException {
...
try {
return downsampler.decode(invalidatingStream, width, height, options, callbacks);
} finally {
exceptionStream.release();
if (ownsBufferedStream) {
bufferedStream.release();
}
}
}
}
複製程式碼
泛型解析:T
表示輸入型別一般為File
,InputStream
或者ByteBuffer
,Z
表示輸出型別,一般為Bitmap
和Drawable
Encoder和ResourceEncoder
Encoder
表面意思為加密,本質上和加密沒有關係,主要作用是將T持久化到本地cache;Encode
接受泛型T
,而這個T
可以是InputStream
、ByteBuffer
、Resource<T>
,ResourceEncoder
繼承Encoder<Resource<T>>
,接受泛型T
,而Resource
中的T
一般取值範圍為:Bitmap
、BitmapDrawable
、GifDrawable
;
我們分析一下把Bitmap持久化到本地cache的類:BitmapEncoder
public class BitmapEncoder implements ResourceEncoder<Bitmap> {
@Override
public boolean encode(@NonNull Resource<Bitmap> resource, @NonNull File file,
@NonNull Options options) {
final Bitmap bitmap = resource.get();
Bitmap.CompressFormat format = getFormat(bitmap, options);
GlideTrace.
beginSectionFormat("encode: [%dx%d] %s", bitmap.getWidth(), bitmap.getHeight(), format);
try {
long start = LogTime.getLogTime();
int quality = options.get(COMPRESSION_QUALITY);
boolean success = false;
OutputStream os = null;
try {
os = new FileOutputStream(file);
if (arrayPool != null) {
os = new BufferedOutputStream(os, arrayPool);
}
bitmap.compress(format, quality, os);
os.close();
success = true;
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to encode Bitmap", e);
}
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Do nothing.
}
}
}
return success;
} finally {
GlideTrace.endSection();
}
}
}
複製程式碼
BitmapEncoder
重寫encode()
方法,該方法中入參file
代表將要儲存的cache檔案;encode基本流程是:根據File得到輸出流OutPutStream,呼叫Bitmap.compress將bitmap寫入輸出流,然後關閉流等等處理;
DataRewinder
DataRewinder
是一個介面,作用是將流進行rewinding,主要的方法是rewindAndGet()
;Glide中有兩個有意義的實現類:InputStreamRewinder
和ByteBufferRewinder
,簡單看一下這兩個類是實現:
ByteBufferRewinder.java
public class ByteBufferRewinder implements DataRewinder<ByteBuffer> {
private final ByteBuffer buffer;
@SuppressWarnings("WeakerAccess")
public ByteBufferRewinder(ByteBuffer buffer) {
this.buffer = buffer;
}
@NonNull
@Override
public ByteBuffer rewindAndGet() {
buffer.position(0);
return buffer;
}
@Override
public void cleanup() {
// Do nothing.
}
複製程式碼
InputStreamRewinder.java
public final class InputStreamRewinder implements DataRewinder<InputStream> {
// 5mb.
private static final int MARK_LIMIT = 5 * 1024 * 1024;
private final RecyclableBufferedInputStream bufferedStream;
@Synthetic
InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {
bufferedStream = new RecyclableBufferedInputStream(is, byteArrayPool);
bufferedStream.mark(MARK_LIMIT);
}
@NonNull
@Override
public InputStream rewindAndGet() throws IOException {
bufferedStream.reset();
return bufferedStream;
}
@Override
public void cleanup() {
bufferedStream.release();
}
複製程式碼
ResourceTranscoder
ResourceTranscoder
是一個介面,作用是對兩個Resource<?>進行轉換,接受泛型<Z,R>,主要方法是transcode()
,一般泛型的接收範圍是Bitmap
、Drawable
、byte[]
等,看一下簡單的BitmapBytesTranscoder
原始碼:
public class BitmapBytesTranscoder implements ResourceTranscoder<Bitmap, byte[]> {
private final Bitmap.CompressFormat compressFormat;
private final int quality;
public BitmapBytesTranscoder() {
this(Bitmap.CompressFormat.JPEG, 100);
}
@SuppressWarnings("WeakerAccess")
public BitmapBytesTranscoder(@NonNull Bitmap.CompressFormat compressFormat, int quality) {
this.compressFormat = compressFormat;
this.quality = quality;
}
@Nullable
@Override
public Resource<byte[]> transcode(@NonNull Resource<Bitmap> toTranscode,
@NonNull Options options) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
toTranscode.get().compress(compressFormat, quality, os);
toTranscode.recycle();
return new BytesResource(os.toByteArray());
}
}
複製程式碼
載入/解析 資料轉換流程
上面介紹一堆元件和一堆泛型,資料型別的轉換到底是怎樣?搞明白這一點還得從Rigistry提供的操作方法入手:
Registry
提供getModelLoaders()
和getLoadPath()
,我們先從定義方法的泛型來看:
public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {...}
public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
@NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
@NonNull Class<Transcode> transcodeClass) {...}
複製程式碼
getModelLoader()分析
getModelLoaders()
入參型別為<Model>
,返回型別為<Model,?>
,<Model>
具體型別就是我們呼叫Glide.with().load(model)
時load()
傳入的型別,返回型別<?>
是我們在Registry
中註冊的所有符合輸入<Model>
的型別,比如InputStream
或者ByteBuffer
;
LoadPath()分析
LoadPath()
入參型別為<Data, TResource, Transcode>
,其中<Data>
是在getModelLoaders()
返回的型別,例如InputStream
或者ByteBuffer
,<TResource>
是待定型別,呼叫者一般傳?
,<Transcode>
呼叫Glide.with().as(xxx)
時as()
傳入的型別,Glide提供有asBitmap()
,asFile()
,asGif()
,預設是Drawable
型別;在呼叫時<TResource>
是待定型別,肯定有邏輯獲取它的目標型別,下面分析getLoadPath()
方法一看究竟;
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) {
List<DecodePath<Data, TResource, Transcode>> decodePaths =
getDecodePaths(dataClass, resourceClass, transcodeClass);
if (decodePaths.isEmpty()) {
result = null;
} else {
result =
new LoadPath<>(
dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool);
}
loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
}
return result;
}
複製程式碼
LoadPath()
方法從loadPathCache
獲取快取物件,如果不存在,呼叫getDecodePaths()
,經過判斷,建立LoadPath
物件,將獲取的結果放入LoadPath,最後放入loadPathCache
並返回,LoadPath
是對Data
,TResource
,Transcode
和List<DecodePath<Data, TResource, Transcode>>
的封裝,最終的邏輯還是再DecodePath
中;
看一下getDecodePaths()
方法定義:
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<>();
//獲取所有dataClass對應的ResourceClasses
List<Class<TResource>> registeredResourceClasses =
decoderRegistry.getResourceClasses(dataClass, resourceClass);**程式碼1
//遍歷registeredResourceClass
for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
//獲取所有的registeredResourceClass對應的registeredTranscodeClasses
List<Class<Transcode>> registeredTranscodeClasses =
transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);**程式碼2
//遍歷registeredTranscodeClasses
for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {
//獲取dataClass和registeredResourceClass對應的所有ResourceDecoder
List<ResourceDecoder<Data, TResource>> decoders =
decoderRegistry.getDecoders(dataClass, registeredResourceClass);**程式碼3
//獲取registeredResourceClass和registeredTranscodeClasss對應的所有ResourceTranscoder
ResourceTranscoder<TResource, Transcode> transcoder =
transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);**程式碼4
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
//建立DecodePath,把相關資訊封裝
DecodePath<Data, TResource, Transcode> path =
new DecodePath<>(dataClass, registeredResourceClass, registeredTranscodeClass,
decoders, transcoder, throwableListPool);
//新增進集合
decodePaths.add(path);
}
}
//返回集合
return decodePaths;
}
複製程式碼
為了更清楚的分析程式碼,我可以將假設泛型的型別為<InputStream,?,Drawable>
;
我在上面程式碼中做了標記:程式碼1通過呼叫transcoderRegistry.getTranscodeClasses()
,返回的型別就是泛型?
所未知的具體型別;
程式碼2通過呼叫transcoderRegistry.getTranscodeClasses()
,返回所有符合條件的registeredTranscodeClasses
;
程式碼3通過呼叫decoderRegistry.getDecoders()
獲取符合條件的List<ResourceDecoder>
;
程式碼4通過呼叫transcoderRegistry.get()
獲取符合條件的ResourceTranscoder
DecodePath是對Data, TResource, Transcode,decoders,transcoder
的封裝;
第一層for迴圈理解 由於<TResource>
是入參是未知型別,並不是使用者定義的,是Registry模組支援的中間型別,它是靠入參型別<Data>
進行篩選,所以就可能有可能有多個匹配;
第二層for迴圈理解 因為<Transcode>
是使用者傳入,這個泛型是一個已確定型別,通常是Drawable
,但是真正註冊給transcoderRegistry
可能是BitmapDrawable
或則BitmapDrawable
型別,這一刻還不確定是哪個Drawable
,所以在這一步,registry返回給呼叫者多個;
總結:
載入過程是從getModelLoader()
呼叫,資料從Model->Data;
解析過程是從getLoadPath()
呼叫,中間經過decoder、transcoder,資料型別從Data->TResource->Transcode
寫快取資料轉換流程
寫快取過程分為兩類,一類是直接將原資料快取,另一類是將變化後的資料寫快取,他們分別對應的是Encoder
和ResourceEncoder
;
Encoder流程
Encoder
的使用場景在SourceGenerator.cacheData(dataToCache)
方法中,最終通過呼叫Registry.getSourceEncoder()
獲取到Encode
;
public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {
Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());
if (encoder != null) {
return encoder;
}
throw new NoSourceEncoderAvailableException(data.getClass());
}
複製程式碼
上一節說過SourceGenerator
是對原資料的獲取,cacheData()
中拿到的 dataToCache
一般是載入過程返回的Data,確切的說是InputStream
或者ByteBuffer
型別,而Encoder
最終儲存到檔案,型別為File,所以Encoder
資料是從Data->File的;執行的時機在載入之後,和解析過程並列執行;
ResourceEncoder流程
ResourceEncoder
的使用場景是在資料解析完畢後,將處理過的資料進行快取,呼叫的地方在DecodeJob.onResourceDecoded()
方法中,其最終通過呼叫Registry.getResultEncoder()
獲取;
public <X> ResourceEncoder<X> getResultEncoder(@NonNull Resource<X> resource)
throws NoResultEncoderAvailableException {
ResourceEncoder<X> resourceEncoder = resourceEncoderRegistry.get(resource.getResourceClass());
if (resourceEncoder != null) {
return resourceEncoder;
}
throw new NoResultEncoderAvailableException(resource.getResourceClass());
}
複製程式碼
ResourceEncoder
資料型別是從Resource<X>
->File
;執行的時機在解析流程之後;
總結
最後畫一張資料簡單的資料裝換流程圖