文章目錄
- 一、前言
- 二、如何閱讀源碼
- 三、源碼解析
- 1、with()
Android Glide圖片加載框架系列文章
Android Glide圖片加載框架(一)基本用法
Android Glide圖片加載框架(二)源碼解析之with()
Android Glide圖片加載框架(二)源碼解析之load()
Android Glide圖片加載框架(二)源碼解析之into()
Android Glide圖片加載框架(三)緩存機制
一、前言
在本系列的上一篇文章中,我們學習了Glide的基本用法,體驗了這個圖片加載框架的強大功能,以及它非常簡便的API。還沒有看過上一篇文章的朋友,建議先去閱讀 Android Glide圖片加載框架(一)基本用法。
在多數情況下,我們想要在界面上加載并展示一張圖片只需要一行代碼就能實現,如下所示:
Glide.with(this).load(url).into(img);
雖說只有這簡簡單單的一行代碼,但大家可能不知道的是,Glide在背后幫我們默默執行了成噸的工作。這個形容詞我想了很久,因為我覺得用非常多這個形容詞不足以描述Glide背后的工作量,我查到的英文資料是用tons of work來進行形容的,因此我覺得這里使用成噸來形容更加貼切一些。
雖說我們在平時使用Glide的時候格外地簡單和方便,但是知其然也要知其所以然。那么今天我們就來解析一下Glide的源碼,看看它在這些簡單用法的背后,到底執行了多么復雜的工作。
二、如何閱讀源碼
在開始解析Glide源碼之前,我想先和大家談一下該如何閱讀源碼,這個問題也是我平時被問得比較多的,因為很多人都覺得閱讀源碼是一件比較困難的事情。
那么閱讀源碼到底困難嗎?
這個當然主要還是要視具體的源碼而定。比如同樣是圖片加載框架,我讀Volley的源碼時就感覺酣暢淋漓,并且對Volley的架構設計和代碼質量深感佩服。讀Glide的源碼時卻讓我相當痛苦,代碼極其難懂。當然這里我并不是說Glide的代碼寫得不好,只是因為Glide和復雜程度和Volley完全不是在一個量級上的。
那么,雖然源碼的復雜程度是外在的不可變條件,但我們卻可以通過一些技巧來提升自己閱讀源碼的能力。這里我和大家分享一下我平時閱讀源碼時所使用的技巧,簡單概括就是八個字:抽絲剝繭、點到即止
。
應該認準一個功能點,然后去分析這個功能點是如何實現的。但只要去追尋主體的實現邏輯即可,千萬不要試圖去搞懂每一行代碼都是什么意思,那樣很容易會陷入到思維黑洞當中,而且越陷越深。因為這些龐大的系統都不是由一個人寫出來的,每一行代碼都想搞明白,就會感覺自己是在盲人摸象,永遠也研究不透。如果只是去分析主體的實現邏輯,那么就有比較明確的目的性,這樣閱讀源碼會更加輕松,也更加有成效。
而今天帶大家閱讀的Glide源碼就非常適合使用這個技巧,因為Glide的源碼太復雜了,千萬不要試圖去搞明白它每行代碼的作用,而是應該只分析它的主體實現邏輯。
那么我們本篇文章就先確立好一個目標,就是要通過閱讀源碼搞明白下面這行代碼:
Glide.with(this).load(url).into(img);
到底是如何實現將一張網絡圖片展示到ImageView上面的。先將Glide的一整套圖片加載機制的基本流程梳理清楚,然后我們再通過后面的幾篇文章具體去了解Glide源碼方方面面的細節。
準備好了嗎?那么我們現在開始。
既然是要閱讀Glide的源碼,那么我們自然需要先將Glide的源碼下載下來。其實如果你是使用在 build.gradle
中添加依賴的方式將Glide引入到項目中的,那么源碼自動就已經下載下來了,在Android Studio中就可以直接進行查看。
不過,使用添加依賴的方式引入的Glide,我們只能看到它的源碼,但不能做任何的修改,如果你還需要修改它的源碼的話,可以到GitHub上面將它的完整源碼下載下來。
Glide的GitHub主頁的地址是:https://github.com/bumptech/glide
不過在這個地址下載到的永遠都是最新的源碼,有可能還正在處于開發當中。而我們整個系列都是使用Glide 4.8.0這個版本來進行講解的,因此如果你需要專門去下載4.8.0版本的源碼,可以到這個地址進行下載:https://github.com/bumptech/glide/tree/v4.8.0
三、源碼解析
1、with()
with()
方法是Glide類中的一組靜態方法,它有好幾個方法重載,我們來看一下Glide類中所有 with()
方法的方法重載:
public class Glide{...@NonNullpublic static RequestManager with(@NonNull Context context) {return getRetriever(context).get(context);}@NonNullpublic static RequestManager with(@NonNull Activity activity) {return getRetriever(activity).get(activity);}@NonNullpublic static RequestManager with(@NonNull FragmentActivity activity) {return getRetriever(activity).get(activity);}@NonNullpublic static RequestManager with(@NonNull Fragment fragment) {return getRetriever(fragment.getActivity()).get(fragment);}@SuppressWarnings("deprecation")@Deprecated@NonNullpublic static RequestManager with(@NonNull android.app.Fragment fragment) {return getRetriever(fragment.getActivity()).get(fragment);}@NonNullpublic static RequestManager with(@NonNull View view) {return getRetriever(view.getContext()).get(view);}@NonNullprivate 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();}...
}
可以看到,with()
方法的重載種類非常多,既可以傳入 Activity
,也可以傳入 Fragment
或者是 Context
。每一個 with()
方法重載的代碼都非常簡單,都是先調用 getRetriever()
方法獲取 RequestManagerRetriever
對象,然后再調用 RequestManagerRetriever
的實例 get()
方法,去獲取 RequestManager
對象。
而 RequestManagerRetriever
的實例 get()
方法中的邏輯是什么樣的呢?我們一起來看一看:
public class RequestManagerRetriever implements Handler.Callback {/*** The top application level RequestManager.*/private volatile RequestManager applicationManager;@NonNullprivate RequestManager getApplicationManager(@NonNull Context context) {// Either an application context or we're on a background thread.if (applicationManager == null) {synchronized (this) {if (applicationManager == null) {// Normally pause/resume is taken care of by the fragment we add to the fragment or// activity. However, in this case since the manager attached to the application will not// receive lifecycle events, we must force the manager to start resumed using// ApplicationLifecycle.// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context.getApplicationContext());applicationManager =factory.build(glide,new ApplicationLifecycle(),new EmptyRequestManagerTreeNode(),context.getApplicationContext());}}}return applicationManager;}@NonNullpublic 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) {return get(((ContextWrapper) context).getBaseContext());}}return getApplicationManager(context);}@NonNullpublic 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));}}@NonNullpublic RequestManager get(@NonNull Fragment fragment) {Preconditions.checkNotNull(fragment.getActivity(),"You cannot start a load on a fragment before it is attached or after it is destroyed");if (Util.isOnBackgroundThread()) {return get(fragment.getActivity().getApplicationContext());} else {FragmentManager fm = fragment.getChildFragmentManager();return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());}}@SuppressWarnings("deprecation")@NonNullpublic RequestManager get(@NonNull Activity activity) {if (Util.isOnBackgroundThread()) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));}}@SuppressWarnings("deprecation")@NonNullpublic RequestManager get(@NonNull View view) {if (Util.isOnBackgroundThread()) {return get(view.getContext().getApplicationContext());}Preconditions.checkNotNull(view);Preconditions.checkNotNull(view.getContext(),"Unable to obtain a request manager for a view without a Context");Activity activity = findActivity(view.getContext());// The view might be somewhere else, like a service.if (activity == null) {return get(view.getContext().getApplicationContext());}// Support Fragments.// Although the user might have non-support Fragments attached to FragmentActivity, searching// for non-support Fragments is so expensive pre O and that should be rare enough that we// prefer to just fall back to the Activity directly.if (activity instanceof FragmentActivity) {Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);return fragment != null ? get(fragment) : get(activity);}// Standard Fragments.android.app.Fragment fragment = findFragment(view, activity);if (fragment == null) {return get(activity);}return get(fragment);}@SuppressWarnings("deprecation")@Deprecated@NonNull@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)public RequestManager get(@NonNull android.app.Fragment fragment) {if (fragment.getActivity() == null) {throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");}if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {return get(fragment.getActivity().getApplicationContext());} else {android.app.FragmentManager fm = fragment.getChildFragmentManager();return fragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());}}@SuppressWarnings("deprecation")@Deprecated@NonNullRequestManagerFragment getRequestManagerFragment(Activity activity) {return getRequestManagerFragment(activity.getFragmentManager(), /*parentHint=*/ null, isActivityVisible(activity));}@SuppressWarnings("deprecation")@NonNullprivate RequestManagerFragment getRequestManagerFragment(@NonNull final android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) {current = new RequestManagerFragment();current.setParentFragmentHint(parentHint);if (isParentVisible) {current.getGlideLifecycle().onStart();}pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})@Deprecated@NonNullprivate RequestManager fragmentGet(@NonNull Context context,@NonNull android.app.FragmentManager fm,@Nullable android.app.Fragment parentHint,boolean isParentVisible) {RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context);requestManager =factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);current.setRequestManager(requestManager);}return requestManager;}@NonNullprivate SupportRequestManagerFragment getSupportRequestManagerFragment(@NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {SupportRequestManagerFragment current =(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingSupportRequestManagerFragments.get(fm);if (current == null) {current = new SupportRequestManagerFragment();current.setParentFragmentHint(parentHint);if (isParentVisible) {current.getGlideLifecycle().onStart();}pendingSupportRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}@NonNullprivate RequestManager supportFragmentGet(@NonNull Context context,@NonNull FragmentManager fm,@Nullable Fragment parentHint,boolean isParentVisible) {SupportRequestManagerFragment current =getSupportRequestManagerFragment(fm, parentHint, isParentVisible);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {// TODO(b/27524013): Factor out this Glide.get() call.Glide glide = Glide.get(context);requestManager =factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);current.setRequestManager(requestManager);}return requestManager;}
}
上述代碼雖然看上去邏輯有點復雜,但是將它們梳理清楚后還是很簡單的。 RequestManagerRetriever
類中看似有很多個 get()
方法的重載,什么Context參數,Activity參數,Fragment參數等等,實際上只有兩種情況而已,即傳入Application類型的參數,和傳入非Application類型的參數
。
-
Application類型參數:
如果在Glide.with()
方法中傳入的是一個Application對象
,那么這里就會調用帶有Context參數的get()方法重載,然后會在第48行調用getApplicationManager()
方法來獲取一個RequestManager
對象。其實這是最簡單的一種情況,因為Application對象的生命周期即應用程序的生命周期,因此Glide并不需要做什么特殊的處理,它自動就是和應用程序的生命周期是同步的,如果應用程序關閉的話,Glide的加載也會同時終止。 -
非Application類型參數:
不管你在Glide.with()
方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、還是app包下的Fragment,最終的流程都是一樣的,那就是會向當前的Activity當中添加一個隱藏的Fragment
。具體添加的邏輯是在上述代碼的第176行和第215行,分別對應的app包和v4包下的兩種Fragment的情況。
這里為什么要添加一個隱藏的Fragment呢?
因為Glide需要知道加載的生命周期。很簡單的一個道理,如果你在某個Activity上正在加載著一張圖片,結果圖片還沒加載出來,Activity就被用戶關掉了,那么圖片還應該繼續加載嗎?當然不應該。可是 Glide并沒有辦法知道Activity的生命周期,于是Glide就使用了添加隱藏Fragment的這種小技巧,因為Fragment的生命周期和Activity是同步的,如果Activity被銷毀了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件并停止圖片加載了
。
這里額外再提一句,從第54行代碼可以看出,如果我們是在非主線程當中使用的Glide,那么不管你是傳入的Activity還是Fragment,都會被強制當成Application來處理
。不過其實這就屬于是在分析代碼的細節了,本篇文章我們將會把目光主要放在Glide的主線工作流程上面,后面不會過多去分析這些細節方面的內容。
總體來說,第一個with()方法的源碼還是比較好理解的。其實就是為了得到一個RequestManager對象而已,然后 Glide會根據我們傳入with()方法的參數來確定圖片加載的生命周期
,并沒有什么特別復雜的邏輯。不過復雜的邏輯還在后面等著我們呢,接下來我們開始分析第二步,load()方法。