Android 圖片載入框架Glide4.0源碼完全解析(二)

来源:http://www.cnblogs.com/guanmanman/archive/2017/06/17/7040942.html
-Advertisement-
Play Games

這篇是講Glide源碼中into方法的實現原理,可以說with和load方法只是做了前期的初始化配置工作,而真正意義上的圖片載入就是在into方法中實現的,所以該方法的複雜程度是可以想象的,還是依照我之前的寫作習慣,一步步的分析,不留下任何的盲點給大家帶來困惑,那麼下麵就開始吧。 ...


寫在之前

上一篇博文寫的是Android 圖片載入框架Glide4.0源碼完全解析(一),主要分析了Glide4.0源碼中的with方法和load方法,原本打算是一起發佈的,但是由於into方法複雜性遠不是前兩個方法所能比擬的,又不願意馬馬虎虎的隨便應付的寫作,還是保持一貫的一步步深入的講解,所以就提前發佈了一篇,以減少篇幅。

正文

這篇是講Glide源碼中into方法的實現原理,可以說with和load方法只是做了前期的初始化配置工作,而真正意義上的圖片載入就是在into方法中實現的,所以該方法的複雜程度是可以想象的,還是依照我之前的寫作習慣,一步步的分析,不留下任何的盲點給大家帶來困惑,那麼下麵就開始吧。

Glide 源碼分析

into()

前面兩個方法把所需的基礎配置基本已做好,那麼接下來就是真正的要去載入資源了,那麼我們來看看吧:

首先進去into方法中:

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();
          break;
        case CENTER_INSIDE:
          requestOptions.optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions.optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions.optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }

    return into(context.buildImageViewTarget(view, transcodeClass));
  }

主要是為requestOptions 做一些配置,這個配置時根據View的屬性而來的。

然後調用GlideContext的buildImageViewTarget方法,並把view和transcodeClass傳遞進去來構造一個viewTarget的對象,跟進去看看:

直接的調用imageViewTargetFactory的buildTarget方法,然後看下buildTarget的源碼:

還記得我多次提醒transcodeClass是什麼嗎?沒錯就是Drawable.class,就是在這個分支中使用到,那麼在buildTarget中就是創建一個DrawableImageViewTarget對象,並它view傳遞進去,DrawableImageViewTarget繼承於ImageViewTarget,又把view傳遞到了ViewTarget中,最終把view存放到了ViewTarget中的view變數中,並且還創建了一個SizeDeterminer對象:

ok,這就是我們的ImageView的最終去向,知道了view的去向,我們在返回到RequestBuilder中,創建個DrawableImageViewTarget對象後,又把它重定向到into方法中:

這個方法是非常重要的,必須要理解清楚,其實真正的理解好了也就很簡單的了:

首先,從target中調用getRequest方法獲取Request請求,這個getRequest方法是在ViewTarget父類中:

它首先從View中獲取tag標識,但是我們從未為view設置標識,它也就不存在什麼標識,所以getTag方法會返回一個null值,由此request也就是一個null空值。

在從taget中獲取不到request,那麼就需要為它去構造request對象,所以它調用了buildRequest:

private Request buildRequest(Target<TranscodeType> target) {
    return buildRequestRecursive(target, null, transitionOptions, requestOptions.getPriority(),
        requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight());
  }

buildRequest方法又調用了buildRequestRecursive方法:

private Request buildRequestRecursive(Target<TranscodeType> target,
      @Nullable ThumbnailRequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority, int overrideWidth, int overrideHeight) {
    ...
      return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority,
          overrideWidth, overrideHeight);
  }

buildRequestRecursive做了一堆判斷,最終會調用obtainRequest方法:

在這裡它直接的調用SingleRequest.obtain方法,並返回一個Request對象,由此我們看到最終是調用到SingleRequest來真正的構造Request對象:

 public static <R> SingleRequest<R> obtain(
      GlideContext glideContext,
      Object model,
      Class<R> transcodeClass,
      RequestOptions requestOptions,
      int overrideWidth,
      int overrideHeight,
      Priority priority,
      Target<R> target,
      RequestListener<R> requestListener,
      RequestCoordinator requestCoordinator,
      Engine engine,
      TransitionFactory<? super R> animationFactory) {
    @SuppressWarnings("unchecked") SingleRequest<R> request =
        (SingleRequest<R>) POOL.acquire();
    if (request == null) {
      request = new SingleRequest<>();
    }
    request.init(
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        requestListener,
        requestCoordinator,
        engine,
        animationFactory);
    return request;
  }

由obtain方法看到,創建了一個SingleRequest對象,並調用它的init方法來進行初始化操作。

ok,到此Request創建完畢,來看看它擁有哪些成員:

讓我們再回到into方法中,創建了Request對象後,把它設置給我們的target,最終則是調用setTag方法為view設置tag:

private void setTag(@Nullable Object tag) {
    if (tagId == null) {
      isTagUsedAtLeastOnce = true;
      view.setTag(tag);
    } else {
      view.setTag(tagId, tag);
    }
  }

設置完畢之後將會調用requestManager中的track方法:

void track(Target<?> target, Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }

track方法中分別把target和request添加到targetTracker和requestTracker追蹤器中。

然後來看看runRequest方法:

它將會調用Request對象的begin方法來企圖開啟我們的請求,我們知道request其實就是我們的SingleRequest對象,那麼到它的begin方法中看看到底做了哪些事情:

@Override
  public void begin() {
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    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;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }

    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));
    }
  }

首先,它判斷model是否為空,如果請求url為空的話,那就不用請求了,直接調用onLoadFailed載入失敗。

然後,調用onSizeReady方法,真正的資源載入就是從這個方法中開始的。

最後,根據Status對象狀態和是否有占點陣圖來設置載入過程中的占點陣圖。

那麼重點就是在onSizeReady方法中了,我們來詳細的看看它的源碼:

  @Override
  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 = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    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(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
  }

onSizeReady方法中首先把State狀態更改為RUNNING,然後獲取到ImageView的寬高屬性值,這個屬性值就是要載入的圖片的寬高,順便提一句,Glide框架會根據請求載入圖片的ImageView的寬高來進行載入相對應的寬高圖片,每次根據view的大小載入的圖片是不一定一樣的。最後調用engine中的load方法,我們再來看看load方法的源碼:

public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    EngineResource<?> cached = 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;
    }

    EngineResource<?> active = 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;
    }

    EngineJob<?> current = jobs.get(key);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
        useUnlimitedSourceExecutorPool);
    DecodeJob<R> decodeJob = decodeJobFactory.build(
        glideContext,
        model,
        key,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        onlyRetrieveFromCache,
        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);
  }

哇吼,所有重要的東西都在這裡了,解析下:

①:判斷是否在主線程運行,說明到目前為止還是在主線程執行的,並沒有真正的開啟子線程。

②:通過keyFactory工廠來構建一個EngineKey對象,key關聯著model,也就是url,它很根據model,view的寬高等等屬性來構建一個EngineKey對象,這個對象可以用來指定緩存地址,可以用來從緩存中查找資源等。

③:根據創建的key對象分別調用loadFromCache和loadFromActiveResources方法來從記憶體中查找是否有緩存資源,如果有,則回調cb.onResourceReady來直接設置圖片了。

④:分別使用engineJobFactory和decodeJobFactory構建EngineJob和DecodeJob對象,這兩個對象是真正的載入資源的兩個重要類,EngineJob對象負責開啟線程去載入資源,並且載入得資源後轉換到主線程併進行回調;DecodeJob是真正的執行者,它就是去網路載入資源的地方,EngineJob開啟線程,真正執行的是DecodeJob,DecodeJob之後完畢之後叫道EngineJob去分發回調。這就是這兩個類的關係。

