Glide 知識梳理(6) - Glide 源碼解析之流程剖析

1、前言

不得不說,Glide真的是十分難啃,來來回回看了不少的文章,並對照着源碼分析了一遍,才理清了大概的思路,但願這篇文章能對你們有必定的幫助。java

爲何要閱讀 Glide 的源碼

在進入正題以前讓咱們先談一些題外話,就是 爲何咱們要去看 Glide 的源碼android

若是你們有閱讀過以前的五篇關於Glide使用的教程:程序員

Glide 知識梳理(1) - 基本用法
Glide 知識梳理(2) - 自定義 Target
Glide 知識梳理(3) - 自定義 transform
Glide 知識梳理(4) - 自定義 animate
Glide 知識梳理(5) - 自定義 GlideModuleapi

能夠發現其實Glide的功能已經很完備了,不管是佔位符、錯誤圖片仍是請求完後對於返回圖片的變換,都提供瞭解決的方案,徹底能夠知足平常的需求。數組

那麼,咱們爲何要花費大量的時間去看Glide的源碼呢,我本身的觀點是如下幾點:緩存

  • 理解API的原理。在以前介紹使用的幾篇文章中,咱們談到了許多的方法,例如placeholder/error/...,還講到了自定義transform/target/animate。可是因爲Glide將其封裝的很好,僅僅經過簡單使用你根本沒法瞭解這些用法最後是如何生效的,只有經過閱讀源碼才能明白。
  • 學習圖片加載框架的核心思想。不管是古老的ImageLoader,仍是後來的Picassofresco,對於一個圖片加載框架來講,都離不開三點:請求管理、工做線程管理和圖片緩存管理。閱讀源碼將有助於咱們學習到圖片請求框架對於這三個核心問題的解決方案,這也是我認爲 最關鍵的一點
  • 學習Glide的架構設計,對於Glide來講,這一點可能適合於高水平的程序員,由於實在是太複雜了。

怎麼閱讀 Glide 的源碼

在閱讀源碼以前,仍是要作一些準備性的工做的,否則你會發現沒過多久你就想放棄了,個人準備工做分爲如下幾步:網絡

  • 掌握Glide的高級用法。千萬不要知足於調用load方法加載出圖片就知足了,要學會去了解它的一些高級用法,例如在 Glide 知識梳理(5) - 自定義GlideModule 一文中介紹如何自定義ModelLoader,你就會對ModelLoader有個大概的印象,知道它是用來作什麼的,否則在源碼中看到這個類的時候確定會一臉懵逼,沒多久就放棄了。
  • 看幾篇網上寫的不錯的文章,例如郭神的 Android圖片加載框架最全解析(二),從源碼的角度理解Glide的執行流程 ,沒必要過於關注實現的細節,而是注意看他們對每段代碼的描述,先有個大概的印象。
  • 從一個最簡單的Demo入手,經過 斷點的方式,一步步地走,觀察每一個變量的類型和值。
  • 最後,不管如今記得如何清楚,必定本身親自寫文檔,最重要的是 畫圖,把整個調用的流程經過圖片的形式整理出來,否則真的是會忘的。

源碼分析流程

咱們從最簡單的例子入手,加載一個網絡的圖片地址,並在ImageView上展現。架構

Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(mImageView);
複製代碼

這上面的鏈式調用分爲三步,其中前兩步是進行一些準備工做,真正進行處理的邏輯是在第三步當中: app

2、with(Activity activity)

with(Activity activity)Glide的一個靜態方法,當調用該方法以後會在Activity中添加一個Glide內部自定義的RequestManagerFragment,當Activity的狀態發生變化時,該Fragment的狀態也會發生相應的變化。通過一系列的調用,這些變化將會通知到RequestManagerRequestManager則經過RequestTracker來管理全部的Request,這樣RequestManager在處理請求的時候,就能夠根據Activity當前的狀態進行處理。 框架

3、load(String string)

