Glide原始碼導讀

AngelDevil發表於2016-09-05

最近比較無聊,為了找點事幹,就花了兩天時間把Glide的原始碼大概看了一下。剛開始看Glide的原始碼頭腦還是比較亂的,因為作者引入了幾個概念,又大量用了泛型,如果不瞭解這些概念讀起程式碼來就比較痛苦,我也沒有詳細看各種實現細節的東西,只是瞭解了下這個框架的大概樣子,在這篇文章裡,我會介紹下Glide中的一些關鍵概念,並走一遍圖片載入流程,如果你要閱讀Glide原始碼的話,應該多少會有點幫助。

基本概念

首先是三個最基本的概念:Model, DataResource

想一下,我們載入圖片需要什麼?一般是一個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繼承自EncoderEncoder是用來持久化Data的,ResourceEncoder是用來持久化Resource的。看Glide預設註冊的Encoder就知道了,預設註冊的EncoderByteBufferInputStream,而ResourceEncoderBitmapBitmapDrawableGifDrawable,也就是一個持久化原始資料,一個持久化處理過的資料。我感覺把Encoder做為一個上級的抽象,引入一個和ResourceEncoder同級的DataEncoder就好理解了,正好和前面的基本概念DataResource對應。

Encoder就有Decoder,對應的類叫ResourceDecoder,用來將資料(InputStream等)解析為Resource

圖片載入出來後還可能會應用各種變換,如圓角圖片,圓形圖片,處理這部分工作的叫Transformation

基礎概念介紹的差不多了,載入流程也差不多出來了:

sequence1

但我們發現前面的介紹中少了一環,即: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

到這裡,整個載入流程就清楚了:

sequence2

基本載入流程

接下來要做的就是根據我們的使用方法走一遍流程,呼叫如下:

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通過SupportRequestManagerFragmentRequestManagerFragment關聯了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))

應用一個RequestOptionsRequestOptions可以設定各種請求相關的選項,如佔點陣圖片,載入失敗的圖片,快取策略等。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是要載入到的目標,比如ImageViewTargetAppWidgetTarget,在這裡我們傳進來了一個ImageView,內部生成了一個DrawableImageViewTarget。這裡最主要的操作是buildRequest然後交給RequestManagertrack

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);
 }
}

主要載入邏輯就在這三個函式中了:

  1. 先獲取當前的Stage
  2. 根據當前的Stage獲取相應的Generator,
  3. 執行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;
}

先忽略函式開始時dataToCachesourceCacheGenerator相關的程式碼,第一次載入時這兩個一定是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包含了用來獲取資料的DataFetcherSourceGenerator.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的startNextstartNext的前半部分實現就起作用了,會進行寫快取的操作。

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就是封裝了多個DecodePathDecodePath用於decode and Transform資料,如InputStream->Bitmap->BitmapDrawable,DecodePath中會獲取預先註冊的Decoder來decode獲取到的資料,decode成功後通過回撥呼叫DecodeJobonResourceDecoded方法。

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的列表,並呼叫了它的applyOptionsregisterComponents方法。以專案中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引數,通過這個類的例項我們就可以註冊我們自定義的ModelLoaderEncoder等基礎元件了。

自定義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/

相關文章