⑤:EngineJob和DecodeJob的構建是基本一致的,我們看看比較複雜的DecodeJob的構建:在build方法中,首先通過pool來創建一個DecodeJob對象,然後調用DecodeJob對象的init方法進行初始化,在初始化中值得註意的是調用了decodeHelper對象的init方法。decodeHelper方法是DecodeJob的重要輔助類,後面我們會詳細的接觸它。

⑥:上面也提到回調,這裡先cb添加到engineJob.addCallback();中,然後調用EngineJob的start方法來開啟線程。

我們再來看看start方法中的源碼:

start方法中將會調用GlideExecutor的execute方法:

execute中正式的開啟了線程池進行載入資源。由此我們也正式的由主線程轉到了子線程中。

上面我們也分析了,真正執行線程的是在DecodeJob類中,那麼我們去看看的run方法是怎麼執行的:

run方法中調用runWrapped方法,主要就是在它裡面執行的,來看看它的源碼:

我們知道,在構造DecodeJob時調用init方法是runReason被賦值為INITIALIZE值,由此它將會進入到INITIALIZE分支中,調用getNextStage方法:

在getNextStage方法中經錯幾次的經轉會返回Stage.SOURCE值,然後在調用getNextGenerator方法來獲取當前的currentGenerator對象,我們在來看看獲取的這個currentGenerator到底是什麼?

getNextGenerator方法中根據stage值來創建對象,由此我們可以知道currentGenerator是一個SourceGenerator對象,那麼我們繼續往下走,來看看runGenerators方法:

runGenerators最重要的就是執行了currentGenerator的startNext方法,這裡將會真正的去載入網路資源:

我們從上面知道currentGenerator就是SourceGenerator對象,那麼我們去看看SourceGenerator中startNext的實現:

到這裡就太關鍵了,因為很多人都死在這一步上,根本找不到是怎麼去載入資源的,是怎麼執行到我們熟悉的HttpURLConnection的,那麼到這裡你千萬不能分神,稍微分神就會轉迷糊了,哈哈,那麼接下來就開始分析吧:

由於我們在創建SourceGenerator對象時,只是傳遞了DecodeHelper和回調cb對象,其他的一切初始化操作都是不存在的,所以在startNext中,前面的兩個判斷是不成立的,主要是看while迴圈裡面的內容:

while迴圈中首先調用hasNextModelLoader進行判斷,我們來看下hasNextModelLoader的內容,這裡必須集中精力理解清楚,不然肯定不知所云:

private boolean hasNextModelLoader() {
    return loadDataListIndex < helper.getLoadData().size();
  }

在hasNextModelLoader中它要去helper載入數據,調用getLoadData方法,我們跟進去看看:

在getLoadData方法中是調用GlideContext中的getRegistry方法來回去Registry對象,它是在Glide的構造方法中創建的,而且註冊添加了很多解析器還記得嗎?不記得的趕快去看看Glide的構造方法。這裡我們插入一點非常重要的分析,理解清楚它對接下來的網路執行非常的重要:回到Glidet的構造方法來看看Registry中幾個特殊的解析類:

registry.register(xx)
        .append(xx)
        ...
        .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
        ...
        .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
        ...
        ;

還有印象這段很長的代碼吧?

我們重點來看看上面的兩個append方法,其他的都是一樣或是類似的道理的。跟進去registry的append方法中:

繼續進入modelLoaderRegistry.append方法中:

再跟進multiModelLoaderFactory.append方法中:

重點來了,最後進去add方法:

這裡可以看到什麼?你理解了嗎?

創建了一個Entry對象,把我們的modelClass,dataClass和factory對象關聯起來,然後存放到entries的list集合中,就這麼簡單,但是對這個Entry對象的理解關係到我們後面對整個網路載入的流程十分的巨大,ok,到這裡,我們插入的講解已經完了,主要想告訴你的就是這個entries集合包含了那些對象和創建Entry對象所關聯的類和工廠。

