《看完不忘系列》將以從樹幹到細枝
的思路來分析一些技術框架,本文是開篇文章,將對開源項目Glide
圖片加載庫進行介紹。若是老鐵們看完仍是忘了,就 回來揍我一頓 點贊收藏加關注
,多看兩遍~java
基於Glide
最新版本4.11.0
,未遷AndroidX的項目只能使用4.9.0
,簡單使用:android
引入依賴,app/build.gradle:git
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 複製代碼
一句代碼,完成圖片加載:github
Glide.with(this) //指定上下文,能夠是app、activity、fragment
.load(url) //網絡圖片地址 .into(img); //用於展現的imageView 複製代碼
用起來簡潔優雅,而後咱們先大體預覽下Glide
的一些職能,web
以Glide.with(this).load(url).into(img)
爲起點,拆成with購車
、load上牌
、into發車
三個環節來分析。面試
簡單來講,with的功能就是根據傳入的上下文context來獲取圖片請求管理器RequestManager
,他用來管理和啓動圖片請求,緩存
context能夠傳入app、activity、fragment,這決定了圖片請求的生命週期。一般是使用粒度較細的context,即便用當前頁面的context而不是全局的app。這樣作的好處是,打開一個頁面開啓圖片加載,而後退出頁面,圖片請求就會跟隨頁面銷燬而被取消,而不是繼續加載而浪費資源。網絡
當context是app時,經過RequestManagerRetriever
得到的RequestManager
是一個全局單例,這類圖片請求的生命週期將會跟隨整個app,app
class RequestManagerRetriever implements Handler.Callback {
volatile RequestManager applicationManager; RequestManager getApplicationManager(Context context) { //雙重檢查鎖,獲取單例的RequestManager if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build(glide,new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(),context.getApplicationContext()); } } } //返回應用級別的RequestManager單例 return applicationManager; } } 複製代碼
當context是Activity時,建立一個SupportRequestManagerFragment
(無界面的空fragment)添加到Activity,從而感知Activity的生命週期,同時建立RequestManager
給空fragment持有,框架
class RequestManagerRetriever implements Handler.Callback {
RequestManager supportFragmentGet(Context context,FragmentManager fm, Fragment parentHint,boolean isParentVisible) { //獲取空fragment,無則建立 SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); //若是空fragment沒有RequestManager,就建立一個 requestManager = factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); //讓空fragment持有RequestManager current.setRequestManager(requestManager); } //返回頁面級別的RequestManager return requestManager; } } 複製代碼
而後看到getSupportRequestManagerFragment
方法,
class RequestManagerRetriever implements Handler.Callback {
Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments = new HashMap<>(); SupportRequestManagerFragment getSupportRequestManagerFragment( final FragmentManager fm, Fragment parentHint, boolean isParentVisible) { //經過tag找到Activity中的空fragment SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { //findFragmentByTag沒找到空fragment,有多是延遲問題?再從Map中找一下 current = pendingSupportRequestManagerFragments.get(fm); if (current == null) { //確實沒有空fragment,就建立一個 current = new SupportRequestManagerFragment(); //... //緩存進Map pendingSupportRequestManagerFragments.put(fm, current); //空fragment添加到Activity,使其能感知Activity的生命週期 fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); //... } } return current; } } 複製代碼
綜上,經過with操做,
當context是app時,獲得應用級別RequestManager
全局單例;
當context是Activity時,每一個頁面都會被添加一個空fragment,由空fragment持有頁面級別RequestManager
。
注意:若是with發生在子線程,無論context是誰,都返回
應用級別RequestManager
單例。發散:添加空fragment來感知頁面生命週期的思想,在Lifecycle的實現中也能夠看到,見ReportFragment的
injectIfNeededIn
方法。(不過這個方法在Lifecycle的2.2.0版本中有所改動,Android 10開始的設備改爲了使用Application.ActivityLifecycleCallbacks來感知,感興趣能夠康康)
至此,咱們根據存款context
買到了心儀的車RequestManager
,下面開始上牌~
load方法獲得了一個RequestBuilder
圖片請求構建器,見名知意猜一下,是用來建立圖片請求的,
class RequestManager implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> { RequestBuilder<Drawable> load(String string) { return asDrawable().load(string); } RequestBuilder<Drawable> asDrawable() { //須要加載的類型爲Drawable return as(Drawable.class); } <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) { //建立一個請求構建器 return new RequestBuilder<>(glide, this, resourceClass, context); } } 複製代碼
而後跟進asDrawable().load(string)
,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { RequestBuilder<TranscodeType> load(String string) { return loadGeneric(string); } RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { //只是簡單地賦值 this.model = model; isModelSet = true; return this; } } 複製代碼
到這裏,咱們就完成了上牌,獲得了一個RequestBuilder
圖片請求構建器。沒錯,上牌就這麼簡單,畢竟搖號已經夠艱難了對吧?
來到into了,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { ViewTarget<ImageView, TranscodeType> into(ImageView view) { //... BaseRequestOptions<?> requestOptions = this; if (!requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() != null) { //根據ImageView的ScaleType,來配置參數 switch (view.getScaleType()) { case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; case CENTER_INSIDE: requestOptions = requestOptions.clone().optionalCenterInside(); break; //... } } return into( //前面提到,Target是展現圖片的載體,這裏他封裝了ImageView glideContext.buildImageViewTarget(view, transcodeClass),null, requestOptions,Executors.mainThreadExecutor()); } } 複製代碼
繼續跟進into重載方法,
class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>> implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> { <Y extends Target<TranscodeType>> Y into(Y target,RequestListener<TranscodeType> targetListener, BaseRequestOptions<?> options,Executor callbackExecutor) { //... //建立圖片請求 Request request = buildRequest(target, targetListener, options, callbackExecutor); //獲取Target載體已有的請求 Request previous = target.getRequest(); //若是兩個請求等效,而且xxx(先忽略) if (request.isEquivalentTo(previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { if (!Preconditions.checkNotNull(previous).isRunning()) { //啓動異步請求 previous.begin(); } return target; } requestManager.clear(target); //圖片載體綁定圖片請求,即imageView setTag爲request target.setRequest(request); //啓動異步請求 requestManager.track(target, request); return target; } } 複製代碼
跟進requestManager.track(target, request)
,
//RequestManager.java
void track(Target<?> target,Request request) { targetTracker.track(target); requestTracker.runRequest(request); } //RequestTracker.java void runRequest(Request request) { requests.add(request); if (!isPaused) { //開啓圖片請求 request.begin(); } else { request.clear(); //若是處於暫停狀態,就把請求存起來,晚些處理 pendingRequests.add(request); } } 複製代碼
到這裏,就啓動了圖片請求,
那麼,接下來重點關注的就是request.begin()
了,
class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {
void begin() { synchronized (requestLock) { //... if (Util.isValidDimensions(overrideWidth, overrideHeight)) { //若是已經有了明確的尺寸,開始加載 onSizeReady(overrideWidth, overrideHeight); } else { //沒有的話先去獲取尺寸,最終仍是走onSizeReady target.getSize(this); } //... } } void onSizeReady(int width, int height) { synchronized (requestLock) { //... //engine.load,傳了不少參數 loadStatus = engine.load(glideContext,model, requestOptions.getSignature(), this.width,this.height, requestOptions.getResourceClass(), transcodeClass,priority, requestOptions.getDiskCacheStrategy(), //... this,callbackExecutor); //... } } } 複製代碼
跟進engine.load
,
class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { <R> LoadStatus load( GlideContext glideContext, //... Executor callbackExecutor) { //... EngineResource<?> memoryResource; synchronized (this) { //從內存加載 memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); if (memoryResource == null) { //若是內存裏沒有緩存,則加載 return waitForExistingOrStartNewJob( glideContext, model, //... key, startTime); } } cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE); return null; } <R> LoadStatus waitForExistingOrStartNewJob(...) { //... EngineJob<R> engineJob =engineJobFactory.build(...); DecodeJob<R> decodeJob =decodeJobFactory.build(...); jobs.put(key, engineJob); //添加回調,這個cb就是SingleRequest本身,todo1 engineJob.addCallback(cb, callbackExecutor); //engineJob開啓decodeJob engineJob.start(decodeJob); return new LoadStatus(cb, engineJob); } } 複製代碼
DecodeJob
是一個Runable,看看他的run方法,調用鏈以下:
DecodeJob.run -> DecodeJob.runWrapped -> DecodeJob.runGenerators ->
SourceGenerator.startNext -> SourceGenerator.startNextLoad ->
MultiModelLoader#MultiFetcher.loadData ->
HttpUrlFetcher.loadData
來到HttpUrlFetcher
,
class HttpUrlFetcher implements DataFetcher<InputStream> {
void loadData(Priority priority, DataCallback<? super InputStream> callback) { try { //獲取輸入流,沒有引入okhttp,則使用HttpURLConnection InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); //回調出去 callback.onDataReady(result); } catch (IOException e) { callback.onLoadFailed(e); } finally { } } } 複製代碼
到這裏,網絡請求就完成了,下面看看圖片是怎麼設置上去的,前邊留了個todo1,
//添加回調,這個cb就是SingleRequest本身,todo1
callback就是SingleRequest
,被回調前有一些對象包裝、解碼操做,暫不深究,來到SingleRequest
,
class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {
void onResourceReady(Resource<?> resource, DataSource dataSource) { Resource<?> toRelease = null; try { synchronized (requestLock) { //... Object received = resource.get(); //... //這裏 onResourceReady((Resource<R>) resource, (R) received, dataSource); } } finally { if (toRelease != null) { engine.release(toRelease); } } } void onResourceReady(Resource<R> resource, R result, DataSource dataSource) { //... try { //... //這裏,回調給Target載體 target.onResourceReady(result, animation); } finally { } notifyLoadSuccess(); } } 複製代碼
跟進target.onResourceReady
,最終來到DrawableImageViewTarget
,
class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
void setResource(Drawable resource) { //ImageView設置圖片 view.setImageDrawable(resource); } } 複製代碼
結合階段一和二,
終於,汽車發動起來了~
把with購車
、load上牌
、into發車
三個環節彙總起來,
能夠看到,整個圖片加載過程,就是with獲得RequestManager
,load獲得RequestBuilder
,而後into開啓加載:
建立Request
、開啓Engine
、運行DecodeJob
線程、HttpUrlFetcher
加載網絡數據、回調給載體Target
、載體爲ImageView
設置展現圖片。
在子線程下載完圖片後,如何回調主線程設置圖片?在Executors
類裏,
private static final Executor MAIN_THREAD_EXECUTOR = new Executor() {
//主線程Handler private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable command) { //切回主線程 handler.post(command); } }; 複製代碼
調用棧以下:
GlideBuilder
類裏會初始化一些線程池:
Glide build(Context context) {
private GlideExecutor sourceExecutor; //加載圖片 private GlideExecutor diskCacheExecutor; //管理磁盤緩存 private GlideExecutor animationExecutor; //管理動畫 } 複製代碼
有內存緩存和磁盤緩存,在Engine.load
時會去取,篇幅緣由後面單獨開篇來寫。
Fresco
支持解析webp動圖,Glide
不支持,不過已經有了開源的方案,見GitHub - GlideWebpDecoder。
Fresco
和Glide
怎麼選?
Fresco
具備必定侵入性,須要繼承SimpleDraweeView
;
Fresco
調用繁瑣,沒有Glide
的鏈式調用優雅,固然這個能夠包一層來解決;
Fresco
在5.0如下的系統進行了內存優化(Ashmem區),這個優點在當下的環境已經不值一提,由於這些系統佔比已經很是低了,一些App的minSDK都已經設置成21了。
因此,更推薦使用Glide
(我的拙見,僅供參考)
做爲《看完不忘系列》的文章,本文刪減了不少源碼,重點在於理清Glide
圖片加載流程,你們看的時候最好能跟着思路去閱讀源碼~而後,Glide
還有解碼、緩存的流程沒有分析,後面會單獨開篇來寫。
本文使用 mdnice 排版