Glide範例原始碼分析

wyman_1007發表於2017-12-26

Glide最簡單的使用應該就是下邊的程式碼:

Glide.with(Context).load("url").into(ImageView);
複製程式碼

開發者使用起來非常簡單,但裡面大有文章。 之前看了Retrofit、Volley、OkHttp庫原始碼都感覺自己看下來沒有太大問題。難度不算大。但Glide的原始碼如果你有能力不需要其他資源能看下來的確能力很了得。因為Glide的原始碼複雜程度絕對在上面說的幾個框架之上。 在根據上邊範例程式碼看原始碼過程中我畫了個UML圖。基本上都不夠畫,只能在into部分以最簡單的方式呈現出來。上圖:

GlideUML.png

大致說一下這個圖的三大部分就是:

  • .with(Context)
  • .load("url")
  • .into(ImageView)

.with(Context)

Paste_Image.png
我們開始講解with部分:

Paste_Image.png
Glide的with有5個過載方法。其實都可以歸類為當在非主執行緒呼叫with(),通通走with(Context);而在主執行緒呼叫就看呼叫哪個一般兩步走:

Paste_Image.png

哪兩步:就是紅色長方形框部分get(FragmentActivity)或get(Activity); 而紅色圈圈其實最終走getApplicationManager(context)

####getApplicationManager(context) 該方法屬於工作執行緒呼叫with或with裡面的引數是Application的Context

Paste_Image.png
先講這個方法是因為它比較簡單就是建立一個ResquestManager,傳進兩個引數:ApplicationLifecycle和EmptyRequestManagerTreeNode

  • ApplicationLifecycle 其實看名字大概就知道它是監聽Application的
  • EmptyRequestManagerTreeNode這個因為是Application所以其實它是什麼也沒有做。

Paste_Image.png

####get((Activity) context);

Paste_Image.png

Paste_Image.png

只看截圖應該知道大概幹了什麼事情那吧:

  • 建立RequestManagerFragment
  • 在該fragment獲取lifecycle,和RequestManagerTreeNode

其實get(FragmentActivity activity)也是大致做這樣的事情:

Paste_Image.png

為什麼要分開做主要是因為在android中有兩種fragment,一種是v4和另外一種是非v4包的。

####Lifecycle和RequestManagerTreeNode分別幹什麼

  • ActivityFragmentLifecycle 實現 Lifecycle介面 主要就是管理所有實現Lifecycle的介面。

Paste_Image.png
將所有Lifecycle的介面放進WeakHashMap上,然後分不同情況呼叫介面的onStart、onStop、onDestroy

  • RequestManagerTreeNode RequestManagerTreeNode有兩個實現它介面的類:SupportFragmentRequestManagerTreeNode和FragmentRequestManagerTreeNode 其實作用都是一樣就是收集它們層級的RequestManager。因為我們在每個Activity或Fragment呼叫Glide.with(this);就會建立一個RequestManager。而RequestManagerTreeNode的作用就是獲取RequestManager的集合Set。

Paste_Image.png

####RequestManager的建立過程:

Paste_Image.png

Paste_Image.png
新增ActivityFragmentLifecycle、RequestManagerTreeNode、RequestTracker、ConnectivityMonitorFactory; 如果在主執行緒直接把RequestManager放進ActivityFragmentLifecycle,非主執行緒通過Handler post進去。為了就是和生命週期同步。 這裡有兩個類:RequestTracker和ConnectivityMonitorFactory

  • RequestTracker其實就是有點和ActivityFragmentLifecycle的功能類似就是將Request收集起來,進行統一管理。

Paste_Image.png

Paste_Image.png

Paste_Image.png

  • ConnectivityMonitorFactory 的主要作用就是監聽網路情況。而監聽網路主要通過廣播。 ConnectivityMonitorFactory會根據Manifest檔案中是否有新增userPermission對網路進行監聽;

Paste_Image.png

Paste_Image.png

Paste_Image.png

整個with階段Glide主要就是做了這些東西:

  • 建立一個空的Fragment對生命週期進行監聽,在onStart執行request.onStart();
  • 通過TreeNode對每個Fragment都進行管理;
  • 通過RequestTracker對每個Request進行管理;
  • 網路監聽,通過廣播對網路進行監聽;
  • Glide物件的建立,會在後面交待清楚; 快取方面的操作在本文中不打算分析,以後有機會在學習。 下面看看load(url)部分