那麼現在回到DecodeHelper中的getLoadData方法中,它從GlideContext獲取到Registry對象,Registry對象有哪些內容在上面的插入講解中也已舉特例分析了,然後調用getModelLoaders方法,並傳進model對象,那麼來看看它是怎麼實現的:

它會從modelLoaderRegistry中獲取,在來看:

它會通過model從getModelLoadersForClass方法中獲取到modelLoaders的集合,來看看:

首先從cache緩存中獲取,如果為空,將會從multiModelLoaderFactory工廠中獲取,在繼續跟進multiModelLoaderFactory的build方法看看:

從entries集合中分別的遍歷出entry對象,然後調用entry.handles來進行匹配是否符合,來看handles方法:

this.modelClass是什麼還記得嗎?沒錯就是在Glide創建Registry對象是append的XX.class,例如:

.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())

而modelClass呢,這裡就是我們傳遞用來載入網路圖片的一個url地址,那麼調用isAssignableFrom方法進行匹配,我們知道entries包含很多的解析器,所以在這一步將會排除掉不匹配的解析器,然後調用調用build方法來創建載入器:

entry.factory知道是什麼吧?沒錯就是append方法中new的一個工廠類。

還是以下麵的特例來說:

.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())

在這裡這個factory就是HttpUriLoader.Factory,我們來看看它是怎麼實現的:

這裡它將會multiFactory.build方法來構造一個ModelLoader對象,看清楚它傳遞的參數:GlideUrl.class, InputStream.class,然後跟進去查看:

歷史總是驚人的相似,再次從entries中獲取Entry對象,然後調用entry.handles方法根據GlideUrl.class, InputStream.class這兩個參數進行匹配過濾。

然後我們找到了以下的append內容相匹配的Entry對象

.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())

找到HttpGlideUrlLoader.Factory之後,然後調用build方法去構建,在build方法中同樣的方式調用entry.factory.build(this),來看看HttpGlideUrlLoader.Factory()的build的源碼:

直接的創建一個HttpGlideUrlLoader對象並返回。

到此我們獲取了真正的圖片載入對象,然後我們回到HttpUriLoader的Factory中:在multiFactory.build(GlideUrl.class, InputStream.class)獲取到HttpGlideUrlLoader對象後,並傳遞到創建的HttpUriLoader對象中去,我們來看看:

把HttpGlideUrlLoader對象賦值給HttpUriLoader的成員變數this.urlLoader中。

ok,到此我們真正的載入器已經獲取到了,當然並不是只有一個,可能有多個,因為在Registry註冊了多個可以解析Uri.class的解析器。

好,我們在回到ModelLoaderRegistry類中的getModelLoaders方法中,從getModelLoadersForClass方法中我們獲取到了可以解析我們請求modle的所有解析器,通過for迴圈遍歷出所有的解析器,存放到filteredLoaders集合中並返回,一直返回到DecodeHelper類中的getLoadData方法中。

然後遍歷modelLoaders集合,分別獲取ModelLoader對象,並調用buildLoadData方法,我們知道modelLoaders集合中一定會包含一個ModelLoader是HttpUriLoader,那來看看它的buildLoadData方法:

它會調用urlLoader.buildLoadData方法,這個urlLoader就是HttpGlideUrlLoader對象,再來看看:

這裡最重要的就是創建個一個HttpUrlFetcher對象:

然後把HttpUrlFetcher對象存放到新建的LoadData對象中:

最後把LoadData對象返回,我們往上返回到SourceGenerator的startNext方法中:

在獲取到LoadData對象後,調用loadData.fetcher.loadData(helper.getPriority(), this);這個方法,從上面分析loadData.fetcher就是HttpUrlFetcher對象,那我們來看看它裡面的loadData是怎麼載入數據的:

它會調用loadDataWithRedirects方法來返回一個InputStream輸入流,來看看loadDataWithRedirects的源碼:

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == -1) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

