Android Glide源碼解析 -電腦資料

電腦資料 時間:2019-01-01 我要投稿
【www.solarmaxlimited.com - 電腦資料】

   

功能介紹

    使用文章介紹以及和Picasso的對比分析請參考Introduction to Glide, Image Loader Library for Android, recommended by Google

    由于這篇文章使用glide的老版本,因此有些使用方法可能不太一致了,

Android Glide源碼解析

    本文基于github上Glide最新代碼4.0.0版本做解析。

    最基本的使用方式如下:

<code avrasm="" class="hljs">Glide.with(this)                .asDrawable()                .load(http://i6.topit.me/6/5d/45/1131907198420455d6o.jpg)                .apply(fitCenterTransform(this))                .apply(placeholderOf(R.drawable.skyblue_logo_wechatfavorite_checked))                .into(imageView);</code>

    Glide使用了現在非常流行的流氏編碼方式,方便了開發者的使用,簡明、扼要。

    接下來主要對上面這一段流氏操作做拆分。

Glide 主入口

    這個類有點像門臉模式的統一代理入口,不過實際作用在4.0.0中很多功能都被放到后面的其他類中,此類關注的點就很少了。雖然整個libray的所有需要的組建都在這個類中,但是實際也只是一個統一初始化的地方。

RequestManager(Glide.with(…))

    這個類主要用于管理和啟動Glide的所有請求,可以使用activity,fragment或者連接生命周期的事件去智能的停止,啟動,和重啟請求。也可以檢索或者通過實例化一個新的對象,或者使用靜態的Glide去利用構建在Activity和Fragment生命周期處理中。它的方法跟你的Fragment和Activity的是同步的。

RequestBuilder

    通用類,可以處理設置選項,并啟動負載的通用資源類型。

    在這個類中,主要是應用請求的很多選項(如下的選項從字面都能看出具體的用處,在ImageView控件中經常也能看到,另外之前版本可不是這么使用的):

<code class="hljs" php="">public final class RequestOptions extends BaseRequestOptions<requestoptions>{  private static RequestOptions skipMemoryCacheTrueOptions;  private static RequestOptions skipMemoryCacheFalseOptions;  private static RequestOptions fitCenterOptions;  private static RequestOptions centerCropOptions;  private static RequestOptions circleCropOptions;  private static RequestOptions noTransformOptions;  private static RequestOptions noAnimationOptions;  // ...省略...}</requestoptions></code>

    RequestBuilder transition(TransitionOptions transitionOptions){} 這個方法主要是用于加載對象從占位符(placeholder)或者縮略圖(thumbnail)到真正對象加載完成的轉場動畫。

    RequestBuilder load(…){}多太方法中,這里可以加載很多類型的數據對象,可以是String,Uri,File,resourceId,byte[]這些。當然這些后面對應的編碼方式也是不一樣的。

    Target into(…){}這個方法是觸發Request真正啟動的地方,在上邊的示例中最后就是調用這個方法發起請求。

    不得不說的registry域,這個域掛載了很多元件,該注冊器中囊括了模塊加載器(ModelLoader)、編碼器(Encoder)、資源解碼器(ResourceDecoder)、資源編碼器(ResourceEncoder)、數據回轉器(DataRewinder)、轉碼器(Transcoder)。這些都是Glide在對資源編解碼中既是基礎又是核心功能。

代碼結構

    這里主要列舉一下一些重要的組件以及他們的結構關系:

    ModelLoader

   

    DataFetcher

   

    Target

   

    Resource

   

    ResourceTransformation

   

    Pool

   

    Cache

   

    Decoder

   

    Encoder

   

    把這些組件代碼結構列舉出來主要是為了讓讀者和使用者一目了然的看到自己需要的一些功能。

執行流程

1、根據不同版本的Fragment創建RequestManagerFragment或者SupportRequestManagerFragment,并加入到對應的FragmentManager中。這兩種Fragment是不帶有任何界面的,主要是用于同步生命周期。具體實現如下:

<code avrasm="" class="hljs">public static RequestManager with(Context context) {    RequestManagerRetriever retriever = RequestManagerRetriever.get();    return retriever.get(context);  }// RequestManagerRetriever.get(...)  @TargetApi(Build.VERSION_CODES.HONEYCOMB)  public RequestManager get(Activity activity) {    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {      return get(activity.getApplicationContext());    } else {      assertNotDestroyed(activity);      android.app.FragmentManager fm = activity.getFragmentManager();      return fragmentGet(activity, fm, null);    }  }  @TargetApi(Build.VERSION_CODES.HONEYCOMB)  RequestManager fragmentGet(Context context, android.app.FragmentManager fm,      android.app.Fragment parentHint) {    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);    RequestManager requestManager = current.getRequestManager();    if (requestManager == null) {      requestManager =          new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());      current.setRequestManager(requestManager);    }    return requestManager;  }  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)  RequestManagerFragment getRequestManagerFragment(      final android.app.FragmentManager fm, android.app.Fragment parentHint) {    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);    if (current == null) {      current = pendingRequestManagerFragments.get(fm);      if (current == null) {        current = new RequestManagerFragment();        current.setParentFragmentHint(parentHint);        pendingRequestManagerFragments.put(fm, current);        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();      }    }    return current;  }</code>

2、創建一個RequestBuilder,并添加一個DrawableTransitionOptions類型的轉場動畫

<code class="hljs" cs="">public RequestBuilder<drawable>asDrawable() {    return as(Drawable.class).transition(new DrawableTransitionOptions());  }</drawable></code>

3、加載對象(model域)

<code class="hljs" cs="">public RequestBuilder<transcodetype>load(@Nullable Object model) {    return loadGeneric(model);  }private RequestBuilder<transcodetype>loadGeneric(@Nullable Object model) {    this.model = model;    isModelSet = true;    return this;  }</transcodetype></transcodetype></code>

4、裝載對象(包含請求的發起點)。

<code class="hljs" cs="">public<y extends="" transcodetype="">> Y into(@NonNull Y target) {    Util.assertMainThread();    Preconditions.checkNotNull(target);    if (!isModelSet) {      throw new IllegalArgumentException(You must call #load() before calling #into());    }    Request previous = target.getRequest();    if (previous != null) {      requestManager.clear(target);    }    requestOptions.lock();    Request request = buildRequest(target);    target.setRequest(request);    requestManager.track(target, request);    return target;  }</y></code>

    一般而言,大部分使用者都是用來裝載圖片的,因此都會調用如下這個方法:

<code class="hljs" cs="">public Target<transcodetype>into(ImageView view) {    Util.assertMainThread();    Preconditions.checkNotNull(view);    if (!requestOptions.isTransformationSet()        && requestOptions.isTransformationAllowed()        && view.getScaleType() != null) {      if (requestOptions.isLocked()) {        requestOptions = requestOptions.clone();      }      switch (view.getScaleType()) {        case CENTER_CROP:          requestOptions.optionalCenterCrop(context);          break;        case FIT_CENTER:        case FIT_START:        case FIT_END:          requestOptions.optionalFitCenter(context);          break;        //$CASES-OMITTED$        default:          // Do nothing.      }    }    return into(context.buildImageViewTarget(view, transcodeClass));  }</transcodetype></code>

    這里針對ImageView的填充方式做了篩選并對應設置到requestOptions上。最終的是通過ImageView和轉碼類型(transcodeClass)創建不通過的Target(例如Bitmap對應的BitmapImageViewTarget和Drawable對應的DrawableImageViewTarget)

    4.1 Request的創建buildRequest(target)。

    在Request的創建中會針對是否有縮略圖來創建不同尺寸的請求,縮略圖方法可以使用RequestBuilder.thumbnail(…)方法來添加上。

    Glide中的Request都是使用了SingleRequest類,當然縮略圖采用的是ThumbnailRequestCoordinator類:

<code class="hljs" cs="">private Request obtainRequest(Target<transcodetype>target,      BaseRequestOptionsrequestOptions, RequestCoordinator requestCoordinator,      TransitionOptionstransitionOptions, Priority priority,      int overrideWidth, int overrideHeight) {    requestOptions.lock();    return SingleRequest.obtain(        context,        model,        transcodeClass,        requestOptions,        overrideWidth,        overrideHeight,        priority,        target,        requestListener,        requestCoordinator,        context.getEngine(),        transitionOptions.getTransitionFactory());  }</transcodetype></code>

    比較值得推崇的是SingleRequest.obtain寫法,個人認為比new關鍵字更簡潔明了吧。

    target.setRequest(request)也是一個比較值得注意的地方,如果target是ViewTarget,那么request會被設置到View的tag上。這樣其實是有一個好處,每一個View有一個自己的Request,如果有重復請求,那么都會先去拿到上一個已經綁定的Request,并且從RequestManager中清理回收掉。這應該是去重的功能。

    4.2 requestManager.track(target, request)

    這個方法非常的復雜,主要用于觸發請求、編解碼、裝載和緩存這些功能。下面就一步一步來看吧:

    4.2.1 緩存target,并啟動Request

<code class="hljs" java="">void track(Targettarget, Request request) {    targetTracker.track(target);    requestTracker.runRequest(request);  }  /**   * Starts tracking the given request.   */  public void runRequest(Request request) {    requests.add(request); //添加內存緩存    if (!isPaused) {      request.begin(); // 開始    } else {      pendingRequests.add(request); // 掛起請求    }  }</code>

    繼續看一下SingleRequest中的begin方法:

<code class="hljs" java="">@Override  public void begin() {    stateVerifier.throwIfRecycled();    startTime = LogTime.getLogTime();    // 如果model空的,那么是不能執行的。 這里的model就是前面提到的RequestBuilder中的model    if (model == null) {      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {        width = overrideWidth;        height = overrideHeight;      }      // Only log at more verbose log levels if the user has set a fallback drawable, because      // fallback Drawables indicate the user expects null models occasionally.      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;      onLoadFailed(new GlideException(Received null model), logLevel);      return;    }    status = Status.WAITING_FOR_SIZE;    // 如果當前的View尺寸已經加載獲取到了,那么就會進入真正的加載流程。    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {      onSizeReady(overrideWidth, overrideHeight);    } else {    // 反之,當前View還沒有畫出來,那么是沒有尺寸的。    // 這里會調用到ViewTreeObserver.addOnPreDrawListener。    // 等待View的尺寸都ok,才會繼續      target.getSize(this);    }    // 如果等待和正在執行狀態,那么當前會加載占位符Drawable    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)        && canNotifyStatusChanged()) {      target.onLoadStarted(getPlaceholderDrawable());    }    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logV(finished run method in  + LogTime.getElapsedMillis(startTime));    }  }</code>

    接下來是target.getSize(this)方法。這里主要說一下尺寸未加載出來的情況(ViewTarget.java):

<code class="hljs" java="">void getSize(SizeReadyCallback cb) {      int currentWidth = getViewWidthOrParam();      int currentHeight = getViewHeightOrParam();      if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {        cb.onSizeReady(currentWidth, currentHeight);      } else {        // We want to notify callbacks in the order they were added and we only expect one or two        // callbacks to        // be added a time, so a List is a reasonable choice.        if (!cbs.contains(cb)) {          cbs.add(cb);        }        if (layoutListener == null) {          final ViewTreeObserver bserver = view.getViewTreeObserver();          layoutListener = new SizeDeterminerLayoutListener(this);          // 繪畫之前加入尺寸的監聽。這一點我想大部分Android開發同學應該都知道。          // 接下來在看看系統觸發該Listener時target又干了些什么。          observer.addOnPreDrawListener(layoutListener);        }      }    }private static class SizeDeterminerLayoutListener implements ViewTreeObserver        .OnPreDrawListener {        // 注意這里是弱引用      private final WeakReference<sizedeterminer>sizeDeterminerRef;      public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {        sizeDeterminerRef = new WeakReference<>(sizeDeterminer);      }      @Override      public boolean onPreDraw() {        if (Log.isLoggable(TAG, Log.VERBOSE)) {          Log.v(TAG, OnGlobalLayoutListener called listener= + this);        }        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();        if (sizeDeterminer != null) {         // 通知SizeDeterminer去重新檢查尺寸,并觸發后續操作。         // SizeDeterminer有點像工具類,又作為尺寸回調的檢測接口          sizeDeterminer.checkCurrentDimens();        }        return true;      }    }</sizedeterminer></code>

    ok,繼續回到SingleRequest.onSizeReady方法,主要就是Engine發起load操作

<code avrasm="" class="hljs">public void onSizeReady(int width, int height) {    stateVerifier.throwIfRecycled();    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logV(Got onSizeReady in  + LogTime.getElapsedMillis(startTime));    }    if (status != Status.WAITING_FOR_SIZE) {      return;    }    status = Status.RUNNING;    float sizeMultiplier = requestOptions.getSizeMultiplier();    this.width = Math.round(sizeMultiplier * width);    this.height = Math.round(sizeMultiplier * height);    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logV(finished setup for calling load in  + LogTime.getElapsedMillis(startTime));    }    loadStatus = engine.load(        glideContext,        model,        requestOptions.getSignature(),        this.width,        this.height,        requestOptions.getResourceClass(),        transcodeClass,        priority,        requestOptions.getDiskCacheStrategy(),        requestOptions.getTransformations(),        requestOptions.isTransformationRequired(),        requestOptions.getOptions(),        requestOptions.isMemoryCacheable(),        this);    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logV(finished onSizeReady in  + LogTime.getElapsedMillis(startTime));    }  }</code>

    特別的,所有的操作都是來之唯一一個Engine,它的創建是來至于Glide的初始化。如果有需要修改緩存配置的同學可以繼續看一下diskCacheFactory的創建:

<code class="hljs" cs="">if (engine == null) {      engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);    }</code>

    繼續看一下Engine.load的詳細過程:

<code class="hljs" lasso="">public<r>LoadStatus load(      GlideContext glideContext,      Object model,      Key signature,      int width,      int height,      ClassresourceClass,      Class<r>transcodeClass,      Priority priority,      DiskCacheStrategy diskCacheStrategy,      Map<class<?>, Transformation> transformations,      boolean isTransformationRequired,      Options options,      boolean isMemoryCacheable,      ResourceCallback cb) {    Util.assertMainThread();    long startTime = LogTime.getLogTime();    // 創建key,這是給每次加載資源的唯一標示,

電腦資料

Android Glide源碼解析》(http://www.solarmaxlimited.com)。 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); // 通過key查找緩存資源 (PS 這里的緩存主要是內存中的緩存,切記,可以查看MemoryCache) EngineResourcecached = loadFromCache(key, isMemoryCacheable); if (cached != null) { // 如果有,那么直接利用當前緩存的資源。 cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey(Loaded resource from cache, startTime, key); } return null; } // 這是一個二級內存的緩存引用,很簡單用了一個Map<key,>>>裝載起來的。 // 這個緩存主要是誰來放進去呢? 可以參考上面一級內存緩存loadFromCache方法。 EngineResourceactive = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey(Loaded resource from active resources, startTime, key); } return null; } // 根據key獲取緩存的job。 EngineJob current = jobs.get(key); if (current != null) { current.addCallback(cb); // 給當前job添加上回調Callback if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey(Added to existing load, startTime, key); } return new LoadStatus(cb, current); } // 創建job EngineJob<r>engineJob = engineJobFactory.build(key, isMemoryCacheable); DecodeJob<r>decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, options, engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb); // 放入線程池,執行 engineJob.start(decodeJob); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey(Started new load, startTime, key); } return new LoadStatus(cb, engineJob); }</r></r></key,></class<?></r></r></code>

    上面有一些值得注意的地方:

內存緩存:在Glide中默認是LruResourceCache。當然你也可以自定義; 為何要兩級內存緩存(loadFromActiveResources)。個人理解是一級緩存采用LRU算法進行緩存,并不能保證全部能命中,添加二級緩存提高命中率之用; EngineJob和DecodeJob各自職責:EngineJob充當了管理和調度者,主要負責加載和各類回調通知;DecodeJob是真正干活的勞動者,這個類實現了Runnable接口。

    下面來看看DecodeJob是如何執行的:

<code class="hljs" cs="">private void runWrapped() {     switch (runReason) {      case INITIALIZE:        // 初始化 獲取下一個階段狀態        stage = getNextStage(Stage.INITIALIZE);        currentGenerator = getNextGenerator();        // 運行        runGenerators();        break;      case SWITCH_TO_SOURCE_SERVICE:        runGenerators();        break;      case DECODE_DATA:        decodeFromRetrievedData();        break;      default:        throw new IllegalStateException(Unrecognized run reason:  + runReason);    }  }// 這里的階段策略首先是從resource中尋找,然后再是data,,再是sourceprivate Stage getNextStage(Stage current) {    switch (current) {      case INITIALIZE:       // 根據定義的緩存策略來回去下一個狀態      // 緩存策略來之于RequestBuilder的requestOptions域      // 如果你有自定義的策略,可以調用RequestBuilder.apply方法即可      // 詳細的可用緩存策略請參看DiskCacheStrategy.java        return diskCacheStrategy.decodeCachedResource()            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);      case RESOURCE_CACHE:        return diskCacheStrategy.decodeCachedData()            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);      case DATA_CACHE:        return Stage.SOURCE;      case SOURCE:      case FINISHED:        return Stage.FINISHED;      default:        throw new IllegalArgumentException(Unrecognized stage:  + current);    }// 根據Stage找到數據抓取生成器。private DataFetcherGenerator getNextGenerator() {    switch (stage) {      case RESOURCE_CACHE:       // 產生含有降低采樣/轉換資源數據緩存文件的DataFetcher。        return new ResourceCacheGenerator(decodeHelper, this);      case DATA_CACHE:       // 產生包含原始未修改的源數據緩存文件的DataFetcher。        return new DataCacheGenerator(decodeHelper, this);      case SOURCE:      // 生成使用注冊的ModelLoader和加載時提供的Model獲取源數據規定的DataFetcher。      // 根據不同的磁盤緩存策略,源數據可首先被寫入到磁盤,然后從緩存文件中加載,而不是直接返回。        return new SourceGenerator(decodeHelper, this);      case FINISHED:        return null;      default:        throw new IllegalStateException(Unrecognized stage:  + stage);    }  }</code>

    經過很多流程,最后來到了發起實際請求的地方SourceGenerator.startNext()方法:

<code class="hljs" java="">public boolean startNext() {    if (dataToCache != null) {      Object data = dataToCache;      dataToCache = null;      cacheData(data);    }    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {      return true;    }    sourceCacheGenerator = null;    loadData = null;    boolean started = false;    // 查找ModelLoader    while (!started && hasNextModelLoader()) {      loadData = helper.getLoadData().get(loadDataListIndex++);      if (loadData != null          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {        started = true;        根據model的fetcher加載數據        loadData.fetcher.loadData(helper.getPriority(), this);      }    }    return started;  }</code>

    這里的Model必須是實現了GlideModule接口的,fetcher是實現了DataFetcher接口。有興趣同學可以繼續看一下integration中的okhttp和volley工程。Glide主要采用了這兩種網絡libray來下載圖片。

    4.2.2 數據下載完成后的緩存處理SourceGenerator.onDataReady

<code class="hljs" lasso="">public void onDataReady(Object data) {    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {      dataToCache = data;      // We might be being called back on someone else's thread. Before doing anything, we should      // reschedule to get back onto Glide's thread.      cb.reschedule();    } else {      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,          loadData.fetcher.getDataSource(), originalKey);    }  }</code>

    有些小伙伴可能看不太明白為什么就一個dataToCache = data就完了…其實cb.reschedule()很重要,這里的cb就是DecodeJob.reschedule():

<code class="hljs" cs="">public void reschedule() {    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;    callback.reschedule(this);  }</code>

    這里又有一個Callback,繼續追蹤,這里的Callback接口是定義在DecodeJob內的,而實現是在外部的Engine中(這里會用線程池重新啟動當前job,那為什么要這樣做呢?源碼中的解釋是為了不同線程的切換,因為下載都是借用第三方網絡庫,而實際的編解碼是在Glide自定義的線程池中進行的):

<code class="hljs" cs="">public void reschedule(DecodeJobjob) {    if (isCancelled) {      MAIN_THREAD_HANDLER.obtainMessage(MSG_CANCELLED, this).sendToTarget();    } else {      sourceExecutor.execute(job);    }  }</code>

    接下來繼續DecodeJob.runWrapped()方法。這個時候的runReason是SWITCH_TO_SOURCE_SERVICE,因此直接執行runGenerators(),這里繼續執行SourceGenerator.startNext()方法,值得注意的dataToCache域,因為上一次執行的時候是下載,因此再次執行的時候內存緩存已經存在,因此直接緩存數據cacheData(data):

<code class="hljs" cs="">private void cacheData(Object dataToCache) {    long startTime = LogTime.getLogTime();    try {    // 根據不同的數據獲取注冊的不同Encoder      Encoder<object>encoder = helper.getSourceEncoder(dataToCache);      DataCacheWriter<object>writer =          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());      riginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());      // 這里的DiskCache實現是Engine中LazyDiskCacheProvider提供的DiskCacheAdapter。      helper.getDiskCache().put(originalKey, writer);      if (Log.isLoggable(TAG, Log.VERBOSE)) {        Log.v(TAG, Finished encoding source to cache            + , key:  + originalKey            + , data:  + dataToCache            + , encoder:  + encoder            + , duration:  + LogTime.getElapsedMillis(startTime));      }    } finally {      loadData.fetcher.cleanup();    }    // 創建針對緩存的Generator    sourceCacheGenerator =        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);  }</object></object></code>

    繼續回到SourceGenerator.startNext()方法,這個時候已經有了sourceCacheGenerator,那么直接執行DataCacheGenerator.startNext()方法:

<code class="hljs" cs="">public boolean startNext() {    while (modelLoaders == null || !hasNextModelLoader()) {      sourceIdIndex++;      if (sourceIdIndex >= cacheKeys.size()) {        return false;      }      Key sourceId = cacheKeys.get(sourceIdIndex);      Key riginalKey = new DataCacheKey(sourceId, helper.getSignature());      cacheFile = helper.getDiskCache().get(originalKey);      if (cacheFile != null) {        this.sourceKey = sourceId;        modelLoaders = helper.getModelLoaders(cacheFile);        modelLoaderIndex = 0;      }    }    loadData = null;    boolean started = false;    // 這里會通過model尋找注冊過的ModelLoader    while (!started && hasNextModelLoader()) {      ModelLoader<file,>modelLoader = modelLoaders.get(modelLoaderIndex++);      loadData =          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),              helper.getOptions());      // 通過FileLoader繼續加載數據      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {        started = true;        loadData.fetcher.loadData(helper.getPriority(), this);      }    }    return started;  }</file,></code>

    這里的ModelLoader跟之前提到過的Register的模塊加載器(ModelLoader)對應是modelLoaderRegistry域,具體執行的操作是Registry.getModelLoaders(…)方法如下:

<code class="hljs" php="">public<model>List<modelloader<model,>> getModelLoaders(Model model) {    List<modelloader<model,>> result = modelLoaderRegistry.getModelLoaders(model);    if (result.isEmpty()) {      throw new NoModelLoaderAvailableException(model);    }    return result;  }</modelloader<model,></modelloader<model,></model></code>

    繼續回到DataCacheGenerator.startNext()方法,找到了ModelLoader,這里筆者跟蹤到的是FileLoader類(FileFetcher.loadData(…)方法):

<code class="hljs" lasso="">public void loadData(Priority priority, DataCallbackcallback) {        // 讀取文件數據      try {        data = opener.open(file);      } catch (FileNotFoundException e) {        if (Log.isLoggable(TAG, Log.DEBUG)) {          Log.d(TAG, Failed to open file, e);        }        //失敗        callback.onLoadFailed(e);        return;      }      // 成功      callback.onDataReady(data);    }</code>

    4.2.3 裝載流程

    回調通知這里就不打算多講了,主要線路如下:

<code class="hljs" haml="">-->DataCacheGenerator.onDataReady  -->SourceGenerator.onDataFetcherReady    -->DecodeJob.onDataFetcherReady    -->DecodeJob.decodeFromRetrievedData    -->DecodeJob.notifyEncodeAndRelease    -->DecodeJob.notifyComplete      -->EngineJob.onResourceReady</code>

    Debug流程圖:

   

    需要說明的就是在EngineJob中有一個Handler叫MAIN_THREAD_HANDLER。為了實現在主UI中裝載資源的作用,ok繼續上邊的流程:

<code class="hljs" haml="">-->EngineJob.handleResultOnMainThread        -->SingleRequest.onResourceReady          -->ImageViewTarget.onResourceReady          -->ImageViewTarget.setResource            -->ImageView.setImageDrawable/ImageView.setImageBitmap</code>

    Debug流程圖2:

   

    數據的裝載過程中有一個很重要的步驟就是decode,這個操作發生在DecodeJob.decodeFromRetrievedData的時候,繼續看代碼:

<code class="hljs" cs="">private void decodeFromRetrievedData() {    if (Log.isLoggable(TAG, Log.VERBOSE)) {      logWithTimeAndKey(Retrieved data, startFetchTime,          data:  + currentData          + , cache key:  + currentSourceKey          + , fetcher:  + currentFetcher);    }    Resource<r>resource = null;    try {      resource = decodeFromData(currentFetcher, currentData, currentDataSource);    } catch (GlideException e) {      e.setLoggingDetails(currentAttemptingKey, currentDataSource);      exceptions.add(e);    }    if (resource != null) {      notifyEncodeAndRelease(resource, currentDataSource);    } else {      runGenerators();    }  }</r></code>

    這中間發生了很多轉換主要流程:

<code class="hljs" haml="">-->DecodeJob.decodeFromData-->DecodeJob.decodeFromFetcher-->DecodeJob.runLoadPath  -->LoadPath.load  -->LoadPath.loadWithExceptionList  -->LoadPath.decode  -->LoadPath.decodeResource  -->LoadPath.decodeResourceWithList    -->ResourceDecoder.handles    -->ResourceDecoder.decode</code>

    這里講到了decode,那么encode發生在什么時候呢?直接通過Encoder接口調用發現,在數據緩存的時候才會觸發編碼。具體調用在DiskLruCacheWrapper和DataCacheWriter中。一些值得參考的寫法例如BitmapEncoder對Bitmap的壓縮處理。

結束語

    最近看開源庫Glide關注度一直比較高,因此打算一探究竟。 由于時間比較緊,因此一些應該有的時序圖沒有畫,這里也只能簡單用箭頭代替。不過個人認為整體執行流程已經表達清楚了。

總體來說代碼寫的挺漂亮的,單從使用者角度來說入手是比較容易的。 源碼使用了大量的工廠方法來創建對象,就像String.valueof(…)方法一樣,這也體現編碼的優雅。 不過想要對這個庫進行改造,可能并非易事,筆者在跟蹤代碼的過程中發現很多地方有Callback這樣的接口,來來回回查找幾次很容易就暈頭轉向了。。。 另外一個感覺難受的地方就是構造方法帶入參數太多,就拿SingleRequest來說就是12個構造參數。 單例的使用感覺還是有些模糊,就比如GlideContext,有些時候通過Glide.get(context).getGlideContext()獲取,而有些類中采用構造傳入。個人覺得既然讓Glide作為單例,那么還這樣傳入參數是不是有點多余?代碼的編寫都是可以折中考慮,不過如果整個項目擬定好了一個規則的話,我想最好還是遵循它。另外再吐槽一下單例,很多開發人員喜歡用單例,如果你是有代碼潔癖的開發者,那么你肯定很討厭這樣,單例很容易造成代碼的散落和結構不清晰。

思考

    源碼的解析只是把最重要的加載流程走了一遍,有一些比較細節的地方沒有關注,如果你有需要,可以自己跟著這個主線debug一下就能查找到。

最新文章
国产v亚洲v天堂无码网站,综合亚洲欧美日韩一区二区,精品一级毛片A久久久久,欧美一级待黄大片视频
开心五月激情中文在线观看 | 日本人成精品视频在线播放 | 亚洲Av2020在线播放 | 亚洲欧美日韩日产在线首页 | 中文字幕中字在线视频 | 日本久久一区一本精品 |