源碼分析Android的圖片加載庫 Glide的一次加載過程


基于com.github.bumptech.glide:glide:3.7.0
這是一篇快速過源碼,而非品味細枝末節的分析,否則簡書的2W byte的限制,可能要分好幾期才能徹徹底底的講完。

 Glide
    .with(myFragment)
    .load(url)
    .centerCrop()
    .placeholder(R.drawable.loading_spinner)
    .crossFade()
    .into(myImageView);

我們就以此來作為出發點。
可以看到整個請求使用的當前流行的流式代碼,我們來逐個擊破。

  • 初步調查

    • Glide
      注釋寫的很明白,是一個提供請求接口的單例,和我們熟悉的門面模式很相似,它是一個請求的入口,你可以看到,類中的很多方法都是靜態的,直接通過Glide來調起的。
      那么我們直奔主題,看一看with方法,你會發現有很多重載,但是最后都是統一進入了fragmentGet或者是supportFragmentGet來獲得一個RequestManager對象
     RequestManager supportFragmentGet(Context context, FragmentManager fm) {
        //根據傳入的Fragment來獲取RequestManager
        SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }
    
  會根據你具體傳入的類型的不同,最終選擇3.0+的Fragment還是AppCompat的Fragment。由于兩種不同的Fragment的FragmentManager是不同的,此處有兩種方法`getSupportRequestManagerFragment` 和`getRequestManagerFragment`,實際上原理是一樣的。

SupportRequestManagerFragment getSupportRequestManagerFragment(final FragmentManager fm) {
//當前fragment棧中是否有我們需要的fragment在
SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(
FRAGMENT_TAG);
if (current == null) {
//如果不存在,去我們的緩存Map中取
current = pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
//如果依然沒有去生成這個Fragment
current = new SupportRequestManagerFragment();
pendingSupportRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
//抹去緩存map中該key值
handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

     Glide利用生成額外的無界面Fragment到Framgent棧中,用來同步context的生命周期。

    * RequestManager
            A class for managing and starting requests for Glide
    用來管理和發起請求的類。
    我們順著load方法去看,又是個重載方法,根據傳入的type的類型不同,返回不同類型的`GenericRequestBuilder`

public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
...
}

DrawableRequestBuilder.java

@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}

GenericRequestBuilder.java

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}

     最后得到的是一個`DrawableRequestBuilder<ModelType>`對象,強轉成`DrawableTypeRequest<ModelType>`。

    * GenericRequestBuilder的不斷構造
     我們通過centerCrop方法去進中央裁剪,DrawableTypeRequest的centerCrop方法在父類DrawableRequestBuilder中

@Override
public DrawableRequestBuilder<ModelType> transform(Transformation<GifBitmapWrapper>... transformation) {
super.transform(transformation);
return this;
}

GenericRequestBuilder.java

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> transform(
Transformation<ResourceType>... transformations) {
isTransformationSet = true;
if (transformations.length == 1) {
transformation = transformations[0];
} else {
transformation = new MultiTransformation<ResourceType>(transformations);
}

    return this;
}
    修改內部參數,并且以構造者模式返回自己本身以供繼續修改使用。
   之后的`placeholder`方法也是繼續修改GenericRequestBuilder的參數,為請求增加占屏圖片。
   `crossFade`方法則是增加了animationFactory參數

GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(
GlideAnimationFactory<TranscodeType> animationFactory) {
if (animationFactory == null) {
throw new NullPointerException("Animation factory must not be null!");
}
this.animationFactory = animationFactory;

    return this;
}
    期間方法不單單有transform,包括priority修改優先級,encoder修改編碼方式,diskCacheStrategy修改硬盤緩存策略等等。
    
  * ###最后的into方法
      之前都是在買材料囤貨,就是這個方法開始做請求。
     DrawableRequestBuilder.java

@Override
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}

GenericRequestBuilder.java

public Target<TranscodeType> into(ImageView view) {
//判斷是否在主線程
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
//如果imageView本身有填充方式,請求那么做相應的處理
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.
}
}

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

}

   然后通過傳入view和轉碼類型(transcodeClass)來創建相應的Target。
