天天看點

Glide 資源加載流程分析

轉載請标明位址 QuincySx: http://www.jianshu.com/p/eed7054e3722

這是 Glide 的第二篇,在上一篇中講的都是大概流程,直接閱讀起來可能比較困難,推薦結合源碼浏覽,在這一篇中就講資源加載,是以貼上來的源碼就會多一些。

public Target<TranscodeType> into(ImageView view) {
       .....
        //調用 (glide.buildImageViewTarget()
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }
           

然後在調用以下方法 這個地方無論調什麼都是生成 ViewTarget 就不細追究了

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }
           

建立完 ViewTarget 之後調用 init()

public <Y extends Target<TranscodeType>> Y into(Y target) {
     ...
        //在 Target 中擷取請求對象
        Request previous = target.getRequest();

        //如果有請求對象則把它清掉
        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }
        
        //重新建構新的請求對象 并設定到 target 中,添加生命周期監聽
        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
        //開始請求資源
        requestTracker.runRequest(request);

        return target;
    }
           

然後我們進去到 RequestTracker 的 runRequestra() 方法中

public void runRequest(Request request) {
        //先把請求添加到隊列中,然後判斷這個隊列是不是暫停狀态,暫停的話就放到暫停清單裡,不是暫停的話就開始運作
        requests.add(request);
        if (!isPaused) {
            request.begin();
        } else {
            pendingRequests.add(request);
        }
    }
           

簡單說一下 RequestTracker 請求隊列,為什麼這裡要維護一個暫停或運作的狀态呢,因為 RequestTracker 的生命周期是跟随 RequestManager 的,如果你看過 Glide 往頁面中添加 Fragment 的那個步驟的話,你就會發現 RequestManager 是在 Fragment 中維護的,他同樣監聽這 Fragment 的顯示狀态,通俗點說就是一個顯示的頁面那麼請求隊列的狀态就是運作,其他已不顯示的頁面 隊列就會成為暫停狀态,因為隊列是監聽 Fragment 的生命周期的,會動态調整每個頁面請求隊列的狀态,已達到節省系統資源的目的。

接下來再看 request.begin() 方法

public void begin() {
        startTime = LogTime.getLogTime();
        if (model == null) {
            onException(null);
            return;
        }
        
        //更新請求的狀态
        status = Status.WAITING_FOR_SIZE;
        //因為如果沒有設定縮小 overrideWidth,overrideHeight 預設為 -1
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            //如果設定了圖檔縮小,并且重新設定的寬高大于0 直接調用 onSizeReady
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            //如果沒有設定了圖檔縮小則去計算 View 本身的寬高
            target.getSize(this);
        }

        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
            //設定占位圖檔
            target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished run method in " + LogTime.getElapsedMillis(startTime));
        }
    }
           

再看 onSizeReady 方法之前我們先看一下 ViewTager 的 getSize 方法

public void getSize(SizeReadyCallback cb) {
            int currentWidth = getViewWidthOrParam();
            int currentHeight = getViewHeightOrParam();
            //擷取View 的寬高
            if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {  
                //寬高不為0回調 onSizeReady
                cb.onSizeReady(currentWidth, currentHeight);
            } else {
                //寬高為0 就監聽 View 的繪制之前 的事件再去獲得寬高,回調進行加載
                // 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 observer = view.getViewTreeObserver();
                    layoutListener = new SizeDeterminerLayoutListener(this);
                    observer.addOnPreDrawListener(layoutListener);
                }
            }
        }
           

//檢視 onSizeReady 方法

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

//我們接下來看一下 engine 的 load 方法 在調用 load 方法的時候,看到最後有一個 this 參數這是一個回調接口 這個地方注意一下

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());
        //根據 key 在記憶體緩存中擷取緩存
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        //如果不為 null 則調用完成的回調接口
        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;
        }

        //jobs 是一個正在運作的任務集合,擷取 key 相同的任務 防止有相同的請求正在運作
        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 = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        //在建立 EngineRunnable 的時候他把 engineJob  也傳了進去而他繼承自
        //EngineRunnable.EngineRunnableManager 這個地方記住
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        //目前請求加入 jobs 維護
        jobs.put(key, engineJob);
        //添加回調到 GenericRequest 的方法
        engineJob.addCallback(cb);
        //運作任務 
        engineJob.start(runnable);

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

接下來我們再看 engineJob.start(runnable); 方法

public void start(EngineRunnable engineRunnable) {
        this.engineRunnable = engineRunnable;
        //因為 EngineRunnable 是繼承的 Runnable 是以執行 EngineRunnable 的 run 方法
        //還要注意的是這個地方是 在 緩存服務中送出 了工作線程
        future = diskCacheService.submit(engineRunnable);
    }
           

我們接着看 EngineRunnable 的 run 方法 ,因為這個方法會走兩次稍微注意一下

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

        Exception exception = null;
        Resource<?> resource = null;
        try {
            //開始獲得資料
            resource = decode();
        } catch (OutOfMemoryError e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Out Of Memory Error decoding", e);
            }
            exception = new ErrorWrappingGlideException(e);
        } 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;
        }
        
        //檢視資源加載是否成功
        if (resource == null) {
            onLoadFailed(exception);
        } else {
            onLoadComplete(resource);
        }
    }

   //這個方法将會調用兩次
   //第一次:因為在構造中 this.stage = Stage.CACHE 是以第一次肯定調用 decodeFromCache() 方法
   //第二次:因為 stage = Stage.SOURCE 狀态改變 是以調用 decodeFromSource()
   private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            return decodeFromCache();
        } else {
            return decodeFromSource();
        }
    }

    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();
    }
    
    private void onLoadComplete(Resource resource) {
        manager.onResourceReady(resource);
    }
    
    //如果資源加載失敗會把stage 加載狀态修改 然後調用 回調接口去請求資源
    private void onLoadFailed(Exception e) {
        if (isDecodingFromCache()) {
            stage = Stage.SOURCE;
            manager.submitForSource(this);
        } else {
            //如果資源請求還失敗就會抛出異常
            manager.onException(e);
        }
    }
           