###.load(url)

Paste_Image.png

RequestManager的load()方法:

Paste_Image.png

我們就看load(string)

Paste_Image.png
返回一個DrawableTypeRequest

  • fromString()

Paste_Image.png

Paste_Image.png
可以看到最終是new一個DrawableTypeRequest 這裡開始有點複雜了,開始分析原始碼的時候,我就是卡在這裡。後面大概看了兩篇博文,解決了我一點疑惑。最後我會貼上兩篇博文的連結出來。

####這裡留意一下泛型,後面會出現四個泛型的物件,到時更暈

Paste_Image.png
這裡的泛型T為String型別。當然主要看它傳入什麼型別。現在本博文以String為例; 這裡有個ModelLoader物件,後面程式碼會經常出現。

####現在返回來看看Glide物件的構建:

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png
程式碼有點多,前面截圖的第一張就不說了,就是準備一些快取的和BitmapPool,這裡Glide建立了一堆loader、transcoder等東西,其實就是為load()方法做鋪墊的。Glide框架這麼強大,就源於這堆loader、transcoder;當然還有其他的功能; 註冊了這麼多loader或transcoder怎麼管理呢? 主要通過GenericLoaderFactory和ModelLoader

Paste_Image.png

Paste_Image.png
這裡截了一小部分,有興趣的研究可以看看原始碼。其實主要就是通過 ModelLoader裡面的兩個泛型做key值獲取到對應的ModelLoader具體物件。

  • 現在看回去剛才的loadGeneric方法:

ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);

Paste_Image.png

一路跟下去最終就是呼叫剛才說的GenericLoaderFactory的buildModelLoader。這裡我們可以根據泛型知道對應的ModelLoader:String InputStream

Paste_Image.png
這裡需要一直跟下去,其實你就會發現最終是 HttpUrlGlideUrlLoader 非截圖的StreamStringLoader;

相同的方法就會發現

ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(modelClass, context);

fileDescriptorModelLoader 是 FileDescriptorUriLoader

最後是new DrawableTypeRequest物件 optionsApplier.apply其實也是返回DrawableTypeRequest,所以看DrawableTypeRequest物件構建:

Paste_Image.png

這裡傳了一大堆引數其實,很多引數是我們之前講過的,這裡就不介紹了。這裡主要看buildProvider方法: 它是返回一個FixedLoadProvider, 這裡buildProvider主要看:下面部分

Paste_Image.png
transcoder為 GlideBitmapDrawableTranscoder modelLoader為 ImageVideoModelLoader 通過 HttpUrlGlideUrlLoader和 FileDescriptorUriLoader轉換出來的。 dataLoadProvider為ImageVideoGifDrawableLoadProvider

這裡記錄一下DrawableTypeRequest各個Loader、Provider待會會用得到:

  • LoadProvider:FixedLoadProvider
  • streamModelLoader :HttpUrlGlideUrlLoader
  • fileDescriptorModelLoader:FileDescriptorUriLoader
  • ResourceTranscoder : GlideBitmapDrawableTranscoder
  • DataLoadProvider : ImageVideoGifDrawableLoadProvider
  • modelLoader : ImageVideoModelLoader

基本上load()方法就是根據ModelLoader和傳入在Glide註冊的Loader,Provider選取各種需要的Loader和Provider。

###into(ImageView)

Paste_Image.png

  • DrawableTypeRequest.into(ImageView) DrawableTypeRequest並沒有into方法,它的父類DrawableRequestBuilder有:

Paste_Image.png
繼續看父類GenericRequestBuilder的into方法:

Paste_Image.png
switch語句是設定ImageVIew的ScaleType,看最後一句:

into(glide.buildImageViewTarget(view, transcodeClass));

返回一句Target,Target是一個介面; 先看看glide.buildImageViewTarget(view,transcodeClass);這句程式碼; 這裡的transcodeClass是 GlideDrawable.class

Paste_Image.png

glide.buildImageViewTarget(view, transcodeClass)

Paste_Image.png

程式碼一直跟落去會看到上面截圖; 返回Target; 這裡其實你一直跟落去GlideDrawableImageViewTarget會發現是通過view獲取寬高等資訊。關鍵是看泛型是什麼東西: 其實不難發現泛型Z就是:GlideDrawable.class 這裡不多作解釋; 即返回的Target為:GlideDrawableImageViewTarget 那麼into最終其實就是into(GlideDrawableImageViewTarget);