這裡的代碼我們就非常的熟悉了,稍微的簡單介紹下:

①:connectionFactory對象是在創建HttpUrlFetcher對象時在構造方法中初始化的,它就是DEFAULT_CONNECTION_FACTORY,也就是DefaultHttpUrlConnectionFactory工廠類,當調用它的build方法時:

通過url打開連接,返回一個HttpURLConnection對象,用於網路請求,這些代碼平常我們都有接觸,不多說。

②:建立連接,過去返回狀態碼,判斷,然後通過getStreamForSuccessfulRequest方法返回一個InputStream輸入流。

在獲取到InputStream輸入流之後,最會將會調用callback.onDataReady(result);回調方法,並把輸入流傳遞過去。

那這個callback是什麼呢?

還記得loadData.fetcher.loadData(helper.getPriority(), this);這段代碼吧?

沒錯他就是SourceGenerator類實現的DataCallback回調類。

那麼在進到SourceGenerator找到onDataReady方法吧:

在這裡它又調用cb回調類的onDataFetcherReady方法,並傳遞了相關參數:loadData.fetcher是HttpUrlFetcher,loadData.fetcher.getDataSource()則是DataSource.REMOTE:

 @Override
  public DataSource getDataSource() {
    return DataSource.REMOTE;
  }

那麼這個cb又是什麼呢,它是FetcherReadyCallback回調類,在DecodeJob中實現,那麼回到DecodeJob的onDataFetcherReady方法中:

在onDataFetcherReady方法中保存了相關的參數變數,判斷是否是當前線程,然後調用decodeFromRetrievedData方法來解碼數據:

首先創建一個Resource類型的變數,通過decodeFromData方法把輸入流解碼並返回給resource,由此可也看出,解碼主要是在decodeFromData方法中:

在這裡已經體現loadData.fetcher這個fetcher的用意,主要是去關閉輸入流和HttpUrlConnection的。

@Override
  public void cleanup() {
    if (stream != null) {
      try {
        stream.close();
      } catch (IOException e) {
        // Ignore
      }
    }
    if (urlConnection != null) {
      urlConnection.disconnect();
    }
  }

在這之前它又調用decodeFromFetcher方法來進行解碼返回一個Resource:

在這裡獲取到data.getClass,這個Class就是InputStrem.class,那麼在調用decodeHelper.getLoadPath方法後,我們來看看做了哪些操作:

getLoadPath方法啥也沒做,直接調用Registry中的getLoadPath,並傳遞了擁有的變數,其中resourceClass是Object.class,transcodeClass則是Drawable.class,這是在前面已構建初始化好的,直接拿來用。

我們在來跟進去看看

這裡首先從loadPathCache緩存中獲取LoadPath對象,如果沒有則調用getDecodePaths方法進行獲取:

getDecodePaths方法中還是蠻重要的,對真個解碼過程的理解有很大的幫助,所以我們來認真的分析下:

①:首先從decoderRegistry.getResourceClasses方法中獲取已經註冊的registeredResourceClasses:

還記得我們創建Registry是註冊了一大堆的東西嗎?

對,通過handles方法對InputStrem.class和Object.class進行匹配的就是我們用紅色框畫出來的部分。由此我們可以得出registeredResourceClasses集合中分別對應的是Bitmap.class,BitmapDrawable.class和GifDrawable.class的三種Class對象。

②:遍歷registeredResourceClasses集合,通過transcoderRegistry.getTranscodeClasses方法獲取到已註冊的registeredTranscodeClasses集合:

這是對registeredResourceClasses集合的再次匹配,我們知道transcodeClasses實際上是Drawable.class,在registeredResourceClasses集合中只有BitmapDrawable.class和GifDrawable.class是繼承Drawable的,由此我們得出registeredTranscodeClasses包含BitmapDrawable.class和GifDrawable.class兩種Class對象。

那麼由此可知decoders存放了兩種解碼器:

對應的BitmapDrawable的BitmapDrawableDecoder解碼器。
對應的GifDrawable.class的StreamGifDecoder解碼器。

這個非常重要,在後面有使用到。必須理解清楚。

③:通過上面獲取的registeredResourceClass和registeredTranscodeClass獲取到transcoder轉化器,主要是從transcoderRegistry集合中通過get方法獲取,而transcoderRegistry又包含哪些轉化器呢?

而又有get方法我們可以知道:

當時普通圖片是transcoder將會是BitmapDrawableTranscoder,如是過動態圖片gif的話transcoder將會是GifDrawableBytesTranscoder。

然後把它們存放到創建的DecodePath對象中:

最後是把封裝好的DecodePath對象存儲到decodePaths集合中並返回。

然後在回到Registry中的getLoadPath方法中,在獲取到decodePaths集合中,把它和dataClass, resourceClass, transcodeClass又封裝到LoadPath對象中,並緩存到loadPathCache對象中。

再次返回DecodeJob類中的decodeFromFetcher方法中,在獲取到LoadPath後,調用runLoadPath方法,來看下它的源碼:

它調用glideContext.getRegistry()獲取Registry對象,然後調用Registry的getRewinder獲取裝置器,跟進去看看:

在getRewinder方法中直接的調用dataRewinderRegistry對象的build方法,那麼這個dataRewinderRegistry又是什麼呢?

它是在Registry構造方法中創建的對象:

this.dataRewinderRegistry = new DataRewinderRegistry();

而且在Glide中進行了註冊:

獲取到factory之後調用dataRewinderRegistry.register方法註冊到Map集合rewinders中去:

再來看看factory.getDataClass()都獲取到哪些key:

①:在ByteBufferRewinder.Factory中調用getDataClass()獲取的key是ByteBuffer.class

②:在InputStreamRewinder.Factory中調用getDataClass()獲取的key是InputStream.class

上面兩個key值在接下來會使用到。

那麼我們在返回到getRewinder方法中,調用dataRewinderRegistry對象的build方法到底做了什麼:

在build的方法中總共做了四件事,分別用不同的線框標註出來了:

①:從rewinders集合中獲取到和data.getClass()相匹配的Factory對象,從上面的分析中,我們知道rewinders註冊的只有兩個key,分別是ByteBuffer.class和InputStream.class,而我們又知道data.getClass()是一個InputStream.class,由此可以匹配成功。這個result就是InputStreamRewinder.Factory對象。

②:假如result沒有匹配成功的話,也就是沒有通過key匹配成功,那麼就進行遍歷rewinders集合,通過values值進行匹配,把匹配成功的Factory對象再賦值給result。

③:假如通過鍵key和值values都沒有匹配成功,那麼也不要緊,直接使用預設的DEFAULT_FACTORY

④:最後調用result的build方法。

這裡由於我們通過鍵key直接已匹配成功,所以我們知道result就是InputStreamRewinder.Factory對象,那麼來看看調用它的build方法做了什麼事情:

這裡直接的返回了一個InputStreamRewinder對象,在InputStreamRewinder的構造方法中又創建了RecyclableBufferedInputStream對象,並它InputStream流傳遞進去:

RecyclableBufferedInputStream繼承FilterInputStream流,那麼我們的InputStream流最終保存的位置就是在FilterInputStream類中。

ok,到這裡我們知道了InputStream數據流的去向,而在InputStreamRewinder中又有對InputStream流的引用,那麼在回到InputStreamRewinder之後我們來看看接下來又做了什麼呢?

再次定位到DecodeJob類中的runLoadPath方法,在獲取到InputStreamRewinder後,它會調用path.load方法並返回Resource資源類,這個path是在之前獲取到的LoadPath對象,這裡註意下,傳遞了一個new DecodeCallback 對象的回調類,後面會用到。最後調用InputStreamRewinder.cleanup()進行資源釋放。那麼我們來看看LoadPath的load方法是怎麼實現的:

