Android | 《看完不忘系列》之Glide

《看完不忘系列》將以從樹幹到細枝的思路來分析一些技術框架,本文是開篇文章,將對開源項目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:購車

簡單來講,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的實現中也能夠看到,見ReportFragmentinjectIfNeededIn方法。(不過這個方法在Lifecycle的2.2.0版本中有所改動,Android 10開始的設備改爲了使用Application.ActivityLifecycleCallbacks來感知,感興趣能夠康康)

至此,咱們根據存款context買到了心儀的車RequestManager,下面開始上牌~

load:上牌

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:發車

階段一

來到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時會去取,篇幅緣由後面單獨開篇來寫。

webp動圖

Fresco支持解析webp動圖,Glide不支持,不過已經有了開源的方案,見GitHub - GlideWebpDecoder

選型

FrescoGlide怎麼選?

Fresco具備必定侵入性,須要繼承SimpleDraweeView

Fresco調用繁瑣,沒有Glide的鏈式調用優雅,固然這個能夠包一層來解決;

Fresco在5.0如下的系統進行了內存優化(Ashmem區),這個優點在當下的環境已經不值一提,由於這些系統佔比已經很是低了,一些App的minSDK都已經設置成21了。

因此,更推薦使用Glide(我的拙見,僅供參考)

尾聲

做爲《看完不忘系列》的文章,本文刪減了不少源碼,重點在於理清Glide圖片加載流程,你們看的時候最好能跟着思路去閱讀源碼~而後,Glide還有解碼、緩存的流程沒有分析,後面會單獨開篇來寫。

參考資料


本文使用 mdnice 排版

相關文章
相關標籤/搜索