Paste_Image.png

Paste_Image.png
關鍵看紅色框框的呼叫,前面是一些非空判斷和清空資源重用資源;

Request request = buildRequest(target);

Paste_Image.png

Paste_Image.png
第二個截圖是GenericRequestBuilder.buildRequestRecursive()方法呼叫因為前面 程式碼是對縮圖的處理可以忽略; 看最後一句:

return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);

private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
            RequestCoordinator requestCoordinator) {
        return GenericRequest.obtain(
                loadProvider,
                model,
                signature,
                context,
                priority,
                target,
                sizeMultiplier,
                placeholderDrawable,
                placeholderId,
                errorPlaceholder,
                errorId,
                fallbackDrawable,
                fallbackResource,
                requestListener,
                requestCoordinator,
                glide.getEngine(),
                transformation,
                transcodeClass,
                isCacheable,
                animationFactory,
                overrideWidth,
                overrideHeight,
                diskCacheStrategy);
    }
複製程式碼

####其實最終就是構建一個Request

Paste_Image.png

Request是什麼其實就是載入圖片的請求;前面的RequestManager就是負責管理Request的; 太多引數只能截部分圖; 你會看到四個泛型分別是<A,T,Z,R>

Paste_Image.png

從我們的例子裡,

  • A String
  • T ImageVideoWrapper
  • Z GifBitmapWrapper
  • R GlideDrawable

繼續往下看

Paste_Image.png

Request建立完成後就用target set Request; 然後設定監聽器 最後用RequestTracker執行Request;

Paste_Image.png

看Request.begin() 上面已經說了真正實現Request的是GenericRequest 所以看GenericRequest的begin()

Paste_Image.png
主要看

onSizeReady(overrideWidth, overrideHeight);

####所有謎底都在這裡

  • 包括執行緒;
  • 網路執行
  • 回撥

Paste_Image.png

Paste_Image.png

前面的取寬高其實是縮圖的寬高; 主要看下面的程式碼:

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

這裡的modelLoader 即 ImageVideoModelLoader dataFetcher 即 ImageVideoFetcher

Paste_Image.png

這個截圖是上面load(url)階段時候記錄的。現在用得上啦吧。說Glide原始碼複雜就在這裡,太多這些loader,provider的東西了。

繼續....

ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();

transcoder 這裡的是 GlideBitmapDrawableTranscoder;上面也提到過;

####最後階段了

loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, 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();

        //這裡的id其實就是url地址
        final String id = fetcher.getId();
        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 = 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 runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

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

大Boss來了,這裡就是核心部分,什麼網路載入圖片,編碼解碼的工作都在這裡完成。

由於這博文不說快取部分所以很多程式碼可以忽略; 主要看:

Paste_Image.png

其實看名字都大概知道幹什麼的; 建立EnginJob主要處理Runnable和回撥 DecodeJob就是解碼 最終new LoadStatus就是更新Request的狀態

這裡主要看看Runnable的程式碼:

Paste_Image.png
上面截圖是EnginRunnable的run方法

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png

這裡的fetcher即DataFetcher,實現DataFetcher是 ImageVideoModelLoader,而ImageVideoModelLoader的loadData();

Paste_Image.png

Paste_Image.png

這裡的streamFetcher 就是HttpUrlFetcher

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
            throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("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 IOException("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(2500);
        urlConnection.setReadTimeout(2500);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // 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 IOException("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 IOException("Unable to retrieve response code from HttpUrlConnection.");
            }
            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
        }
    }
複製程式碼

載入圖片資源由HttpUrlFetcher的loadData完成。 最終返回 ImageVideoFetcher 往下看:

Paste_Image.png
解碼部分:loadProvider即ImageVideoGifDrawableLoadProvider 不明白怎麼找可以看前面解釋; 最終你可以跟到是GifBitmapWrapperResourceDecoder的decode方法:

Paste_Image.png

Paste_Image.png

Paste_Image.png
GifBitmapWrapperResourceDecoder這個類就是解釋圖片的。

Paste_Image.png
這裡看註解,大概意思是通過輸入流的開始部分的2048大小就可以判斷這張圖是gif還是其他格式的。 如果不是gif就通過ImageVideoDataLoadProvider繼續完成解碼工作;至於為什麼是ImageVideoDataLoadProvider你可以通過之前找provider或dataloader的方法找。

