最近比較無聊,為了找點事幹,就花了兩天時間把Glide的原始碼大概看了一下。剛開始看Glide的原始碼頭腦還是比較亂的,因為作者引入了幾個概念,又大量用了泛型,如果不瞭解這些概念讀起程式碼來就比較痛苦,我也沒有詳細看各種實現細節的東西,只是瞭解了下這個框架的大概樣子,在這篇文章裡,我會介紹下Glide中的一些關鍵概念,並走一遍圖片載入流程,如果你要閱讀Glide原始碼的話,應該多少會有點幫助。
基本概念
首先是三個最基本的概念:Model
, Data
和Resource
。
想一下,我們載入圖片需要什麼?一般是一個url,但url並不是所有情況,還有資源ID,檔案等等,甚至可以是Feed流中的一條Feed,雖然一般我們會從Feed中取出圖片的url來轉換為從url中載入的情況,Glide把這些抽像為了一個概念,就是Model
,所以Model
就是資料地址的最初來源。
Model
並不能直接解析為圖片,比如一個url,是要轉換為網路流的InputStream才能被解析為圖片的,Model
需要進行一次轉換才能做為資料解析的資料來源,這些轉換後的東西就叫做Data
,Glide並沒有一個Data類,但有很多和它相關的概念,如dataClase,DataFetcher等。
那麼Resource
呢,其實它就是一個包裝類,一個wrapper,它wrap一個物件,使這個物件可以通過物件池進行快取與重用。
這三個基本概念介紹完了,接下來看一下Glide基本框架。
做為一個圖片載入框架,肯定會包含快取部分。
可以從網上很容易的瞭解到,Glide的磁碟快取可以快取原始資料,也可以快取處理過的資料。什麼意思呢,就是你有一張1000x1000的圖片,但你是在列表中展示的,比如是200x200,那麼快取時可以直接將整個網路流快取下來,即1000x1000的圖片,要展示的時候再縮放,但這就降低了展示效率,所以Glide也可以把處理過的200x200的圖片快取起來,增加了快取大小,但優化了展示速度。
至於怎麼把資料快取到磁碟,就引入了一個叫Encoder
的概念,Encoder
是用來持久化資料的。
但看原始碼時你會發現,Glide中有一個類叫Registry
,可以註冊多個Encoder
,但你會發現它還可以註冊ResourceEncoder
。這兩個Encoder
很容易引起混淆,而其實ResouseEncoder
繼承自Encoder
。Encoder
是用來持久化Data
的,ResourceEncoder
是用來持久化Resource
的。看Glide預設註冊的Encoder
就知道了,預設註冊的Encoder
為ByteBuffer
和InputStream
,而ResourceEncoder
是Bitmap
、BitmapDrawable
和GifDrawable
,也就是一個持久化原始資料,一個持久化處理過的資料。我感覺把Encoder
做為一個上級的抽象,引入一個和ResourceEncoder
同級的DataEncoder
就好理解了,正好和前面的基本概念Data
和Resource
對應。
有Encoder
就有Decoder
,對應的類叫ResourceDecoder
,用來將資料(InputStream等)解析為Resource
。
圖片載入出來後還可能會應用各種變換,如圓角圖片,圓形圖片,處理這部分工作的叫Transformation
基礎概念介紹的差不多了,載入流程也差不多出來了:
但我們發現前面的介紹中少了一環,即:Glide是怎麼把Model
轉換為Data
的。這就引入另一個概念,ModelLoader
,就是把Model
轉換成Data
的,為了方便說明,直接把這個類的程式碼貼上來了,去掉了一些註釋。
/**
* A factory interface for translating an arbitrarily complex data model into a concrete data type
* that can be used by an {@link DataFetcher} to obtain the data for a resource represented by the
* model.
*
* @param <Model> The type of the model.
* @param <Data> The type of the data that can be used by a
* {@link com.bumptech.glide.load.ResourceDecoder} to decode a resource.
*/
public interface ModelLoader<Model, Data> {
/**
* Contains a set of {@link com.bumptech.glide.load.Key Keys} identifying the source of the load,
* alternate cache keys pointing to equivalent data, and a
* {@link com.bumptech.glide.load.data.DataFetcher} that can be used to fetch data not found in
* cache.
*
* @param <Data> The type of data that well be loaded.
*/
class LoadData<Data> {
public final Key sourceKey;
public final List<Key> alternateKeys;
public final DataFetcher<Data> fetcher;
public LoadData(Key sourceKey, DataFetcher<Data> fetcher) {
this(sourceKey, Collections.<Key>emptyList(), fetcher);
}
public LoadData(Key sourceKey, List<Key> alternateKeys, DataFetcher<Data> fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
LoadData<Data> buildLoadData(Model model, int width, int height, Options options);
boolean handles(Model model);
}
ModelLoader
有兩個方法,一個handles
表示是否可以處理這個型別的Model
,如果可以的話就可以通過buildLoadData
生成一個LoadData
,而LoadData
包含了要用來做快取的key,及用來獲取資料的DataFetcher
。
到這裡,整個載入流程就清楚了:
基本載入流程
接下來要做的就是根據我們的使用方法走一遍流程,呼叫如下:
Glide.with(mContext)
.load(url)
.apply(RequestOptions.placeholderOf(R.drawable.loading))
.into(myImageView);
一步步看,先是Glide.with(mContext)
:
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
通過RequestManagerRetriever
獲取到了一個RequestManager
,至於為什麼還需要一個RequestManagerRetriever
並有各種過載方法,主要是因為Glide通過SupportRequestManagerFragment
和RequestManagerFragment
關聯了Activity或Fragment的生命週期,用來做pauseRequests
等操作。
然後是load
:
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}
public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide.getGlideContext(), this, resourceClass);
}
是asDrawable.load(model)
的縮寫,就是說這個Model我是要載入為Drawable的,最終返回一個RequestBuilder
,看名字就知道是做什麼了,不過這個類主要是設定Thumbnail Request,Transition等個別設定(舊版本中placeHolder等也是在這裡設定的),大部分設定在RequestOptions
裡,這就是下面這一句:
apply(RequestOptions.placeholderOf(R.drawable.loading))
應用一個RequestOptions
,RequestOptions
可以設定各種請求相關的選項,如佔點陣圖片,載入失敗的圖片,快取策略等。RequestOptions
繼承自BaseRequestOptions
,但全是工廠方法生成各種RequestOptions。
最後就是into
了,把圖片載入到一個Target
中。
public Target<TranscodeType> into(ImageView view) {
...
return into(context.buildImageViewTarget(view, transcodeClass));
}
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
Target
是要載入到的目標,比如ImageViewTarget
,AppWidgetTarget
,在這裡我們傳進來了一個ImageView
,內部生成了一個DrawableImageViewTarget
。這裡最主要的操作是buildRequest
然後交給RequestManager
去track
。
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
// RequestTracker
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
TargetTracker
主要就是記錄一下所有正在載入的圖片的Target
,所以載入行為是在RequestTracker.runRequest
中的,runRequest
先判斷是否是pause狀態(RequestManager設定),如果不是就直接呼叫Request.begin
觸發載入,否則就回到pending佇列裡等待resume。
除了設定縮圖的情景,使用的Request
都是SingleRequest
,看一下它的begin
方法:
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
載入邏輯是這幾行:
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
判斷下是否知道Target
的大小,如果大小已知就呼叫onSizeReady
,否則就呼叫target.getSize
獲取它的大小,當成功獲取到大小後,會通過回撥繼續呼叫onSizeReady
,所以整個載入方法都是在onSizeReady
裡的。至於Target
怎麼獲取它的大小,那要看它的實現了,對於ImageViewTarget
,是通過ViewTreeObserver.OnPreDrawListener
等到View要測繪的時候就知道它的大小了。
onSizeReady
就是把操作轉移到了Engine.load
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
useUnlimitedSourceExecutorPool);
DecodeJob<R> decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
在Engine.load
中,先loadFromCache
,如果快取沒有命中就再loadFromActiveResources
,這是兩級記憶體快取,第一級是LruCache,第二級是ActiveCache,主要作用是,有可能一個圖片很早就被載入了,可能已經從LruCache被移除掉了,但這個圖片可能還在被某一個地方引用著,也就是還是Active的,那它就可能在將來仍被引用到,所以就把它保留在二級的ActiveCache中,ActiveCache中是以弱引用引用圖片的,並通過ReferenceQueue
監測弱引用的回收,然後用Handler.IdleHandler
在CPU空閒時被被回收的引用項從ActiveCache中移除。
接下來看對應的Key是否已經正在載入,如果是的話,就addCallback
,這樣如果有多個地方同時請求同一張圖片的話,只會生成一個載入任務,並都能收到回撥,這點是比Universal-Image-Loader好的地方。
正常的載入流程是生成一個EngineJob
和一個DecodeJob
,通過engineJob.start(decodeJob)
來進行實際的載入。
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
EngineJob.start
直接將DecodeJob
交給Executor去執行了(DecodeJob
實現了Runnable
介面)。DecodeJob
的載入操作放到了runWrapped
中
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
主要載入邏輯就在這三個函式中了:
- 先獲取當前的Stage
- 根據當前的Stage獲取相應的Generator,
- 執行Generator
一共有三種Generator:
ResourceCacheGenerator
:從處理過的快取載入資料DataCacheGenerator
:從原始快取載入資料SourceGenerator
:從資料來源請求資料,如網路請求
前面說過,Glide的磁碟快取可以選擇快取原始圖片,快取處理過的圖片(如列表中顯示縮圖時縮放後的圖片),這三個Generator就分別對應處理過的圖片快取,原始圖片快取,和資料來源載入。
在上面的第三步執行Generator時主要就是呼叫了Generator,其實就是執行Generator的startNext
方法,這裡以SourceGenerator
為例。
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
先忽略函式開始時dataToCache
和sourceCacheGenerator
相關的程式碼,第一次載入時這兩個一定是null的。剩下的流程就是獲取一個LoadData
,呼叫LoadData.fetcher.loadData
載入資料。看一下LoadData
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
int size = modelLoaders.size();
for (int i = 0; i < size; i++) {
ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
LoadData<?> current =
modelLoader.buildLoadData(model, width, height, options);
if (current != null) {
loadData.add(current);
}
}
}
return loadData;
}
在getLoadData
中通過獲取所有提前註冊過的能處理Model
型別的ModelLoader
,呼叫它的buildLoadData
生成LoadData
,最終返回一個LoadData
列表。
前面說過LoadData
包含了用來獲取資料的DataFetcher
。SourceGenerator.startNext
就呼叫了loadData.fetcher.loadData
來進行載入資料,並傳進去一個Callback,就是當前的SourceGenerator
,如果載入成功,會呼叫onDataReady
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);
}
}
資料載入成功後,如果設定了要進行磁碟快取,會設定成員變數dataToCache
,並呼叫Callback的reschedule
,結果就是會再次呼叫當前Generator的startNext
,startNext
的前半部分實現就起作用了,會進行寫快取的操作。
當rescheudle
後寫了快取後,或不快取的情況下,會呼叫onDataFetcherReady
,這個Callback就是前面的DecodeJob
,在onDataFetcherReady
中會呼叫decodeFromRetrievedData
decode資料,最終呼叫到decodeFromFetcher
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
,並呼叫它的load
方法。LoadPath
就是封裝了多個DecodePath
,DecodePath
用於decode and Transform資料,如InputStream->Bitmap->BitmapDrawable,DecodePath
中會獲取預先註冊的Decoder
來decode獲取到的資料,decode成功後通過回撥呼叫DecodeJob
的onResourceDecoded
方法。
public Resource<Z> onResourceDecoded(Resource<Z> decoded) {
Class<Z> resourceSubClass = getResourceClass(decoded);
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(decoded, width, height); ////////////////////////// 1
}
// TODO: Make this the responsibility of the Transformation.
if (!decoded.equals(transformed)) {
decoded.recycle();
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
encodeStrategy)) {
if (encoder == null) {
throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
}
final Key key;
if (encodeStrategy == EncodeStrategy.SOURCE) {
key = new DataCacheKey(currentSourceKey, signature);
} else if (encodeStrategy == EncodeStrategy.TRANSFORMED) {
key = new ResourceCacheKey(currentSourceKey, signature, width, height,
appliedTransformation, resourceSubClass, options);
} else {
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult); ////////////////////////// 2
result = lockedResult;
}
return result;
}
在上述程式碼的註釋1處對載入成功的資源應用Transformation,然後在註釋2處根據快取策略初始化DeferredEncodeManager
,在前面的decodeFromRetrievedData
中,如果有必要會把transform過的資源寫快取。
private void decodeFromRetrievedData() {
...
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
notifyEncodeAndRelease
中處理了對處理過的圖片的快取操作。當快取完成後(如果有需要的話)就通過回撥告訴外面載入完成了。至此,整個載入過程完成。
Glide配置
Glide允許我們進行一定程度的自定義,比如設定自定義的Executor,設定快取池,設定Log等級等,完成這個任務的類叫GlideBuilder
,Glide類在工程中是作為單例使用的,看一下程式碼:
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide.registry);
}
}
}
}
return glide;
}
通過GlideBuilder
生成了一個Glide
例項,我們是沒有辦法直接配置GlideBuilder
的,但我們發現Glide.get
解析了Manifest,獲取了一個GlideModule
的列表,並呼叫了它的applyOptions
和registerComponents
方法。以專案中OkHttp的配置為例
public class OkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Do nothing.
}
@Override
public void registerComponents(Context context, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
GlideModule
有兩個方法,applyOptions
,有一個GlideBuilder
引數,在這裡我們就可以配置Glide了。還有一個registerComponents
方法,並有一個Registry
引數,通過這個類的例項我們就可以註冊我們自定義的ModelLoader
,Encoder
等基礎元件了。
自定義GlideModule
是通過Manifest的meta-data標籤配置的
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule"/>
參考資料
http://www.lightskystreet.com/2015/10/12/glide_source_analysis/