ImageViewTargetFactory.java
    ```
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)");
        }
    }
 OK,我們繼續往下看,看下重載的方法into(Target)
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())");
        }

        Request previous = target.getRequest();

        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }

        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
        requestTracker.runRequest(request);

        return target;
    }

Glide會先去看這個target之前有沒有過請求,如果這個target之前有過請求要把這個請求clear掉,并且recycle。也就是說,如果一個imageView上我們要多次做加載請求,那么最后以最后一次請求為準。這個在listView或者是RecyclerView中使用就相當頻繁了。
看到target.getRequest()方法,根據之前我們說的可能會生成的三種不同的target,我們這里去看BitmapImageViewTarget,最終的getRequest方法是在父類ViewTarget

public Request getRequest() {
        Object tag = getTag();
        Request request = null;
        if (tag != null) {
            if (tag instanceof Request) {
                request = (Request) tag;
            } else {
                throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
            }
        }
        return request;
    }
private Object getTag() {
        if (tagId == null) {
            return view.getTag();
        } else {
            return view.getTag(tagId);
        }
    }

所以很明顯了,Glide玩的套路是把request對象通過setTag的方式和View綁定的

 繼續看request是如何build出來的

  ```

private Request buildRequest(Target<TranscodeType> target) {
//如果實現沒有優先級的規定,設置為優先級普通
if (priority == null) {
priority = Priority.NORMAL;
}
return buildRequestRecursive(target, null);
}
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
if (thumbnailRequestBuilder != null) {
//縮略圖的相應的request創建
...
} else if (thumbSizeMultiplier != null) {
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
...
} else {
// Base case: no thumbnail.
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);
}

   我們看到真正的生成請求方法`obtainRequest`,傳入了大量的參數,我們不一一深究是什么東西,我們先接著看生成了請求之后的下一個方法,`  requestTracker.runRequest(request);`

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

requestTracker內部維護了一個請求列表,那我們直接進入到request實現類,去看看begin方法到底做了什么。

@Override
public void begin() {
startTime = LogTime.getLogTime();
//就是之前提到的load傳入類型,如果都沒有加載類型就拋異常
if (model == null) {
onException(null);
return;
}

    status = Status.WAITING_FOR_SIZE;
    //如果已經拿到了尺寸就進入加載流程,否則繼續View的尺寸
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }
    //如果正在請求,那么就為view填充占位圖
    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }

}

    終于我們找到最終的加載方法在這個onSizeReady回調中
@Override
public void onSizeReady(int width, int height) {
    //參數準備
    ...
    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));
    }

}

     最終GenericRequest把加載任務都扔到了engin中去了

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查找內存緩存中是否存在引擎資源,如果有就直接可以拿來用,觸發onResourceReady回調(一級緩存)
    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;
    }
    // 通過key查找是否存在弱引用可以利用(二級緩存)
    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;
    }
    //再沒有就去本地的map列表中通過key查找是否存在EngineJob,這與上面的EngineResource不同(三級緩存)
    EngineJob current = jobs.get(key);
    if (current != null) {
        //加入內部的callback隊列,最終也會執行如上的onResourceReady回調
        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<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);
    //執行job
    engineJob.start(runnable);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}
    這里涉及到多級緩存,以及最終的請求任務加載,我們打開`EngineRunnable`,來看下最終是怎么請求的。

  * ###藏得最深的DecodeJob
     EngineRunnable.java