Paste_Image.png

真正解碼是通過該類完成。

Paste_Image.png
這裡getStream就是從網路獲取到的InputSteam。 最終你可以找到StreamBitmapDecoder的decode()

Paste_Image.png
downsampler.decode()程式碼有點多就貼出來了:

 @Override
    public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
        final ByteArrayPool byteArrayPool = ByteArrayPool.get();
        final byte[] bytesForOptions = byteArrayPool.getBytes();
        final byte[] bytesForStream = byteArrayPool.getBytes();
        final BitmapFactory.Options options = getDefaultOptions();

        // Use to fix the mark limit to avoid allocating buffers that fit entire images.
        RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(
                is, bytesForStream);
        // Use to retrieve exceptions thrown while reading.
        // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a way to determine
        // if a Bitmap is partially decoded, consider removing.
        ExceptionCatchingInputStream exceptionStream =
                ExceptionCatchingInputStream.obtain(bufferedStream);
        // Use to read data.
        // Ensures that we can always reset after reading an image header so that we can still attempt to decode the
        // full image even when the header decode fails and/or overflows our read buffer. See #283.
        MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
        try {
            exceptionStream.mark(MARK_POSITION);
            int orientation = 0;
            try {
                orientation = new ImageHeaderParser(exceptionStream).getOrientation();
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Cannot determine the image orientation from header", e);
                }
            } finally {
                try {
                    exceptionStream.reset();
                } catch (IOException e) {
                    if (Log.isLoggable(TAG, Log.WARN)) {
                        Log.w(TAG, "Cannot reset the input stream", e);
                    }
                }
            }

            options.inTempStorage = bytesForOptions;

            final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
            final int inWidth = inDimens[0];
            final int inHeight = inDimens[1];

            final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
            final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);

            final Bitmap downsampled =
                    downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,
                            decodeFormat);

            // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
            // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
            // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
            final Exception streamException = exceptionStream.getException();
            if (streamException != null) {
                throw new RuntimeException(streamException);
            }

            Bitmap rotated = null;
            if (downsampled != null) {
                rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);

                if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
                    downsampled.recycle();
                }
            }

            return rotated;
        } finally {
            byteArrayPool.releaseBytes(bytesForOptions);
            byteArrayPool.releaseBytes(bytesForStream);
            exceptionStream.release();
            releaseOptions(options);
        }
    }
複製程式碼

是Downsampler的decode方法 最終如果是非gif格式的圖片返回:GifBitmapWrapper

###最終獲取到非gif格式的bitmap放入Imageview上。 由於看Glide原始碼之前我是看了Volley、Retrofit、OkHttp這三個的原始碼感覺還好,所以就直接在沒有看其他資源的基礎上看Glide的原始碼。但看到with階段就有困難,卡在Glide是通過什麼監聽Activity的生命週期,然後看了看郭霖的介紹Glide的第二篇開始部分  Android圖片載入框架最全解析(二),從原始碼的角度理解Glide的執行流程知道是通過構建空的fragment實現的。因為是第一次接觸通過這樣的方法實現監聽Activity生命週期,所以我覺得這個是Glide的一大優點。值得我們學習。 看到load(url)階段卡住,因為當時沒有發現Glide物件的建立註冊了很多provider、loader,decoder之類。主要是看原始碼不夠細心。如是看了此篇文章Glide載入圖片流程(Part One),這裡其實主要看錶格部分,它給了我啟發。之後,我細緻地看Glide物件構建就知道是通過一個介面加物件管理一大堆provider、loader,decoder。

最後想說看原始碼一定要:

  • 細心
  • 像郭霖大神所說要有目的地看原始碼,不然會走進了森林,走不出來。
  • 耐心: 人家郭大神看了一個月Glide的原始碼,所以你懂得~~~哈哈

題外話一下AOSP帶給我們很多值得學習的地方如:

  • Volley將請求分為快取佇列和網路佇列管理;
  • Retrofit通過註解、集合對Factory的整理、建造者模式、動態代理實現高度解耦
  • OkHttp庫的遞迴迭代實現各個攔截的功能獨立,連續呼叫;靜態程式碼塊實現對舊版本程式碼的呼叫

相關文章