Android體系課-開源框架-這是一份詳細的Glide原始碼分析文章
前言
最近在元件化
開發中準備封裝一個圖片載入庫
,於是乎就有了這篇文章
本篇文章對Glide
原始碼過程做了一個詳細的講解,也是為了記錄下自己對Glide
的理解,以後忘記還可以從這裡查詢。
這裡我有幾點建議: - 看原始碼前先問下自己: 你為什麼去看原始碼,是為了面試?學習?或者和我一樣為了封裝一個自己的圖片載入庫? 如果是為了面試,建議先列出幾種同類型的開源庫,比如我們的圖片載入庫有Picasso,Fresco和Glide,首先你要知道他們的基本使用方式,能說出他們的優缺點,並從中挑選一個類庫去深入瞭解,因為面試官很大可能會讓你自己設計一套類似的開源庫,那這個時候你有對某個類庫做過深入瞭解,會起到事半功倍的效果,就算不能完整設計出來,也可以說出一些基本原理和大致的架構。
個人對看原始碼的思路:
先看整體框架
:然後找到一個切入點
,深入框架內部去學習。學習中間記得根據框架進行分階段總結
,這樣才能不會深陷程式碼泥潭。好了下面我們開始吧。。
整體框架圖
原始碼梳理
我們將原始碼分為三個部分來講解:
with
load
和into
這個三個部分就是我們看原始碼的切入點:
步驟1:with:
Glide.java
```java public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); }
private static RequestManagerRetriever getRetriever(@Nullable Context context) { return Glide.get(context).getRequestManagerRetriever(); }
public static Glide get(@NonNull Context context) { checkAndInitializeGlide(context); } private static void checkAndInitializeGlide(@NonNull Context context) { initializeGlide(context); }
private static void initializeGlide(@NonNull Context context) {
initializeGlide(context, new GlideBuilder());
}
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
Context applicationContext = context.getApplicationContext();
//1.獲取註解自動生成的AppGlideModule
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
List
```
--關注點1--
java
Glide build(@NonNull Context context) {
//建立SourceExecutor執行緒池
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
//建立diskCacheExecutor磁碟快取執行緒池
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
//建立animationExecutor動畫執行緒池
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
//建立記憶體大小測量器,後期會根據這個測量器測出的記憶體狀態,對快取進行操作
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
//這個類裡面對Activity或者Fragment的生命週期做了監聽,在Activity或者Fragment處於活躍狀態時去監聽網路連線狀態,在監聽中做一些重啟等操作
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
//建立一個BitMap的快取池,內部FIFO
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
//建立一個快取池列表
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
//建立一個Resource快取池
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
//建立磁碟快取池
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
//將上面建立的所有物件封裝到一個Engine物件中,以後所有需要的緩衝池或者執行緒池都在這裡面獲取即可
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
//這裡是回撥列表,使用者設定的Listener有可能會有多個
if (defaultRequestListeners == null) {
defaultRequestListeners = Collections.emptyList();
} else {
defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
}
//這裡是設定一些Glide的實驗性資料,這裡不用太過關注
GlideExperiments experiments = glideExperimentsBuilder.build();
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory, experiments);
//最後將上面建立的所有物件都儲存到一個Glide物件中,並返回
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
experiments);
}
看Glide構造方法:
```java Glide(...){ 1.建立了一個Registry,這個方法主要是用來註冊modelClass和dataClass和factory對應,由此可以通過modelClass找到對應的dataClass和factory registry = new Registry(); registry.register(new DefaultImageHeaderParser());
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
...
//這裡是一個比較重要的,標註TODO1。後面呼叫DataFetch會用到這個註冊的factory
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
..
//這裡省略了很多append呼叫:
} ```
回到呼叫 “關注點1
”的程式碼處
initializeGlide
方法講解完後,我們回撥最開始的with
呼叫方法處:
java
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
這裡有個get方法:我們進入看看
public RequestManager get(@NonNull Context context) {
//傳入的context不能為null
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper
// Only unwrap a ContextWrapper if the baseContext has a non-null application context.
// Context#createPackageContext may return a Context without an Application instance,
// in which case a ContextWrapper may be used to attach one.
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
這裡我們看到:
- 1.如果
woth
方法在子執行緒
執行,則會走到getApplicationManager(context
),這裡會建立一個全域性的Glide
,不會隨著控制元件生命週期
銷燬 - 2.如果是
主執行緒
:- 2.1:context是
FragmentActivity
,返回一個Fragment的生命週期的Glide RequestManager - 2.2:context是
Activity
,返回一個Activity的生命週期的Glide RequestManager - 2.3:context是
ContextWrapper
,返回一個ContextWrapper的生命週期的Glide RequestManager
- 2.1:context是
- 3.其他情況就返回應用層的RequestManager,
注意
:
所以在子執行緒中建立的RequestManager都是全域性應用的RequestManager,只能感知應用狀態,無法感知控制元件狀態
總結with方法:
作用:初始化
glide
:建立了多種執行緒池
,多種快取池
,將glide註冊到應用控制元件的生命週期
中,可以感知應用的記憶體狀態以及介面配置等資訊返回值:
RequestManager
步驟2:load
java
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
- 看到
asDrawable
其實就是建立了一個RequestBuilder
來看RequestBuilder
的load
方法:
java
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
//這個判斷預設為false
if (isAutoCloneEnabled()) {
return clone().loadGeneric(model);
}
this.model = model;
isModelSet = true;
return selfOrThrowIfLocked();
}
load
方法很簡單:
就是建立了一個
RequestBuilder
,並設定了請求的url
資訊
步驟3:into
這裡才是glide的重點
前面兩個步驟都是初始化操作
我們將into方法分為三個階段:
- decodeJob前
- decodeJob開始
- decodeJob結束
階段1
:decodeJob前
```java
public ViewTarget
BaseRequestOptions<?> requestOptions = this;
//這裡是設定view的scaleType,glide預設提供了四種scaleType
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
//預設是使用這個狀態設定圖片
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass), //·關注點2·
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
} ```
來看·關注點2
·
java
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z> buildTarget(
@NonNull ImageView view, @NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
可以看到
- 傳入的是Bitmap
.class.則建立BitmapImageViewTarget
- 如果是Drawable
.class,則建立DrawableImageViewTarget
並將傳入的ImageView
包裹進
步驟2 load方法中有了解過,load方法預設呼叫了asDrawable,所以返回的是Drawable型別的資料, 這裡如果需要拿到的是Bitmap的型別圖片資料,則需要呼叫asBitmap,跳過預設asDrawable方法的執行
繼續回到關注點2呼叫
的地方:呼叫了一個內部的into
方法
```java
private
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
} ```
來看關注點3
:
java
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
return buildRequestRecursive(
/*requestLock=*/ new Object(),
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
繼續看buildRequestRecursive 為了不讓大家看的頭暈,非關鍵程式碼我這邊省略了:
```java private Request buildRequestRecursive(...) {
...
//這裡建立一個縮圖的請求:關注點4
Request mainRequest =
buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
...
//這裡建立一個errorRequest請求
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
} ```
進入關注點4
```java private Request buildThumbnailRequestRecursive(...) { ... if (thumbnailBuilder != null) { Request fullRequest = obtainRequest(...) ... Request thumbRequest = thumbnailBuilder.buildRequestRecursive(...) coordinator.setRequests(fullRequest, thumbRequest); return coordinator; }else if(thumbSizeMultiplier != null){ Request fullRequest = obtainRequest(...) Request thumbnailRequest = obtainRequest() coordinator.setRequests(fullRequest, thumbnailRequest); return coordinator; }else{ return obtainRequest( requestLock, target, targetListener, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); }
}
進入obtainRequest看看:
private Request obtainRequest(
Object requestLock,
Target``
- 這裡建立了一個
SingleRequest`的請求類,先記住這裡
總結下關注點3:buildRequest
如果有要求載入縮圖請求的,會將縮圖請求和圖片真實請求放到一起。 如果沒有縮圖請求,則建立一個SingleRequest的請求返回給呼叫測
我們回撥關注點3
被呼叫處:這裡我將前面程式碼再次拷貝一次大家就不用再次向前翻找了。
```java
private
Request previous = target.getRequest();
//這裡判斷當前建立的請求和前面的請求是否是同一個請求,我們看不同的情況,所以跳過這裡
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
//請求前先呼叫clear一下這個target
requestManager.clear(target);
target.setRequest(request);
//這裡是真實請求入口 我們進去看看
requestManager.track(target, request);
return target;
} track方法: synchronized void track(@NonNull Target<?> target, @NonNull Request request) { //這裡將target放入一個targets集合中 targetTracker.track(target); //請求入口 requestTracker.runRequest(request); } runRequest方法: public void runRequest(@NonNull Request request) { requests.add(request); //如果不是暫停狀態,則呼叫begin啟動請求,如果是,則將request放入pendingRequests,延遲啟動, if (!isPaused) { request.begin(); } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); } }
```
進入begin
:
在講解SingleRequest
前,我們下來了解下一個Request
的幾種狀態:
java
private enum Status {
/** Created but not yet running. */
PENDING,
/** In the process of fetching media. */
RUNNING,
/** Waiting for a callback given to the Target to be called to determine target dimensions. */
WAITING_FOR_SIZE,
/** Finished loading media successfully. */
COMPLETE,
/** Failed to load media, may be restarted. */
FAILED,
/** Cleared by the user with a placeholder set, may be restarted. */
CLEARED,
}
這個Status
是SingleRequest
的一個內部列舉類:
- PENDING
:掛起狀態,還未執行,待執行,前面我們說過如果Request處於isPaused狀態,會新增進入pendingRequests的list中,這個裡面的Request就是處於PENDING狀態
- RUNNING
:這個狀態的Request正在去伺服器拿資料階段
- WAITING_FOR_SIZE
:等待獲取ImageView的一個尺寸階段
- COMPLETE
:成功拿到請求資料
- FAILED
:沒有獲取到請求資料,在一定情況下會重啟,例如:在網路狀態良好的時候,Glide會自動重啟請求,去獲取資料,因為Glide註冊了網路狀態的監聽
- CLEARED
:被使用者清理狀態,使用一個placeholder代替資料元,在一定情況下會重啟Request
好了,有了上面的基礎我們再來看下面程式碼:
這個request
是在關注點3
處建立的SingleRequest
```java public void begin() { synchronized (requestLock) { ... //請求處於RUNNING狀態,這個異常會被丟棄 if (status == Status.RUNNING) { throw new IllegalArgumentException("Cannot restart a running request"); } ... //請求處於COMPLETE,則回撥onResourceReady,讓應用去記憶體中獲取,這裡的DataSource.MEMORY_CACHE:標誌資料元在記憶體快取中 if (status == Status.COMPLETE) { onResourceReady( resource, DataSource.MEMORY_CACHE, / isLoadedFromAlternateCacheKey= / false); return; } ...
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());
}
}
} ```
從begin
方法中,看到對請求的狀態做了判斷,只有是未處理的請求才會去請求資料,其他情況根據具體狀態處理
我們進入onSizeReady
看看:
```java
public void onSizeReady(int width, int height) {
...
loadStatus = engine.load(...)
...
}
public
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
private
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob)
} ```
進入engineJob.start(decodeJob)
方法看看:
java
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
-
內部其實就是使用執行緒池去請求
decodeJob
: -
執行
decodeJob
前,我們先來梳理下我們階段1分析的流程
```java
RequestBuilder.java
into{
1.對ScaleType的RequestOption做處理
2.呼叫內部into方法執行
into(引數1:DrawableImageViewTarget(view),引數2:requestOptions,引數3:主執行緒執行器);
{
1.建立Request:buildRequest,返回值:SingleRequest物件
2.檢查是否和前一個請求是同一個,如果是,則直接使用前一個請求傳送begin方法,否就走第3步
3.清除當前target請求;
4.進入requestManager.track(target, request):
track{
1.呼叫requestTracker.runRequest(request):
runRequest{
//如果不是暫停狀態,則呼叫begin啟動請求,如果是,則將request放入pendingRequests,延遲啟動,
1.request.begin():這個request是一個SingleRequest物件
begin{
1.對請求狀態做判斷
2.呼叫onSizeReady
onSizeReady{
engine.load{
1.去快取中獲取資料:loadFromMemory
2.1中沒有獲取到快取資料,則呼叫waitForExistingOrStartNewJob
waitForExistingOrStartNewJob{
1.這裡面啟動//啟動engineJob,並傳入decodeJob:engineJob.start(decodeJob)
start{
1.呼叫執行緒池啟動decodeJob
}
}
}
}
}
}
}
}
} ```
階段2:decodeJob
執行階段
在講解decodeJob
執行階段前我們先來了解下Job
的幾種狀態:
java
/** Why we're being executed again. */
private enum RunReason {
/** The first time we've been submitted. */
INITIALIZE,
/** We want to switch from the disk cache service to the source executor. */
SWITCH_TO_SOURCE_SERVICE,
/**
* We retrieved some data on a thread we don't own and want to switch back to our thread to
* process the data.
*/
DECODE_DATA,
}
這個RunReason
看名字就知道:這裡是表示你啟動這個Job是要做什麼的,主要有三種狀態:
-
INITIALIZE
: 就是一個新的請求必須經過的第一步,這裡job主要任務是去快取中取資料 -
SWITCH_TO_SOURCE_SERVICE
: 就是我們希望這個job實現從磁碟服務到資源獲取階段, 這個怎麼理解呢,瞭解OkHttp原始碼都知道,OkHttp內部使用的是攔截器的模式對請求做處理,每個請求都需要經過攔截器依次處理,最後得到請求資料, 如果OkHttp發現快取攔截器中有資料,則返回,沒有資料,則執行下一個攔截器。 這裡也是一樣,每個job類似一個攔截器,每次都是優先去快取中獲取資料,也就是第一步的INITIALIZE狀態的job,如果沒有資料才會去網路中請求, SWITCH_TO_SOURCE_SERVICE就表示我們快取中沒有獲取到資料,需要啟動下一個job去網路中拿資料。 -
DECODE_DATA
: 這個很好理解,就是在非同步執行緒上獲取到資料,希望跳轉到我們自己的執行緒上去解碼資料
這裡再介紹一個內部列舉類:
java
/** Where we're trying to decode data from. */
private enum Stage {
/** The initial stage. */
INITIALIZE,
/** Decode from a cached resource. */
RESOURCE_CACHE,
/** Decode from cached source data. */
DATA_CACHE,
/** Decode from retrieved source. */
SOURCE,
/** Encoding transformed resources after a successful load. */
ENCODE,
/** No more viable stages. */
FINISHED,
}
- 這個類標識我們應該去哪個類中執行當前
Job
: 每個Job
都有對應的執行器,執行完後,呼叫startNext
執行下一個Job
,下一個job又是對應另外一個stage
的執行器
主要是在下面一個方法中使用:
java
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);
}
}
RESOURCE_CACHE
:對應ResourceCacheGenerator
執行器DATA_CACHE
:對應DataCacheGenerator
執行器SOURCE
:對應SourceGenerator
執行器
經過上面的分析,我們已經知道:
你可以把一個Job理解為一個攔截器,每次都會根據當前Job的狀態是要不同的執行器去執行任務 -
任務匯流排
:
1.ResourceCacheGenerator
:快取中獲取資料,這個資料是被修改過的降取樣快取
2.DataCacheGenerator
:也是快取中獲取資料,這個資料是沒有被修改的網路請求元資料
3.SourceGenerator
:這個執行器執行的任務才真正是用於網路請求資料
是不是和我們的OkHttp
很像?
有了上面的基礎我們再來對decodeJob
原始碼進行分析:
DecodeJob繼承了Runnable介面,我們來看他的run方法
java
public void run() {
...
runWrapped();
...
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//關注點1
stage = getNextStage(Stage.INITIALIZE);
//關注點2
currentGenerator = getNextGenerator();
//關注點3
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
runWrapped
方法中對runReason
做一個判斷:分別指向不同的請求:
//關注點1 我們看INITIALIZE,呼叫了getNextStage傳入Stage.INITIALIZE
java
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:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
這裡面是根據傳入的diskCacheStrategy
磁碟快取策略對應不同的狀態
我們假設diskCacheStrategy
.decodeCachedResource
()返回都是true
則next
關係如下:
java
INITIALIZE->RESOURCE_CACHE->DATA_CACHE->SOURCE->FINISHED
回到前面runWrapped
方法:
//關注點2 getNextGenerator獲取執行器前面已經講過,我們再發下對應關係:
RESOURCE_CACHE
:對應ResourceCacheGenerator
執行器DATA_CACHE
:對應DataCacheGenerator
執行器SOURCE
:對應SourceGenerator
執行器
//關注點3 呼叫runGenerators開始執行對應的job
```java private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
} ```
可以看到這裡有個while迴圈就是根據當前stage狀態迴圈執行不同的請求:
按前面我們的分析
:
優先會執行:
ResourceCacheGenerator
,然後DataCacheGenerator
,最後SourceGenerator
篇幅問題:我們這隻具體分析SourceGenerator的方法:其他兩個執行器前面已經介紹過
```java SourceGenerator.java public boolean startNext() { ... 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; startNextLoad(loadData); } } ... return started; }
看startNextLoad方法: private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData( helper.getPriority(), new DataCallback
try {
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
} catch (IOException e) {
throw new HttpException(
"Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e);
}
...
//這裡獲取返回code
final int statusCode = getHttpStatusCodeOrInvalid(urlConnection);
if (isHttpOk(statusCode)) {
//關注點2
return getStreamForSuccessfulRequest(urlConnection);
} else if (isHttpRedirect(statusCode)) {
String redirectUrlString = urlConnection.getHeaderField(REDIRECT_HEADER_FIELD);
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url", statusCode);
}
URL redirectUrl;
try {
redirectUrl = new URL(url, redirectUrlString);
} catch (MalformedURLException e) {
throw new HttpException("Bad redirect url: " + redirectUrlString, statusCode, e);
}
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == INVALID_STATUS_CODE) {
throw new HttpException(statusCode);
} else {
try {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
} catch (IOException e) {
throw new HttpException("Failed to get a response message", statusCode, e);
}
}
}
```
//關注點1
這裡主要是建立HttpUrlConnection
請求
```java
private HttpURLConnection buildAndConfigureConnection(URL url, Map
```
//關注點2
返回成功,則獲取InputStream
```java private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) throws HttpException { try { if (TextUtils.isEmpty(urlConnection.getContentEncoding())) { int contentLength = urlConnection.getContentLength(); stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength); } else { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding()); } stream = urlConnection.getInputStream(); } } catch (IOException e) { throw new HttpException( "Failed to obtain InputStream", getHttpStatusCodeOrInvalid(urlConnection), e); } return stream; }
繼續回到HttpUrlFeacher的loadData方法
public void loadData(
@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
...
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
} catch (IOException e) {
callback.onLoadFailed(e);//異常回調onLoadFailed介面給應用層
} finally {
...
}
}
```
把返回的InputStream
通過callback
回撥給上一級
這個callback 前面分析過:callBack = MultiFetcher
回到MultiFetcher的onDataReady
方法中:
java
public void onDataReady(@Nullable Data data) {
if (data != null) {
callback.onDataReady(data);
} else {
startNextOrFail();
}
}
看到這裡又呼叫了一次callback.onDataReady(data);
這個callback = loadData傳入下來的SourceGenerator
中的startNextLoad
方法:
```java SourceGenerator.java private void startNextLoad(final LoadData<?> toStart) { loadData.fetcher.loadData( helper.getPriority(), new DataCallback
看cb.reschedule()方法:這個cb = DecodeJob
java
DecodeJob.java
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
這個callback是EngineJob
EngineJob.java
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
getActiveSourceExecutor().execute(job);
}
到這裡又去執行了一次job,這個job 的 runReason = RunReason.SWITCH_TO_SOURCE_SERVICE
前面第一次獲取網路資料也呼叫過這個:
前面是為了獲取快取資料:這次是為了將資料寫入快取
最後將資料通過DecodeJob的onDataFetcherReady回調出去
這裡對階段2做一個總結:
- 工作
:主要負責去快取中取資料,如果沒有取到就去網路伺服器拉資料,獲取到資料後,將資料放入到快取中。
使用的是Job模式,每個Job都有一個狀態,每個狀態對於一個執行器,類似OkHttp中的攔截器模式
階段3
:解碼資料
```java 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; this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);
if (Thread.currentThread() != currentThread) {
runReason = RunReason.DECODE_DATA;
callback.reschedule(this);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
} ```
- 這個方法中又呼叫了
decodeFromRetrievedData
- 作用:解碼得到的響應資料
java
private void decodeFromRetrievedData() {
try {
//這裡解碼資料 關注點1
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
//通知應用 關注點2
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
runGenerators();
}
}
看關注點1
:
java
decodeFromData
decodeFromFetcher(data, dataSource);
runLoadPath(data, dataSource, path);
path.load(rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
result = path.decode(rewinder, width, height, options, decodeCallback);
decodeResource(rewinder, width, height, options);
decodeResourceWithList(rewinder, width, height, options, exceptions);
result = decoder.decode(data, width, height, options);
downsampler.decode(source, width, height, options);
decode(...);
decodeFromWrappedStreams
Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
result = imageReader.decodeBitmap(options);
BitmapFactory.decodeStream(stream(), /* outPadding= */ null, options);
可以看到請求資料解碼過程最後也是通過BitmapFactory.decodeStream
將資料轉換為Bitmap
返回
但是這個過程中Glide對資料做了多重處理,包括裁剪,降採
樣等操作
關注點2:notifyEncodeAndRelease
```java
private void notifyEncodeAndRelease(
...核心程式碼
notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
}
private void notifyComplete(
Resource
執行了CallResourceReady的run方法 run callCallbackOnResourceReady(cb); cb.onResourceReady(engineResource, dataSource, isLoadedFromAlternateCacheKey); cb = SingleRequest onResourceReady target.onResourceReady(result, animation); 這個target = BitmapImageViewTarget ,這裡呼叫其父類ImageViewTarget的onResourceReady方法 setResourceInternal(resource); setResource(resource);執行BitmapImageViewTarget的setResource view.setImageBitmap(resource);到這裡就執行完畢了 ```
- 總結階段3:就是對網路資料根據需求進行降取樣,並將資料設定到對應的target中
這裡對整個步驟3:into
方法做一個總結吧:
階段1
: 建立對應的網路請求,對比當前請求和記憶體中前幾個請求是否一致,如果一直,使用前一個請求進行網路請求。 去快取中獲取對應的資料,如果沒有獲取到就去網路拉取資料
階段2
: 主要負責去快取中取資料,如果沒有取到就去網路伺服器拉資料,獲取到資料後,將資料放入到快取中。 使用的是Job模式,每個Job都有一個狀態,每個狀態對於一個執行器,類似OkHttp中的攔截器模式 根據job狀態的切換,執行各自的請求
階段3
:對資料進行解碼,解碼會進行根據需求進行降取樣,並將獲取的圖片資料傳遞給對應的Target
Glide快取
這裡我們對Glide三級快取
做一個總結:因為一些面試還是經常會問到的
1.怎麼快取?
如果支援磁碟快取,則將原Source
快取到磁碟,在經過解碼處理後,將解碼後的資料快取到activeResources
,activeResources
是一個弱引用的物件
,在記憶體回收的時候,會將資料放到cache快取下面
2.怎麼使用快取
如果直接記憶體快取,則去activeResources
中取解碼後的資料,如果activeResources
中沒有對應的快取,則去cache
快取中獲取,如找到則直接返回cache資料,並將快取資料寫入activeResources中,刪除cache中的快取資料,
如果還沒有,則在呼叫網路請求前,去磁碟中獲取,磁碟中獲取的資料是圖片原資料,需要經過解碼處理才能被控制元件使用,磁碟如果還沒有,才會去網路中請求資料。
以上就是Glide對三級快取的使用機制。
總結
最後用一幅圖來整理下過程:來自 JsonChao
因為是舊版本畫的流程圖,所以有些地方可能會不一致,但是大體流程是一致的,可以參考圖片再去分析原始碼