它直接調用loadWithExceptionList方法進行移交:

在loadWithExceptionList方法中則會調用decode方法進行解碼:

decode方法中調用decodeResource方法,然後在調用decodeResourceWithList方法真正的開始解碼:

①:這裡首先遍歷decoders集合,分別的獲取到ResourceDecoder解碼器,還記得我們的decoders都包含哪些解碼器嗎?沒錯主要包含兩種:BitmapDrawable.class和GifDrawable.class。不記得的往上面翻下,上面已詳細的講解過了。

②:然後通過rewinder.rewindAndGet()獲取我們的InputStream數據流:

③:通過decoder.handles(data, options)方法來過濾掉不相匹配的解碼器,再來看看前面的這張圖:

Bitmap.class已被我們過濾掉,剩下的就只有BitmapDrawable.class和GifDrawable.class,當我們調用handles方法進行匹配時,StreamGifDecoder解碼器是怎麼處理的:

它主要是針對options屬性為gif的圖片來解碼的,其實這不用我說大家也是知道的,而我們載入的只是普通的靜態圖片,因此它是不符合我們的匹配規則的,在來看BitmapDrawableDecoder中,它傳入的是StreamBitmapDecoder的解碼器:

而downsampler.handles(source);中只要source是InputStream就返回true,因此匹配成功。

那麼由上分析,我們真正的decoder就是BitmapDrawableDecoder。

回到decodeResourceWithList方法中,獲取到真正的decoder解碼器,將會調用decode方法正式解碼:

它又會交給StreamBitmapDecoder的decode方法,然後在轉移到Downsampler類中的decode方法中:

這裡就不再一個一個的看方法了,主要是在Downsampler中decodeFromWrappedStreams方法中把InputStream數據流根據width, height, options給轉化成Bitmap圖片,然後把Bitmap存放到BitmapResource對象中去且直接的返回本身。

ok,獲取到BitmapResource對象後,再次返回最初開始解碼的DecodePath類中的decode方法中:

從上面分析我們知道,我們解析的是普通的圖片,所以這個transcoder就是BitmapDrawableTranscoder轉換器類。(前面有詳細的分析,忘記的可以向上翻看)

接著我們去BitmapDrawableTranscoder中的transcode方法看看:

在transcode方法中通過toTranscode.get()獲取bitmap圖片,這個bitmap雖然經過多層的包裝,它其實就是一個BitmapResource對象,這在上面我們有清楚的分析,只不過在返回的回調方法中有多次的封裝而已。

獲取到Bitmap圖片後,調用LazyBitmapDrawableResource的obtain方法再次進行一次的封裝:

把Bitmap封裝到LazyBitmapDrawableResource對象中進行返回。

ok,到這裡其實已完成了完完全全的解碼和封裝到。

一路把LazyBitmapDrawableResource對象返回到DecodeJob類中的decodeFromRetrievedData中,並賦值給resource變數:

當resource不為空時,將會調用notifyEncodeAndRelease方法並傳遞參數:

方法裡面就不多解析了,它會調用notifyComplete方法:

這裡調用了callback.onResourceReady(resource, dataSource);方法,那這個callback是什麼呢?它其實是一個ResourceCallback,在SingleRequest中發起的,並且SingleRequest還實現了ResourceCallback介面內的方法:

public void onResourceReady(Resource<?> resource, DataSource dataSource) {
    stateVerifier.throwIfRecycled();
    loadStatus = null;
    if (resource == null) {
      GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
          + "object of " + transcodeClass + " inside, but instead got null.");
      onLoadFailed(exception);
      return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
      releaseResource(resource);
      GlideException exception = new GlideException("Expected to receive an object of "
          + transcodeClass + " but instead" + " got "
          + (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
          + "Resource{" + resource + "}."
          + (received != null ? "" : " " + "To indicate failure return a null Resource "
          + "object, rather than a Resource object containing null data."));
      onLoadFailed(exception);
      return;
    }

    if (!canSetResource()) {
      releaseResource(resource);
      // We can't put the status to complete before asking canSetResource().
      status = Status.COMPLETE;
      return;
    }

    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }

重點講解下:在onResourceReady中使用resource.get(),我們知道這個resource就是LazyBitmapDrawableResource對象,來看看這個get獲取到了什麼:

在get中獲取到了BitmapDrawable對象直接複製給了received變數,然後調用重載方法onResourceReady方法:

在onResourceReady方法中調用了target.onResourceReady(result, animation);還記得target是什麼嗎?

它在load方法中已講解過,就是DrawableImageViewTarget對象,調用它的onResourceReady會轉移到父類ImageViewTarget中:

然後在調用setResourceInternal方法:

setResource是抽象方法,由它的子類實現,我們在回到DrawableImageViewTarget中:

到這裡直接調用view進行設置圖片了,這個view就是我們Imageview了,所以到這裡就設置好了載入的圖片了。

ok,到這裡就完完全全的解析完了Glide的執行原理了。

說實在的Glide的源碼非常的複雜,往往深入進去就無法出來了,相信這篇博客很能好的給大家一個參考,能讓大家對整個Glide有個全面的理解。

好了,終於寫完了,真的很不容易,很辛苦,希望大家也能從中學到知識。

各位如果還有哪裡不明白的,或是我這裡講的還不夠透徹,亦或是講錯了的地方請留言指正,讓我們共同進步,謝謝

同時,請大家掃一掃關註我的微信公眾號,雖然寫的不是很勤,但是每一篇都有質量保證,讓您學習到真正的知識。

請關註我的微信公眾號


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天介紹一個網路上並不常用的插件two.js,剛開始學習的過程中,發現網上並沒有合適的教程,在此發表基本操作 two.js是一款網頁二維繪圖軟體,可以在指定區域內產生自設的各種動畫效果 下載網址如下: https://two.js.org/#download class1: 一:如何使用: 首先在頁 ...
  • 做頁面需要兩個時間輸入框一個顯示當前時間,一個顯示之前的時間,並且需要一個select下拉框控制兩個時間輸入框之間的差,效果如下圖: 這裡使用的是My97DatePicer,簡單方便,引入my97插件,設置input時間框的格式,這裡設置的時間最大是當前時間,開始時間框不能比結束時間框的時間大 弄完 ...
  • 前言 個人觀點,供您參考 觀點源自作者的使用經驗和日常研究 排名基於框架的受歡迎度, 語法結構, 易用性等特性 ...
  • 今天逛園子,偶然看到最多推薦,有點好奇。 F12查看元素,發現是在css中加了一個after,內容中增加了一個“w”。 本著娛樂至上的準則,自己也試試。複製以下css到設置自定義css中 #digg_count:after{ content: 'w'; } :after, :before { web ...
  • 1、BUG-In android7 phone can not slide above 註:Android 7.0以上,iScroll滑動緩慢遲鈍問題解決 What browser are you using? There was a fix to iScroll's handling of pas ...
  • 介紹 vue schart 是使用vue.js封裝了sChart.js圖表庫的一個小組件。支持vue.js 1.x & 2.x 倉庫地址: "https://github.com/lin xin/vue schart" sChart.js 作為一個小型簡單的圖表庫,沒有過多的圖表類型,只包含了柱狀圖 ...
  • 本來是在看阮大神寫的ajax教程,突然發現點擊目錄文字會跳轉到相對應的文本內容,於是乎激發了我的興趣。 這個究竟怎麼做的,剛開始看的時候一知半解,找度娘就是:“點擊跳轉到頁面指定位置”,找了下,原來專業術語叫:錨點。 度娘真是個博大精深的地方,有著多種的方法可以使用。 最簡單的一種: 設置a標簽的錨 ...
  • 參考資料:ios模式詳解,runtime完整總結 類和對象 Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。 這種特性意味著Obje ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...