基于com.github.bumptech.glide:glide:3.7.0
這是一篇快速過源碼,而非品味細枝末節的分析,否則簡書的2W byte的限制,可能要分好幾期才能徹徹底底的講完。
-
引入
在github上(https://github.com/bumptech/glide) ,Glide的官方Sample使用的是如下的一段的代碼:
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; }
- Glide
會根據你具體傳入的類型的不同,最終選擇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,如果已經存在,則直接將其通過
loadProvider
decode成相應的Bitmap,gif等。否則就通過fetcher
先將我們通過load傳入的路徑進行解析成InputStream、ParcelFileDescriptor,再decode。