目錄結構
- 一、簡單介紹
- 二、with(context)
- 三、load(url)
- 四、into(view)
- 五、結束語
一、簡單介紹
??Glide
是純Java寫的Android端開源圖片加載庫,能夠幫助我們下載、緩存、展示多種格式圖片,也包括GIF格式。并且用法也及其簡單,只需在Gradle中添加依賴后,再在代碼中調用以下代碼便可完成圖片展示。
Glide.with(context).load(url).into(view);
這行代碼看似簡單,但是卻包含了諸多復雜的處理邏輯。秉持“知其然也要知其所以然”之精神,下面我就從源碼入手一探究竟,揭開Glide
神秘面紗。
二、with(context)
??with(xx)
函數是Glide.java
類的靜態(tài)方法,在該類中有五個重載函數,接收的參數類型有Context
、Activity
、FragmentActivity
、Fragment
和View
。
// Glide.java
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getContext()).get(fragment);
}
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
該函數創(chuàng)建了Glide
實例并初始化了一些基本參數,然后創(chuàng)建了一個RequestManager
對象并返回。總共有5個場景,這里就先選取參數為Context
類型情形進行分析。
// Glide.java
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
可以看到該函數首先調用了getRetriever(context)
獲取到了RequestManagerRetriever
對象。在創(chuàng)建該對象之前首先通過Glide.java
中的get
方法獲得了Glide
實例(Glide是一個單例),同時讀取AppGlideModule
和AndroidManifest.xml
的配置。
// Glide.java
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Glide.get(context)獲取Glide實例
return Glide.get(context).getRequestManagerRetriever();
}
public static Glide get(@NonNull Context context) {
if (glide == null) {
// 加載AppGlideModule
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
// 加載Mainfest配置、注冊模塊回調
// 這一步執(zhí)行了 Glide.build()方法構造Glide實例。build方法下面會講到
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
獲取到Glide
實例后,緊接著調用getRequestManagerRetriever
方法返回了上一步已經初始化好的RequestManagerRetriever
對象。
// Glide.java
public RequestManagerRetriever getRequestManagerRetriever() {
return requestManagerRetriever;
}
接著再看一看RequestManagerRetriever
是如何被初始化的,以及初始化過程中都干了哪些事。首先貼源碼看看Glide.build
方法內部具體實現(該方法在上述checkAndInitializeGlide()
函數中被調用):
// GlideBuilder.java
Glide build(@NonNull Context context) {
// 分配線程池、配置緩存策略
sourceExecutor = GlideExecutor.newSourceExecutor();
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
animationExecutor = GlideExecutor.newAnimationExecutor();
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
// 監(jiān)聽網絡變化
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
// engine是負責執(zhí)行加載任務的
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
if (defaultRequestListeners == null) {
defaultRequestListeners = Collections.emptyList();
} else {
defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
}
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
isLoggingRequestOriginsEnabled,
isImageDecoderEnabledForBitmaps);
}
通過源碼我們可以了解到在執(zhí)行Glide.get()
方法時就已經分配好了資源加載、緩存線程池、配置好了緩存策略,這里的engine
專門負責加載、解碼資源(內部邏輯后面再講),ConnectivityMonitor
注冊了網絡狀態(tài)監(jiān)聽器,當網絡斷開時暫停請求網絡資源,重連后繼續(xù)請求資源?;貧w主題,注意到RequestManagerRetriever
是原來是通過RequestManagerFactory
工廠類構造的。進入到RequestManagerFactory.java
類中,可以看到get
方法獲取到了相應的RequestManager
對象。從這里我們可以發(fā)現,無論哪種情況,當App進入后臺后會導致頁面不可見,此時RequestManager綁定到了ApplicationContext,與App的生命周期一致,因此在RequestManager.java
類中也實現了生命周期相關的回調函數。
// RequestManagerRetriever.java
// get有好幾個重載方法,這里僅選取context參數進行分析
public RequestManager get(@NonNull Context context) {
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
&& ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
至此,執(zhí)行完Glide.with(context)
后我們拿到了一個對應的RequestManager
對象,接下來就執(zhí)行下一個任務load(url)
。
三、load(url)
上一步已經拿到了RequestManager
,緊接著調用load
方法開始執(zhí)行下一步操作,同樣先看看load方法的實現。
// RequestManager.java
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {
return asDrawable().load(bitmap);
}
public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
return asDrawable().load(drawable);
}
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
return asDrawable().load(uri);
}
public RequestBuilder<Drawable> load(@Nullable File file) {
return asDrawable().load(file);
}
public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
return asDrawable().load(resourceId);
}
public RequestBuilder<Drawable> load(@Nullable URL url) {
return asDrawable().load(url);
}
public RequestBuilder<Drawable> load(@Nullable byte[] model) {
return asDrawable().load(model);
}
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
??load()
同樣有多個重載函數,傳入的參數可以是圖片對象Bitmap
、Drawable
、本地資源Uri
、在線資源路徑Url
、文件對象File
、assets資源的id
,這里我們只看參數為Url
的情形。
??asDrawable().load(url)
返回了一個RequestBuilder
對象,首先看看asDrawable
方法干了什么。
// RequestManager.java
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
方法創(chuàng)建了RequestBuilder
對象,然后調用RequestBuilder.java
中的load
方法,這一步做的事比較少,沒什么好講的了。
// RequestBuilder.java
// 傳入的String類型的url將會被作為緩存的key
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
// 這里返回了自身
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
??總結一下,load
函數主要工作就是根據傳入的資源類型,構造了一個相應的RequestBuilder
對象。至此一切準備工作準備就緒,接下來就是最為重要的一步了-加載、展示文件,讓我們來著看into(view)
方法如何完成這些任務。
四、into(view)
??上一步最終拿到的是對應類型RequestBuilder
實例,那么就看看該類里into
方法的具體實現。同樣into方法有into(@NonNull Y target)
和into(@NonNull ImageView )
兩個重載函數(這兩個函數最終都會走到同一個函數中),由于調用into
方法時我們傳入的參數是ImageView類型的,所以這里就以后者為例進行分析。
// RequestBuilder.java
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
BaseRequestOptions<?> requestOptions = this;
// View's scale type.
// 處理圖片縮放,根據縮放類型來初始化對應的requestOptions對象
......
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor() // 運行在主線程的handler
);
}
上面代碼段首先處理圖片縮放類型(裁剪、對齊方式等),并將生成的相關參數放入了requestOptions
對象中,然后再將其作為參數傳給了RequestBuilder.java
類私有方法into
。該方法定義的四個參數分別為:1、viewTarget,2、target回調監(jiān)聽器,3、請求參數,4、主線程的回調函數。
??顯然外部傳入ImageView
對象最終被轉換成了ViewTarget
對象,轉換函數便是glideContext.buildImageViewTarget(view, transcodeClass)
。
// GlideContext.java
public <X> ViewTarget<ImageView, X> buildImageViewTarget(@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
ViewTarget
又是由ImageViewTargetFactory
工廠方法生成,接著再看buildTarget
方法是如何生成ViewTarget
對象。
// imageViewTargetFactory.java
public class ImageViewTargetFactory {
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)");
}
}
}
可以看到無論傳入參數是何種類型,最終都會轉換成兩種類型的ViewTarget :1、BitmapImageViewTarget
;2、DrawableImageViewTarget
。這里如何選擇取決于asBitmap()
、asGif()
和asDrawable()
函數是否被調用,默認是Bitmap
類型,所以這里默認返回的是BitmapImageViewTarget
。
// BitmapImageViewTarget.java
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
public BitmapImageViewTarget(ImageView view) {
super(view);
}
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource); // 顯示圖片
}
}
至此ViewTarget
創(chuàng)建完畢,我們再回到RequestBuilder.java
私有into
方法。代碼重新貼一遍,省得再往前翻了。
// RequestBuilder.java`
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
// 注釋1:創(chuàng)建request
Request request = buildRequest(target, targetListener, options, callbackExecutor);
// 獲取前一個reqeust請求對象
Request previous = target.getRequest();
// 與上一個請求相同 并且 上一個請求已完成
if (request.isEquivalentTo(previous)&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// 上一個請求已完成,那么重新啟動它
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
// 與上一個請求不同,則清除掉上一個,再將加入新請求
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
順著代碼次序,來看看這個方法每一步都干了什么:
- 第一步:首先執(zhí)行
buildRequest
方法創(chuàng)建一個新的Request
請求req1
; - 第二步:獲取當前
ViewTarget
上正在進行中的Request
請求req2
; - 第三步:判斷新建的請求
req1
與已有的請求req2
是否相同,如果相同則判斷是否跳過req2
請求的緩存,兩個條件都滿足則開始執(zhí)行begin()
方法開始請求資源并停止往下執(zhí)行,條件都不滿足則繼續(xù)執(zhí)行第四步; - 第四步:給
ViewTarget
設置最新的請求req1
,然后執(zhí)行track
方法追蹤req1
。
總結一下,執(zhí)行into(view)
方法首先獲取到了Request
請求,然后開始執(zhí)行Request
。如果是復用的Request
則直接執(zhí)行begin()
,否則執(zhí)行track(target, request)
,但最終仍然會執(zhí)行begin()
。
// ReqeustManager.java
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
// 與lifecycle綁定
targetTracker.track(target);
// 啟動reqeust
requestTracker.runRequest(request);
}
// RequestTracker.java
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin(); // 立即開始加載
} else {
//防止從以前的請求中加載任何位圖,釋放該請求所擁有的任何資源,顯示當前占位符(如果提供了該占位符),并將該請求標記為已取消。
// request.java( Interface )
request.clear();
pendingRequests.add(request); // 加入隊列等待執(zhí)行
}
}
??我們首先看看track方法的源碼,先是執(zhí)行targetTracker.track(target)
監(jiān)聽ViewTarget
的請求,然后runRequest
開始執(zhí)行。由于最終都是通過begin()
方法開始請求,所以我們先來看看begin()
方法的具體實現。
??Request
類是interface
類型,begin()
它的抽象方法,所以我們要想弄清楚begin()
的具體實現,那就要先找到Request
的實現類,從buildRequest(xx)
方法入手,同樣先貼出源碼:
// RequestBuilder.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);
}
private Request buildRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
// 請求出錯了
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
// 無法確認完成請求和縮略圖請求哪個先完成,所以當縮略圖比完成請求后完成時就不再顯示縮略圖
Request mainRequest =
buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
// 請求成功了,直接返回縮略圖Request
if (errorRequestCoordinator == null) {
return mainRequest;
}
// ...
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
// 同時返回縮略圖請求和錯誤請求
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
顯然代碼里的mainRequest
就是我們要找的Request
了,它是由buildThumbnailRequestRecursive
方法返回的,深入其內部我們發(fā)現Request
最終其實是由SingleRequest.obtain
方法產生,也就是說我們最終拿到的Request
其實就是SingleReqeust
類的一個實例。這里過程比較簡單,代碼就不貼出來了。我們直接去SingleReqeust
類里面 看看begin
方法如何實現的.
// SingleReqeust.java
public void begin() {
if (status == Status.COMPLETE) {
// 資源已下載,直接回調
// 執(zhí)行動畫
onResourceReady(resource, DataSource.MEMORY_CACHE);
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
方法后首先判斷如果資源已經過加載好了則直接回調onResourceReady
顯示圖片并緩存,否則測量出圖片尺寸后再開始加載圖片(onSizeReady()中執(zhí)行加載任務)并同時顯示占位圖。
1、
overrideWith
、overrideHeight
通過override(width, height)
設置:
Glide.with(mContext).load(url).override(75, 75).into(imageView);2、占位圖是用戶調用
placeholder(resId)
設置:
Glide.with(mContext).load(url).placeholder(resId).into(imageView);
接著再看onSizeReady()
測量完圖片尺寸后如何加載圖片的:
// SingleRequest.java
@Override
public void onSizeReady(int width, int height) {
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
// 獲取圖片尺寸
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
// 開始加載任務
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
}
從上述源碼可以看到真正的下載任務是在Engine
類的load
方法中實現的,其中也涉及到了圖片緩存邏輯,很復雜。
五、結束語
??至此,Glide初始化、顯示占位圖、圖片封面的整個業(yè)務流程都走完了。接著往下走就進入資源加載邏輯和緩存策略了,由于這塊內容邏輯最復雜且最重要,所以就不打算放到這篇文章來講了,計劃后續(xù)安排時間再次深入學習并做專題分享。
再說說讀完源碼之后的幾點個人感悟:
1、一個好的框架在結構設計上一定是非常清晰明確的;
在源碼中有大量的
Interface
,閱讀時往往需要去尋找它的定義和具體實現,實際上很快就能找到對應的點,不需要耗費太多的時間。這主要是得益于源碼清晰的結構設計,通過對應的包名和一看就懂的字段名想找到對應的點簡直太容易了。主要業(yè)務代碼的聚合和分離拿捏地很準,聚而不亂、分而不散,讀起來既省時又省心讓人讀而不倦。
2、學習設計模式不僅有助于閱讀、理解源碼,也是自己寫出優(yōu)秀代碼的必備基礎;
源碼中一眼就能看出來的設計模式有
單例模式
、工廠方法
,再加上諸多其他不是很容易看出來的設計模式共同構筑了代碼的骨架。所以設計模式是閱讀源碼的必備基礎,否則即使能讀懂源碼也很難讀懂結構設計的核心思想,更別提自己能寫出多么優(yōu)秀的代碼了。
3、讀源碼是程序員提升自我修養(yǎng)的不二法門。
深知優(yōu)秀的程序員不是憑空冒出來的,而是要“站在巨人的肩膀上”并經過不斷地學習實踐再發(fā)揮,一步步成長起來的。那么程序員要找的“巨人”是誰呢?我覺得那就是大牛寫出來的優(yōu)秀源碼。讀源碼既是一個學習、理解、接受他人優(yōu)秀思想的重要途徑,也是自我審視、提升最高效的方式。