Glide
是Android端開源圖片加載庫(kù),能夠幫助我們下載戏阅、緩存昼弟、展示多種格式圖片。也是現(xiàn)在主流圖片加載框架之一奕筐。源碼內(nèi)部究竟是如何實(shí)現(xiàn)的呢舱痘?講解主流程,簡(jiǎn)略分析救欧。
用法如下:
Glide.with(context).load(url).into(imageView);
我這里拆分為三步分析:
一衰粹、with(context)
點(diǎn)擊源碼查看到是多個(gè)重載方法activity、fragment笆怠、view等等铝耻,下面用其中一個(gè)方法來展示
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
調(diào)用getRetriever方法獲取RequestManagerRetriever
對(duì)象。在創(chuàng)建該對(duì)象之前首先通過Glide.java
中的get
方法獲得了Glide單例對(duì)象以及AppClideModule等配置蹬刷。
@NonNull
public static Glide get(@NonNull Context context) {
if (glide == null) {
GeneratedAppGlideModule annotationGeneratedModule =
getAnnotationGeneratedGlideModules(context.getApplicationContext());
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context, annotationGeneratedModule);
}
}
}
return glide;
}
下面的get方法可知道瓢捉,在子線程不會(huì)添加生命周期;主線程添加一個(gè)空白的fragment來處理生命周期办成。最后返回RequestManager對(duì)象
@NonNull
public RequestManager get(@NonNull Context context) {
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);
}
//調(diào)用get判斷線程
@NonNull
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
//子線程
return get(activity.getApplicationContext());
} else {
//主線程添加生命周期
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
二泡态、load(url)
上面執(zhí)行完成到這里已經(jīng)拿到RequestManager對(duì)象,然后調(diào)用load(url)迂卢∧诚遥看源碼可知是多個(gè)重載方法,傳不同類型的資源而克。最終拿到RequestBuilder對(duì)象
// 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);
}
三靶壮、into(imageView)
上一步拿到了RequestBuilder對(duì)象,調(diào)用into可知有2個(gè)重載方法员萍。into的參數(shù)就是最終顯示的控件腾降。
編輯
into方法內(nèi)部代碼分支很多,代碼龐大碎绎,所以只需走主流程如何顯示ImageView的實(shí)現(xiàn)即可螃壤。當(dāng)into內(nèi)部代碼執(zhí)行完成后回到 buildImageViewTarget方法抗果,這個(gè)方法是顯示使用的,通過Executors.mainThreadExecutor())來切主線程奸晴,最終顯示控件冤馏。
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions,
Executors.mainThreadExecutor());
點(diǎn)擊到into內(nèi)部源碼如下:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
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;
}
這里處理請(qǐng)求
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
將請(qǐng)求對(duì)象裝到集合中,并且有加鎖處理寄啼,運(yùn)用于多線程的并發(fā)請(qǐng)求宿接。
url請(qǐng)求走如下:
編輯
網(wǎng)絡(luò)請(qǐng)求完成callback.onDataReady(result),開始一步一步往回傳數(shù)據(jù)辕录。在這一系列過程中,進(jìn)行了數(shù)據(jù)處理梢卸,比如:圖片壓縮等走诞。 省略N步驟
// HttpUrlFetcher.java 代碼如下
@Override
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));
}
}
}
最后回到了ImageViewTarget類,顯示控件蛤高。這就是整體簡(jiǎn)略主流程蚣旱。
@Override
public void setDrawable(Drawable drawable) {
view.setImageDrawable(drawable);
}
四、緩存原理分析
當(dāng)加載圖片會(huì)走2種方式:
1戴陡、是Http/IO 塞绿;
2、三級(jí)緩存策略
一級(jí)緩存:活動(dòng)緩存 恤批,當(dāng)前Activity退出緩存銷毀异吻。
二級(jí)緩存:LRU內(nèi)存緩存 ,APP應(yīng)用退出緩存銷毀喜庞。
三級(jí)緩存:LRU磁盤緩存 诀浪,一直存在。
一延都、緩存機(jī)制加載流程:
獲取順序是雷猪,先從活動(dòng)緩存取,如果沒有就再去內(nèi)存緩存取晰房,如果還沒是沒有就再去磁盤緩存取求摇,都沒有就再去網(wǎng)絡(luò)下載。
二殊者、緩存介紹:
(1) 活動(dòng)緩存:Glide自己實(shí)現(xiàn)的一種緩存策略与境,將使用的對(duì)象存放在HashMap,里面使用的弱引用幽污,不需要時(shí)立即移除及時(shí)釋放資源嚷辅。
(2)內(nèi)存緩存:使用的LRU算法進(jìn)行處理,核心是使用 LinkedHashMap 實(shí)現(xiàn)距误,保存到內(nèi)存中簸搞。
(3)磁盤緩存:使用的LRU算法進(jìn)行處理扁位,核心是使用 LinkedHashMap 實(shí)現(xiàn),保存到磁盤中趁俊。(Glide使用DiskLruCache實(shí)現(xiàn)域仇,將圖片進(jìn)行的加密、壓縮處理寺擂,所以文件讀寫比普通IO處理效率高)
LRU的原理:假設(shè) maxSize =3暇务,當(dāng)?shù)?個(gè)數(shù)據(jù)進(jìn)入時(shí),移除最先未使用的怔软。畫圖理解一哈:
編輯
LruCache類實(shí)際上是對(duì)LinkedHashMap進(jìn)行的封裝垦细。上代碼證明:
編輯
值得注意的是,第三個(gè)參數(shù)true代表訪問排序
<pre>this.map = new LinkedHashMap<K, V>(0, 0.75f, true);</pre>
三挡逼、活動(dòng)緩存的意義
示例場(chǎng)景:加入maxSize=3時(shí)括改,有新元素添加,此刻正回收1元素家坎,剛好頁(yè)面又使用1元素嘱能。這時(shí)候如果1元素被回收,就會(huì)找不到1元素從而崩潰虱疏。所以設(shè)計(jì)了活動(dòng)緩存
編輯
增加的活動(dòng)緩存區(qū)解決上面的問題惹骂,畫圖方便理解:
編輯
總結(jié):1、當(dāng)元素在使用時(shí)做瞪,將從內(nèi)存緩存(二級(jí)緩存)移動(dòng)到活動(dòng)緩存(一級(jí)緩存)对粪;
2、當(dāng)元素未使用時(shí)穿扳,將從活動(dòng)緩存釋放資源衩侥,然后把該元素從活動(dòng)緩存移動(dòng)到內(nèi)存緩存;
三級(jí)緩存策略的使用總結(jié):
1矛物、優(yōu)先從活動(dòng)緩存讀取
2茫死、活動(dòng)緩存沒有,再內(nèi)存緩存中讀取
3履羞、內(nèi)存緩存沒有峦萎,再去磁盤緩存讀取
4、磁盤緩存沒有忆首,再去網(wǎng)絡(luò)獲取本地文件讀取