Android Glide圖片加載框架(二)源碼解析之with()

文章目錄

  • 一、前言
  • 二、如何閱讀源碼
  • 三、源碼解析
    • 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()方法。




本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446764.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446764.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446764.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

codeforces 1A-C語言解題報告

1A題目網址 題目解析 1.使用axa面積的方塊去鋪滿n*m面積的廣場,方塊之間可以覆蓋,可以比廣場面積大,求最小使用的方塊數目 2.因為可以覆蓋,所以求取每一邊(n或m)能夠鋪多少的a,即為求取n/a向上取整,up(n/a)(na-1)/a,再把它們乘起來 number((na-1)/a)*((ma-1)/a); 3.因為有很…

計算機操作系統生產者和消費者模型的簡單介紹

同步互斥小口訣 畫圖理解題目判斷題目類型分析進程數目 填寫進程模板補充基本代碼(偽代碼)補充PV代碼檢查調整代碼 注意事項 代碼是一步一步寫出來的&#xff0c;代碼是反復調整寫出來的60%是生產者和消費者模型30%是讀者和寫者的模型 生產者和消費者 例子1 媽媽每次放放一…

Android Glide圖片加載框架(二)源碼解析之load()

文章目錄一、前言二、源碼分析1、load()Android Glide圖片加載框架系列文章 Android Glide圖片加載框架&#xff08;一&#xff09;基本用法 Android Glide圖片加載框架&#xff08;二&#xff09;源碼解析之with() Android Glide圖片加載框架&#xff08;二&#xff09;源碼…

codeforces 231A-C語言解題報告

231A題目網址 題目解析 1.輸入比賽的題目,每一道題3個人的解題狀況(0或1),輸出總的解答題目(每一個題至少有2個,記1次) 2.在for循環中去錄入每道題的解答狀況 3.因為有題目解答的計算,所以直接用原始的解答狀況去計算 if(result[0]result[1]result[2]3) { count; } 知識點 …

英語一段落排序題技巧

總述 1.做此題不用通篇理解,找第一句和最后一句,理清楚里面的邏輯即可 首段 1.使用特殊疑問句提出問題,符合首段特點 連貫詞 1.in one case…in another case 兩個段落連在一起 2.some…others 隱式:most sites等同于others 邏輯 1.總分關系 調查–地面調查

計算機操作系統讀者和寫者模型的簡單介紹以及思考

讀者和寫者 讀寫兩組進程&#xff0c;共享一個文件&#xff0c;多個讀者可以同時訪問文件&#xff0c;多個寫者不可以同時訪問文件&#xff0c;寫者和讀者也不可以同時訪問文件共享讀&#xff1b;獨占寫特征:1,資源被誰占有&#xff1b;2&#xff0c;寫者改變資源&#xff0c;…

Android Glide圖片加載框架(二)源碼解析之into()

文章目錄一、前言二、源碼解析1、into(ImageView)2、GlideContext.buildImageViewTarget()3、RequestBuilder.into(Target,RequestListener,RequestOptions);4、RequestBuilder.buildRequest()5、SingleRequest.obtain()6、isEquivalentTo()、isSkipMemoryCacheWithCompletePre…

codeforces 158A-C語言解題報告

158A題目網址 題目解析 1.輸入n個選手,和第k個參考選手 再輸入每一個選手的比賽成績 2.輸出比賽成績>第k個選手的入選人數 3.n,k都是從1開始計算的 4.因為要比較所有的選手與第k個選手,所以使用數組,而且要使用兩個循環(第一個輸入數據,第二個比較成績) 5.不要忘記scanf都…

通過字符串的方式讀取文件的內容

代碼 std::ifstream file("1.txt");std::stringstream data;data << file.rdbuf();printf(data.str().c_str());file.close(); 頭文件 #include <cstdio> #include <cstdlib> #include <fstream> #include <sstream>

2014年考研英語一翻譯知識點

題目講解網址 總結 1.做翻譯題,不用看句子前后的地方,直接看要翻譯的部分 2.多根據語境去翻譯 3.如果是不認識的單詞,一般都是我們平常經常使用/說的詞的代替高級詞 題目句子 It is also the reason why when we try to describe music with words, all wecan do is articul…

Android Glide圖片加載框架(三)緩存機制