如果加載資源的話 會調用 decodeJob.decodeFromSource()

public Resource<Z> decodeFromSource() throws Exception {
        //擷取資源
        Resource<T> decoded = decodeSource();
        //資源轉換以後再說不在這篇文章的讨論範圍内
        return transformEncodeAndTranscode(decoded);
    }
           

我們先看擷取資源的方法

private Resource<T> decodeSource() throws Exception {
        Resource<T> decoded = null;
        try {
            long startTime = LogTime.getLogTime();
            //它是通過加載器 fetcher 來擷取資源 我們的環境是(加載 String 的 Url 并且沒有配置第三方網絡加載器)
            //那麼 fetcher 在哪裡來的呢 還記不記得 GenericRequest 的 onSizeReady() 方法 ,他是從 modelLoader.getResourceFetcher() 擷取的 那麼 modelLoader  哪來的呢
            //你可以去 Glide 構造裡發現 register(String.class, InputStream.class, new StreamStringLoader.Factory()); 這麼一句代碼
            //我們看到了 StreamStringLoader 這個類 但是并沒有發現 getResourceFetcher() 方法,我們看一下他的父類 StringLoader 現在發現了 getResourceFetcher 方法 在看到父類的時候我們又發現 父類是一個帶參的構造,StreamStringLoader 在構造的時候查找了 Uri 的加載器給了父類 (查找就在流程我就不細分析了,自己看源碼吧)
            //我們又在 Glide 構造裡查到 Uri 的加載器是 StreamUriLoader 
            //進去有一看 StreamUriLoader 繼承自構造 UriLoader 的時候 傳入了  GlideUrl 類型的加載器
            //接着找到 HttpUrlGlideUrlLoader ,我們接着看 UriLoader 的 getResourceFetcher() 方法 他判斷了資源是本地資源還是網絡資源,本地資源就直接加載,方法自己看一下吧,否則就調用 HttpUrlGlideUrlLoader 進行網絡加載 
            // HttpUrlGlideUrlLoader 的 getResourceFetcher 是個 HttpUrlFetcher 調用 loadData 進行網絡加載,怎麼加載的代碼自己看一下吧,我就不貼了
            //這個地方比較亂,大家慢慢理一下
            final A data = fetcher.loadData(priority);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Fetched data", startTime);
            }
            if (isCancelled) {
                return null;
            }
            //如果打開磁盤緩存就将在的資源緩存起來,然後再拿出來 ,并且裝換成 Resource
            //如果沒有開啟磁盤緩存 就直接轉換為 Resource
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }
           

圖檔的變換下篇文章講,如果沒有什麼妖蛾子的話該調用 onLoadComplete() 方法,回調 EngineJob.onResourceReady(resource); 的方法 ,接着往下跟蹤發現來到了如下方法

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;

        //記錄是否回調的标志 此時 acquire 為 1
        engineResource.acquire();

        //回調 Engine 的 onEngineJobComplete 的方法做了這麼幾件事件事
        //1. 添加 Engine 的回調
        //2. 緩存資源
        //3. 将任務在 jobs 中删除任務
        listener.onEngineJobComplete(key, engineResource);

        //調用所有等待此資源加載的回調
        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                //回調一次 acquire 的數值加 1
                engineResource.acquire();
                //回調 GenericRequest 的 onResourceReady  代碼看下方
                cb.onResourceReady(engineResource);
            }
        }
        
        //檢視acquire 的值減 1 是否等于0,如果等于零,就說明此資源沒有任何回調,則
        回調Engine 的 onResourceReleased 在活動資源緩存中删除,并且判斷是否緩存到記憶體中,然後清理釋放資源
        engineResource.release();
    }
           

接下來看 GenericRequest 的 onResourceReady() 方法

資源有了剩下的就是将它放到 imageView 上

public void onResourceReady(Resource<?> resource) {
        ...
        Object received = resource.get();
        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
            //如果資源為 NULL 則清除掉活動資源,并緩存
            releaseResource(resource);
            ...
            return;
        }

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

        onResourceReady(resource, (R) received);
    }
           

繼續檢視 onResourceReady 方法

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

        //往上查 requestListener 這個參數,發現是在 GenericRequestBuilder 中的 buildRequestRecursive 方法中,咱們的情景設定是沒有設定縮放,是以 requestListener 是 null
 的
        //requestListener 可以在外面設定Glide 加載失敗或成功的監聽
        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);
       
}
           

如果資源加載失敗則回調 GenericRequest 的 onException 方法

status = Status.FAILED; //修改狀态  
//TODO: what if this is a thumbnail request?
//requestListener 可以在外面設定Glide 加載失敗或成功的監聽
if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
      setErrorPlaceholder(e); //給view設定錯誤占位符
}
           

到此資源的擷取加載流程就完了

小結

到此我們已經簡單的分析了一遍圖檔資源在網絡上加載,并且設定到 view 中,歡迎大家品嘗,并提出意見

注:

本篇基于 Glide 3.8.0

繼續閱讀