with方法會返回一個RequestManager對象,接下來第二步。

Glide 知識梳理(1) - 基本用法 中,咱們學習了許多load的重載方法,能夠從urlSDCardbyte[]數組中來加載。這一步的目的是根據load傳入的類型,建立對應的DrawableTypeRequest對象,DrawableTypeRequest的繼承關係以下所示,它是Request請求的建立者,真正建立的邏輯是在第三步中建立的。

全部的 load方法最終都會經過 loadGeneric(Class<T> class)方法來建立一個 DrawableTypeRequest對象,在 DrawableTypeRequest建立時須要傳入兩個關鍵的變量:

  • streamModelLoader
  • fileDescriptorModelLoader

這兩個變量的類型均爲ModelLoader,看到這個是否是有似曾相識的感受,沒錯,在 Glide 知識梳理(5) - 自定義GlideModule 中咱們介紹了若是經過OkHttpClient來替換HttpURLConnection時就已經介紹了它,這 兩個變量決定了獲取圖片資源的方式

如今,只要明白上面這點就能夠了,後面在用到它其中的成員變量時,咱們再進行詳細的分析。

4、into(ImageView imageView)

下面,咱們來看真正的重頭戲,即into方法執行過程,該方法中包含了加載資源,設置資源到對應目標的一整套邏輯。

4.1 GenericRequestBuilder

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {

    public Target<TranscodeType> into(ImageView view) {
        Util.assertMainThread();
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }
        if (!isTransformationSet && view.getScaleType() != null) {
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    applyCenterCrop();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    applyFitCenter();
                    break;
                //$CASES-OMITTED$
                default:
                    // Do nothing.
            }
        }
        //若是是 ImageView,那麼返回的是 GlideDrawableImageViewTarget。
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }

}
複製代碼

因爲DrawableTypeRequest並無重寫into方法,所以會調用到它的父類DrawableRequestBuilderinto(ImageView)方法中,DrawableRequestBuilder又會調用它的父類GenericRequestBuilderinto(ImageView)方法。

這裏首先會根據viewscaleType進行變換,而後再經過Glide.buildImageViewTarget方法建立TargetTarget的含義是 資源加載完畢後所要傳遞的對象,也就是整個加載過程的終點,對於ImageView來講,該方法建立的是GlideDrawableImageViewTarget

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {

    public <Y extends Target<TranscodeType>> Y into(Y target) {
        Util.assertMainThread();
        if (target == null) {
            throw new IllegalArgumentException("You must pass in a non null Target");
        }
        if (!isModelSet) {
            throw new IllegalArgumentException("You must first set a model (try #load())");
        }

        //若是該 Target 以前已經有關聯的請求,那麼要先將以前的請求取消。
        Request previous = target.getRequest();
        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }

        //建立 Request,並將 Request 和 Target 關聯起來。
        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);

        //這一步會觸發任務的執行。
        requestTracker.runRequest(request);
        return target;
    }

}
複製代碼

接下來,看GenericRequestBuilder中的into(Target)方法,在into方法中會經過buildRequest方法建立Request,並將它和Target進行 雙向關聯Request的實現類爲GenericRequest

在建立完Request以後,經過RequestTrackerrunRequest嘗試去執行任務。

4.2 RequestTracker

public class RequestTracker {

    public void runRequest(Request request) {
        requests.add(request);
        if (!isPaused) {
            request.begin();
        } else {
            pendingRequests.add(request);
        }
    }

}
複製代碼

RequestTracker會判斷當前界面是否處於可見狀態,若是是可見的,那麼就調用Requestbegin方法發起請求,不然就先將請求放入到等待隊列當中,Request的實現類爲GenericRequest