文章目錄一、緩存簡介二、緩存用法內存緩存方式磁盤緩存方式三、緩存KEY四、內存緩存內存緩存流程五、磁盤緩存磁盤緩存流程Android Glide圖片加載框架系列文章 Android Glide圖片加載框架&#xff08;一&#xff09;基本用法 Android Glide圖片加載框架&#xff08;二&#…

計算機操作系統 死鎖問題

概念 條件是基礎&#xff0c;在一定的原因下&#xff0c;產生結果死鎖三胞胎 死鎖 僵持&#xff0c;消耗時間&#xff0c;雙方都占用了部分資源&#xff0c;不釋放活鎖 雙方互相謙讓&#xff0c;都不占用資源饑餓 謙讓的一方一直等待&#xff0c;無法占有資源&#xff0c;導致…

C++ 力扣劍指Offer16-數值的整數次方

題目詳情 /* * 實現函數double Power(double base, int exponent)&#xff0c; * 求base的exponent次方。不得使用庫函數&#xff0c;同時不需要考慮大數問題。示例 1: 輸入: 2.00000, 10 輸出: 1024.00000示例 2: 輸入: 2.10000, 3 輸出: 9.26100 * 示例 3: 輸入: 2.00000, -…

Android Glide圖片加載框架(四)回調與監聽

文章目錄Android Glide圖片加載框架系列文章 Android Glide圖片加載框架&#xff08;一&#xff09;基本用法 Android Glide圖片加載框架&#xff08;二&#xff09;源碼解析之with() Android Glide圖片加載框架&#xff08;二&#xff09;源碼解析之load() Android Glide圖…

算法章節 數組、鏈表、棧、隊列

數組 概念與特性 1&#xff0c;數組是線性表&#xff0c;用一組連續的內存空間存儲?組具有相同類型的數據 2&#xff0c;最大的特性是?持按照下標O(1)時間復雜度內快速訪問數組元素 3&#xff0c;?維數組尋址公式&#xff1a;a[i]_addr base_addr i * data_type_size 操作…

武忠祥.高等數學.基礎課-第一章函數 極限 連續P10

sin(1/x) 詳細解析網址 1.圖像 2.極限 x–>0時,函數極限不存在 sin2x 詳細作圖網址 1.圖像 2.周期為Π f(x)周期為T,f(axb)周期為T/|a| 所以sinx周期為2Π,sin2x周期為2Π/2Π |sinx| 詳細講解網址 1.圖像 2.周期:Π 3.絕對值 &#xff08;1&#xff09;y|sinx|的圖…

Java命令:jstat — 查看JVM的GC信息

文章目錄一、簡介二、常用命令1、jstat -class pid : class loader行為統計2、jstat -compiler pid : JIT編譯器行為統計3、jstat -gc pid 5000 20 : 垃圾回收堆行為統計4、jstat -gccapacity pid 5000 20 : 堆內存統計5、jstat -gcutil pid 5000 20 : 總結垃圾回收統計6、jsta…

算法章節 遞歸、排序、?分查找

遞歸 概念與特性函數調?函數?身的編程?式叫做遞歸&#xff0c;調?為”遞“&#xff0c;返回為”歸“三個條件1. ?個問題的解可以分解為多個?問題的解&#xff1b; 2. 分解之后的?問題&#xff0c;除了數據規模不同&#xff0c;求解思路跟原問題相同&#xff1b; 3. 存在…

codeforces 50A-C語言解題報告

50A題目網址 解題報告-others 題目解析 1.輸入n x m大小的木板,使用21大小的多米諾去填滿,求最多的多米諾數目 2.通過分析把木板分為奇數和偶數的情況 1)有一邊是偶數的情況: 使用2去填滿 2)兩個邊都是奇數 奇數-1偶數 還是讓木板的(奇數-1)邊去和2平行,再加上 (m-1)/2(n/1)…

Java命令:jps — 查看進程信息

文章目錄一、簡介二、常用命令1、jps2、jps -l3、jps -q4、jps -m5、jps -v6、jps失效一、簡介 JVM Process Status Tool&#xff0c;顯示指定系統內所有的HotSpot虛擬機進程。 功能&#xff1a; 顯示當前所有java進程pid的命令&#xff0c;我們可以通過這個命令來查看到底啟…