```@Override
    public void run() {
        if (isCancelled) {
            return;
        }

        Exception exception = null;
        Resource<?> resource = null;
        try {
            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;
        }

        if (resource == null) {
            onLoadFailed(exception);
        } else {
            onLoadComplete(resource);
        }
    }
private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            //緩存中已經有了我們需要的data
            return decodeFromCache();
        } else {
           //緩存中還沒有我們需要的data,我們需要先去獲得data,再將data轉為我們需要的model類型
            return decodeFromSource();
        }
    }

兩個方法,一個是緩存中處理過此類請求,直接從緩存中請求,我們直接看第二個decodeFromSource
decodeFromSource()->DecodeJob.decodeFromSource()->DecodeJob.decodeSource()->DecodeJob.decodeFromSourceData(),
最終,我們找到這一句

   //將data進行decode,變成我們需要的decoded類型
   decoded = loadProvider.getSourceDecoder().decode(data, width, height);

包括,走另一條decodeFromCache,也會走到這一句。經過多次的倒推與查找,我們發現在Glide的構造函數中,有著這么一坨代碼

dataLoadProviderRegistry = new DataLoadProviderRegistry();

        StreamBitmapDataLoadProvider streamBitmapLoadProvider =
                new StreamBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(InputStream.class, Bitmap.class, streamBitmapLoadProvider);

        FileDescriptorBitmapDataLoadProvider fileDescriptorLoadProvider =
                new FileDescriptorBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(ParcelFileDescriptor.class, Bitmap.class, fileDescriptorLoadProvider);

        ImageVideoDataLoadProvider imageVideoDataLoadProvider =
                new ImageVideoDataLoadProvider(streamBitmapLoadProvider, fileDescriptorLoadProvider);
        dataLoadProviderRegistry.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);

        GifDrawableLoadProvider gifDrawableLoadProvider =
                new GifDrawableLoadProvider(context, bitmapPool);
        dataLoadProviderRegistry.register(InputStream.class, GifDrawable.class, gifDrawableLoadProvider);

        dataLoadProviderRegistry.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
                new ImageVideoGifDrawableLoadProvider(imageVideoDataLoadProvider, gifDrawableLoadProvider, bitmapPool));

        dataLoadProviderRegistry.register(InputStream.class, File.class, new StreamFileDataLoadProvider());

        register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
        register(File.class, InputStream.class, new StreamFileLoader.Factory());
        register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        register(int.class, InputStream.class, new StreamResourceLoader.Factory());
        register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
        register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
        register(String.class, InputStream.class, new StreamStringLoader.Factory());
        register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
        register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
        register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
        register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());

其實Glide早就已經把基本所有的加載請求情況都已經考慮在內了。
dataLoadProviderRegistry注冊的是將data轉換成resource的情況
register方法注冊是將model轉換成data。

DecodeJob中的兩個重要的類就是和上面的東西相關的

private final DataFetcher<A> fetcher;
    private final DataLoadProvider<A, T> loadProvider;

fetcher負責把model轉換成data
loadProvider再負責把data轉換成我們需要的資源類型resource。
在上面的decode流程中的decodeSource()方法我們能看到fetcher的調用,也只有在這個方法中我們可以看到fetcher的調用,因為只有緩存中沒有現存的data,我們才會去做一次model轉換成data。
追根溯源,你會發現,最終這個fetcher就是上面register方法中的XXXXXLoader.getResourceFetcher返回的DataFetcher對象,我們就取一個Http的請求看一下:

 register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());]

此處說明一下,之所以選擇GlideUrl轉換成輸入流是因為,所有的http和https的網絡url最后都會被轉換成GlideUrl,具體原因見UriLoader.java

@Override
    public final DataFetcher<T> getResourceFetcher(Uri model, int width, int height) {
        ...
        if (isLocalUri(scheme)) {
            ...
        } else if (urlLoader != null && ("http".equals(scheme) || "https".equals(scheme))) {
            result = urlLoader.getResourceFetcher(new GlideUrl(model.toString()), width, height);
        }

        return result;
}
HttpUrlFetcher.java
@Override
    public InputStream loadData(Priority priority) throws Exception {
        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
    }

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

網絡請求就是在這里去執行的,使用的HttpUrlConnection。

  • 總結

    一次請求流程大致如下
    • 首先通過Glide.with方法生成RequestManager對象來管理請求
    • 調用RequestManager.load方法來講我們的modelType傳入,并得到相應的RequestBuilder對象。
    • 通過構造者模式不斷去給RequestBuilder增加條件,比如裁剪,優先級,占位圖等等
    • 通過into方法傳入目的target,并開啟請求
    • 查看目標View的tag中獲取看看是否有request,如果有則清除。然后用新建的request來覆蓋。
    • 執行request,三級緩存策略,先看緩存中是否存在EngineResource,再看是否有EngineResource的若引用,最后看Map中是否存在EngineJob。如果有則直接返回結果并進行相應加載
    • new并執行EngineRunnable這個DecoderJob的封裝
    • 在DecoderJob內部查看是否存在相應的InputStream或者是ParcelFileDescriptor,如果已經存在,則直接將其通過loadProviderdecode成相應的Bitmap,gif等。否則就通過fetcher先將我們通過load傳入的路徑進行解析成InputStream、ParcelFileDescriptor,再decode。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容