4.3 GenericRequest

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback {

    @Override
    public void begin() {
        startTime = LogTime.getLogTime();
        if (model == null) {
            onException(null);
            return;
        }

        status = Status.WAITING_FOR_SIZE;
        //首先判斷寬高是否有效,若是有效那麼就調用 onSizeReady。
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            //先獲取寬高,最終也會調用到 onSizeReady 方法。
            target.getSize(this);
        }

        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
            //通知目標回調。
            target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished run method in " + LogTime.getElapsedMillis(startTime));
        }
    }

    @Override
    public void onSizeReady(int width, int height) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
        if (status != Status.WAITING_FOR_SIZE) {
            return;
        }
        status = Status.RUNNING;

        width = Math.round(sizeMultiplier * width);
        height = Math.round(sizeMultiplier * height);

        ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
        final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

        if (dataFetcher == null) {
            onException(new Exception("Failed to load model: \'" + model + "\'"));
            return;
        }
        ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
        }
        loadedFromMemoryCache = true;
        //調用 Engine 的 load 方法進行加載。
        loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this);
        loadedFromMemoryCache = resource != null;
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
    }

}
複製代碼

GenericRequestbegin()方法中,首先判斷寬高是否有效,若是有效那麼就調用onSizeReady方法,假如寬高無效,那麼會先計算寬高,計算完以後也會調用onSizeReady,這裏面會調用Engineload方法去加載。

4.4 Engine

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {

    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();

        //建立緩存的`key`。
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        //一級內存緩存,表示緩存在內存當中,而且目前沒有被使用的資源。
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
            cb.onResourceReady(cached);
            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);
            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 對應於一個任務。
        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);

        //DecodeJob 對應於任務的處理者。
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);

        //包含了任務以及任務的處理。
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        //將該 Runnable 放入到線程池當中,執行時會調用 run() 方法。
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

}
複製代碼

Enginebegin方法中,會在內存中查找是否有緩存,若是在內存當中找不到緩存,那麼就會建立EngineJobDecodeJobEngineRunnable這三個類,嘗試從數據源中加載資源,觸發的語句爲engineJob.start(runnable)

4.5 EngineRunnable

class EngineRunnable implements Runnable, Prioritized {

    @Override
    public void run() {
        if (isCancelled) {
            return;
        }

        Exception exception = null;
        Resource<?> resource = null;
        try {
            //decode 方法返回 Resource 對象。
            resource = decode();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Exception decoding", e);
            }
            exception = e;
        }

        if (isCancelled) {
            if (resource != null) {
                resource.recycle();
            }
            return;
        }
        //若是成功加載資源,那麼就會回調onLoadComplete方法。
        if (resource == null) {
            onLoadFailed(exception);
        } else {
            onLoadComplete(resource);
        }
    }

    private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            //從本地緩存中獲取。
            return decodeFromCache();
        } else {
            //從源地址中獲取。
            return decodeFromSource();
        }
    }

    private void onLoadComplete(Resource resource) {
        manager.onResourceReady(resource);
    }

    private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }

        if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }

    private Resource<?> decodeFromSource() throws Exception {
        return decodeJob.decodeFromSource();
    }

}
複製代碼

EngineJobstart方法會經過內部線程池的submit方法提交任務,當任務被調度執行時,會調用到EngineRunnablerun()方法。

run()方法中,會根據任務的類型來判斷是從磁盤緩存仍是從原始數據源中獲取資源,即分別調用DecodeJobdecodeFromCache或者decodeFromSource,最終會將資源封裝爲Resource<T>對象。

假如成功獲取到了資源,那麼會經過manager.onResourceReady返回,manager的類型爲EngineRunnableManager,其實現類爲以前咱們看到的EngineJob

4.6 EngineJob

class EngineJob implements EngineRunnable.EngineRunnableManager {

    @Override
    public void onResourceReady(final Resource<?> resource) {
        this.resource = resource;
        MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
    }

    private void handleResultOnMainThread() {
        if (isCancelled) {
            resource.recycle();
            return;
        } else if (cbs.isEmpty()) {
            throw new IllegalStateException("Received a resource without any callbacks to notify");
        }
        engineResource = engineResourceFactory.build(resource, isCacheable);
        hasResource = true;

        // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
        // synchronously released by one of the callbacks.
        engineResource.acquire();
        listener.onEngineJobComplete(key, engineResource);

        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                engineResource.acquire();
                //ResourceCallback 的實現類爲 GenericRequest
                cb.onResourceReady(engineResource);
            }
        }
        // Our request is complete, so we can release the resource.
        engineResource.release();
    }

}
複製代碼

EngineJobonResourceReady方法中,會將Resource經過Handler的方法傳遞到主線程,並調用handleResultOnMainThread,注意該方法中帶有註釋的部分,這裏的ResourceCallback就是咱們以前看到GenericRequest

4.7 GenericRequest

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback {

    @SuppressWarnings("unchecked")
    @Override
    public void onResourceReady(Resource<?> resource) {
        if (resource == null) {
            onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
                    + " inside, but instead got null."));
            return;
        }

        Object received = resource.get();
        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
            releaseResource(resource);
            onException(new Exception("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.")
            ));
            return;
        }

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

        onResourceReady(resource, (R) received);
    }

    private void onResourceReady(Resource<?> resource, R result) {
        // We must call isFirstReadyResource before setting status.
        boolean isFirstResource = isFirstReadyResource();
        status = Status.COMPLETE;
        this.resource = resource;

        if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
                isFirstResource)) {
            GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
            //通知目標資源已經獲取到了。
            target.onResourceReady(result, animation);
        }

        notifyLoadSuccess();

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
                    + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
        }
    }

}
複製代碼

GenericRequestonResourceReady方法中,會調用TargetonResourceReady方法,也就是咱們最開始講到的GlideDrawableImageViewTarget

4.8 GlideDrawableImageViewTarget

public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {

    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        if (!resource.isAnimated()) {
            //TODO: Try to generalize this to other sizes/shapes.
            // This is a dirty hack that tries to make loading square thumbnails and then square full images less costly
            // by forcing both the smaller thumb and the larger version to have exactly the same intrinsic dimensions.
            // If a drawable is replaced in an ImageView by another drawable with different intrinsic dimensions,
            // the ImageView requests a layout. Scrolling rapidly while replacing thumbs with larger images triggers
            // lots of these calls and causes significant amounts of jank.
            float viewRatio = view.getWidth() / (float) view.getHeight();
            float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
            if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
                    && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
                resource = new SquaringDrawable(resource, view.getWidth());
            }
        }
        super.onResourceReady(resource, animation);
        this.resource = resource;
        resource.setLoopCount(maxLoopCount);
        resource.start();
    }

    /** * Sets the drawable on the view using * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}. * * @param resource The {@link android.graphics.drawable.Drawable} to display in the view. */
    @Override
    protected void setResource(GlideDrawable resource) {
        view.setImageDrawable(resource);
    }
}
複製代碼

GlideDrawableImageViewTargetonResourceReady方法中,會首先回調父類的super.onResourceReady,父類的該方法又會回調setResource方法,而GlideDrawableImageViewTargetsetResource就會經過setResource將加載好的圖片資源設置進去。

因爲夾雜着代碼和文字看着比較亂,整個的流程圖以下所示,並在有道雲筆記上整理了一下關鍵的類和函數調用語句,直達連接

第三步調用流程

5、小結

這篇文章目的是讓你們對整個流程有一個大體的瞭解,因此對於不少細節問題沒有深究,例如:

  • Engineload()方法中,會執行兩次內存緩存的判斷,這裏面實現的機制是怎麼樣的?
  • EngineRunnabledecode()方法中,採用DecodeJob去數據源加載資源,資源加載以及解碼的過程是怎麼樣的?
  • EngineRunnable中回調給EngineResource<T>是一個什麼樣的對象?

對於以上的這些問題,會在後面單獨的一個個章節進行分析。

相關文章
相關標籤/搜索