編輯:關於Android編程
使用文章介紹以及和Picasso的對比分析請參考Introduction to Glide, Image Loader Library for Android, recommended by Google
由於這篇文章使用glide的老版本,因此有些使用方法可能不太一致了。
本文基於github上Glide最新代碼4.0.0版本做解析。
最基本的使用方式如下:
Glide.with(this)
.asDrawable()
.load(http://i6.topit.me/6/5d/45/1131907198420455d6o.jpg)
.apply(fitCenterTransform(this))
.apply(placeholderOf(R.drawable.skyblue_logo_wechatfavorite_checked))
.into(imageView);
Glide使用了現在非常流行的流氏編碼方式,方便了開發者的使用,簡明、扼要。
接下來主要對上面這一段流氏操作做拆分。
這個類有點像門臉模式的統一代理入口,不過實際作用在4.0.0中很多功能都被放到後面的其他類中,此類關注的點就很少了。雖然整個libray的所有需要的組建都在這個類中,但是實際也只是一個統一初始化的地方。
這個類主要用於管理和啟動Glide的所有請求,可以使用activity,fragment或者連接生命周期的事件去智能的停止,啟動,和重啟請求。也可以檢索或者通過實例化一個新的對象,或者使用靜態的Glide去利用構建在Activity和Fragment生命周期處理中。它的方法跟你的Fragment和Activity的是同步的。
通用類,可以處理設置選項,並啟動負載的通用資源類型。
在這個類中,主要是應用請求的很多選項(如下的選項從字面都能看出具體的用處,在ImageView控件中經常也能看到,另外之前版本可不是這麼使用的):
public final class RequestOptions extends BaseRequestOptions {
private static RequestOptions skipMemoryCacheTrueOptions;
private static RequestOptions skipMemoryCacheFalseOptions;
private static RequestOptions fitCenterOptions;
private static RequestOptions centerCropOptions;
private static RequestOptions circleCropOptions;
private static RequestOptions noTransformOptions;
private static RequestOptions noAnimationOptions;
// ...省略...
}
RequestBuilder transition(TransitionOptions transitionOptions){} 這個方法主要是用於加載對象從占位符(placeholder)或者縮略圖(thumbnail)到真正對象加載完成的轉場動畫。
RequestBuilder load(…){}多太方法中,這裡可以加載很多類型的數據對象,可以是String,Uri,File,resourceId,byte[]這些。當然這些後面對應的編碼方式也是不一樣的。
Target into(…){}這個方法是觸發Request真正啟動的地方,在上邊的示例中最後就是調用這個方法發起請求。
不得不說的registry域,這個域掛載了很多元件,該注冊器中囊括了模塊加載器(ModelLoader)、編碼器(Encoder)、資源解碼器(ResourceDecoder)、資源編碼器(ResourceEncoder)、數據回轉器(DataRewinder)、轉碼器(Transcoder)。這些都是Glide在對資源編解碼中既是基礎又是核心功能。
這裡主要列舉一下一些重要的組件以及他們的結構關系:
ModelLoader
DataFetcher
Target<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjxiciAvPg0KPGltZyBhbHQ9"Target" src="/uploadfile/Collfiles/20151201/201512010838508.png" title="\" />
Resource
ResourceTransformation
Pool
Cache
Decoder
Encoder
把這些組件代碼結構列舉出來主要是為了讓讀者和使用者一目了然的看到自己需要的一些功能。
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
// RequestManagerRetriever.get(...)
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager =
new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(
final android.app.FragmentManager fm, android.app.Fragment parentHint) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
public RequestBuilder asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}
public RequestBuilder load(@Nullable Object model) {
return loadGeneric(model);
}
private RequestBuilder loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
public > Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException(You must call #load() before calling #into());
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
一般而言,大部分使用者都是用來裝載圖片的,因此都會調用如下這個方法:
public Target into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
if (requestOptions.isLocked()) {
requestOptions = requestOptions.clone();
}
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions.optionalCenterCrop(context);
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions.optionalFitCenter(context);
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(context.buildImageViewTarget(view, transcodeClass));
}
這裡針對ImageView的填充方式做了篩選並對應設置到requestOptions上。最終的是通過ImageView和轉碼類型(transcodeClass)創建不通過的Target(例如Bitmap對應的BitmapImageViewTarget和Drawable對應的DrawableImageViewTarget)
4.1 Request的創建buildRequest(target)。
在Request的創建中會針對是否有縮略圖來創建不同尺寸的請求,縮略圖方法可以使用RequestBuilder.thumbnail(…)方法來添加上。
Glide中的Request都是使用了SingleRequest類,當然縮略圖采用的是ThumbnailRequestCoordinator類:
private Request obtainRequest(Target target,
BaseRequestOptions requestOptions, RequestCoordinator requestCoordinator,
TransitionOptions transitionOptions, Priority priority,
int overrideWidth, int overrideHeight) {
requestOptions.lock();
return SingleRequest.obtain(
context,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
requestListener,
requestCoordinator,
context.getEngine(),
transitionOptions.getTransitionFactory());
}
比較值得推崇的是SingleRequest.obtain寫法,個人認為比new關鍵字更簡潔明了吧。
target.setRequest(request)也是一個比較值得注意的地方,如果target是ViewTarget,那麼request會被設置到View的tag上。這樣其實是有一個好處,每一個View有一個自己的Request,如果有重復請求,那麼都會先去拿到上一個已經綁定的Request,並且從RequestManager中清理回收掉。這應該是去重的功能。
4.2 requestManager.track(target, request)
這個方法非常的復雜,主要用於觸發請求、編解碼、裝載和緩存這些功能。下面就一步一步來看吧:
4.2.1 緩存target,並啟動Request
void track(Target target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
/**
* Starts tracking the given request.
*/
public void runRequest(Request request) {
requests.add(request); //添加內存緩存
if (!isPaused) {
request.begin(); // 開始
} else {
pendingRequests.add(request); // 掛起請求
}
}
繼續看一下SingleRequest中的begin方法:
@Override
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// 如果model空的,那麼是不能執行的。 這裡的model就是前面提到的RequestBuilder中的model
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException(Received null model), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
// 如果當前的View尺寸已經加載獲取到了,那麼就會進入真正的加載流程。
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
// 反之,當前View還沒有畫出來,那麼是沒有尺寸的。
// 這裡會調用到ViewTreeObserver.addOnPreDrawListener。
// 等待View的尺寸都ok,才會繼續
target.getSize(this);
}
// 如果等待和正在執行狀態,那麼當前會加載占位符Drawable
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV(finished run method in + LogTime.getElapsedMillis(startTime));
}
}
接下來是target.getSize(this)方法。這裡主要說一下尺寸未加載出來的情況(ViewTarget.java):
void getSize(SizeReadyCallback cb) {
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
} else {
// 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);
// 繪畫之前加入尺寸的監聽。這一點我想大部分Android開發同學應該都知道。
// 接下來在看看系統觸發該Listener時target又干了些什麼。
observer.addOnPreDrawListener(layoutListener);
}
}
}
private static class SizeDeterminerLayoutListener implements ViewTreeObserver
.OnPreDrawListener {
// 注意這裡是弱引用
private final WeakReference sizeDeterminerRef;
public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {
sizeDeterminerRef = new WeakReference<>(sizeDeterminer);
}
@Override
public boolean onPreDraw() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, OnGlobalLayoutListener called listener= + this);
}
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
// 通知SizeDeterminer去重新檢查尺寸,並觸發後續操作。
// SizeDeterminer有點像工具類,又作為尺寸回調的檢測接口
sizeDeterminer.checkCurrentDimens();
}
return true;
}
}
ok,繼續回到SingleRequest.onSizeReady方法,主要就是Engine發起load操作
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV(Got onSizeReady in + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = Math.round(sizeMultiplier * width);
this.height = Math.round(sizeMultiplier * height);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV(finished setup for calling load in + LogTime.getElapsedMillis(startTime));
}
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
this);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV(finished onSizeReady in + LogTime.getElapsedMillis(startTime));
}
}
特別的,所有的操作都是來之唯一一個Engine,它的創建是來至於Glide的初始化。如果有需要修改緩存配置的同學可以繼續看一下diskCacheFactory的創建:
if (engine == null) {
engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);
}
繼續看一下Engine.load的詳細過程:
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class resourceClass,
Class transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map, Transformation> transformations,
boolean isTransformationRequired,
Options options,
boolean isMemoryCacheable,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
// 創建key,這是給每次加載資源的唯一標示。
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 通過key查找緩存資源 (PS 這裡的緩存主要是內存中的緩存,切記,可以查看MemoryCache)
EngineResource cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
// 如果有,那麼直接利用當前緩存的資源。
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(Loaded resource from cache, startTime, key);
}
return null;
}
// 這是一個二級內存的緩存引用,很簡單用了一個Map>>裝載起來的。
// 這個緩存主要是誰來放進去呢? 可以參考上面一級內存緩存loadFromCache方法。
EngineResource active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(Loaded resource from active resources, startTime, key);
}
return null;
}
// 根據key獲取緩存的job。
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb); // 給當前job添加上回調Callback
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(Added to existing load, startTime, key);
}
return new LoadStatus(cb, current);
}
// 創建job
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 放入線程池,執行
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(Started new load, startTime, key);
}
return new LoadStatus(cb, engineJob);
}
上面有一些值得注意的地方:
內存緩存:在Glide中默認是LruResourceCache。當然你也可以自定義; 為何要兩級內存緩存(loadFromActiveResources)。個人理解是一級緩存采用LRU算法進行緩存,並不能保證全部能命中,添加二級緩存提高命中率之用; EngineJob和DecodeJob各自職責:EngineJob充當了管理和調度者,主要負責加載和各類回調通知;DecodeJob是真正干活的勞動者,這個類實現了Runnable接口。
下面來看看DecodeJob是如何執行的:
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
// 初始化 獲取下一個階段狀態
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
// 運行
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException(Unrecognized run reason: + runReason);
}
}
// 這裡的階段策略首先是從resource中尋找,然後再是data,,再是source
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 根據定義的緩存策略來回去下一個狀態
// 緩存策略來之於RequestBuilder的requestOptions域
// 如果你有自定義的策略,可以調用RequestBuilder.apply方法即可
// 詳細的可用緩存策略請參看DiskCacheStrategy.java
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException(Unrecognized stage: + current);
}
// 根據Stage找到數據抓取生成器。
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
// 產生含有降低采樣/轉換資源數據緩存文件的DataFetcher。
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
// 產生包含原始未修改的源數據緩存文件的DataFetcher。
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// 生成使用注冊的ModelLoader和加載時提供的Model獲取源數據規定的DataFetcher。
// 根據不同的磁盤緩存策略,源數據可首先被寫入到磁盤,然後從緩存文件中加載,而不是直接返回。
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException(Unrecognized stage: + stage);
}
}
經過很多流程,最後來到了發起實際請求的地方SourceGenerator.startNext()方法:
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
// 查找ModelLoader
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
根據model的fetcher加載數據
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
這裡的Model必須是實現了GlideModule接口的,fetcher是實現了DataFetcher接口。有興趣同學可以繼續看一下integration中的okhttp和volley工程。Glide主要采用了這兩種網絡libray來下載圖片。
4.2.2 數據下載完成後的緩存處理SourceGenerator.onDataReady
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
有些小伙伴可能看不太明白為什麼就一個dataToCache = data就完了…其實cb.reschedule()很重要,這裡的cb就是DecodeJob.reschedule():
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
這裡又有一個Callback,繼續追蹤,這裡的Callback接口是定義在DecodeJob內的,而實現是在外部的Engine中(這裡會用線程池重新啟動當前job,那為什麼要這樣做呢?源碼中的解釋是為了不同線程的切換,因為下載都是借用第三方網絡庫,而實際的編解碼是在Glide自定義的線程池中進行的):
public void reschedule(DecodeJob job) {
if (isCancelled) {
MAIN_THREAD_HANDLER.obtainMessage(MSG_CANCELLED, this).sendToTarget();
} else {
sourceExecutor.execute(job);
}
}
接下來繼續DecodeJob.runWrapped()方法。這個時候的runReason是SWITCH_TO_SOURCE_SERVICE,因此直接執行runGenerators(),這裡繼續執行SourceGenerator.startNext()方法,值得注意的dataToCache域,因為上一次執行的時候是下載,因此再次執行的時候內存緩存已經存在,因此直接緩存數據cacheData(data):
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
// 根據不同的數據獲取注冊的不同Encoder
Encoder
繼續回到SourceGenerator.startNext()方法,這個時候已經有了sourceCacheGenerator,那麼直接執行DataCacheGenerator.startNext()方法:
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
// 這裡會通過model尋找注冊過的ModelLoader
while (!started && hasNextModelLoader()) {
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData =
modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
helper.getOptions());
// 通過FileLoader繼續加載數據
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
這裡的ModelLoader跟之前提到過的Register的模塊加載器(ModelLoader)對應是modelLoaderRegistry域,具體執行的操作是Registry.getModelLoaders(…)方法如下:
public List> getModelLoaders(Model model) {
List> result = modelLoaderRegistry.getModelLoaders(model);
if (result.isEmpty()) {
throw new NoModelLoaderAvailableException(model);
}
return result;
}
繼續回到DataCacheGenerator.startNext()方法,找到了ModelLoader,這裡筆者跟蹤到的是FileLoader類(FileFetcher.loadData(…)方法):
public void loadData(Priority priority, DataCallback callback) {
// 讀取文件數據
try {
data = opener.open(file);
} catch (FileNotFoundException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, Failed to open file, e);
}
//失敗
callback.onLoadFailed(e);
return;
}
// 成功
callback.onDataReady(data);
}
4.2.3 裝載流程
回調通知這裡就不打算多講了,主要線路如下:
-->DataCacheGenerator.onDataReady
-->SourceGenerator.onDataFetcherReady
-->DecodeJob.onDataFetcherReady
-->DecodeJob.decodeFromRetrievedData
-->DecodeJob.notifyEncodeAndRelease
-->DecodeJob.notifyComplete
-->EngineJob.onResourceReady
Debug流程圖:
需要說明的就是在EngineJob中有一個Handler叫MAIN_THREAD_HANDLER。為了實現在主UI中裝載資源的作用,ok繼續上邊的流程:
-->EngineJob.handleResultOnMainThread
-->SingleRequest.onResourceReady
-->ImageViewTarget.onResourceReady
-->ImageViewTarget.setResource
-->ImageView.setImageDrawable/ImageView.setImageBitmap
Debug流程圖2:
數據的裝載過程中有一個很重要的步驟就是decode,這個操作發生在DecodeJob.decodeFromRetrievedData的時候,繼續看代碼:
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(Retrieved data, startFetchTime,
data: + currentData
+ , cache key: + currentSourceKey
+ , fetcher: + currentFetcher);
}
Resource resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
exceptions.add(e);
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
這中間發生了很多轉換主要流程:
-->DecodeJob.decodeFromData
-->DecodeJob.decodeFromFetcher
-->DecodeJob.runLoadPath
-->LoadPath.load
-->LoadPath.loadWithExceptionList
-->LoadPath.decode
-->LoadPath.decodeResource
-->LoadPath.decodeResourceWithList
-->ResourceDecoder.handles
-->ResourceDecoder.decode
這裡講到了decode,那麼encode發生在什麼時候呢?直接通過Encoder接口調用發現,在數據緩存的時候才會觸發編碼。具體調用在DiskLruCacheWrapper和DataCacheWriter中。一些值得參考的寫法例如BitmapEncoder對Bitmap的壓縮處理。
最近看開源庫Glide關注度一直比較高,因此打算一探究竟。 由於時間比較緊,因此一些應該有的時序圖沒有畫,這裡也只能簡單用箭頭代替。不過個人認為整體執行流程已經表達清楚了。
總體來說代碼寫的挺漂亮的,單從使用者角度來說入手是比較容易的。 源碼使用了大量的工廠方法來創建對象,就像String.valueof(…)方法一樣,這也體現編碼的優雅。 不過想要對這個庫進行改造,可能並非易事,筆者在跟蹤代碼的過程中發現很多地方有Callback這樣的接口,來來回回查找幾次很容易就暈頭轉向了。。。 另外一個感覺難受的地方就是構造方法帶入參數太多,就拿SingleRequest來說就是12個構造參數。 單例的使用感覺還是有些模糊,就比如GlideContext,有些時候通過Glide.get(context).getGlideContext()獲取,而有些類中采用構造傳入。個人覺得既然讓Glide作為單例,那麼還這樣傳入參數是不是有點多余?代碼的編寫都是可以折中考慮,不過如果整個項目擬定好了一個規則的話,我想最好還是遵循它。另外再吐槽一下單例,很多開發人員喜歡用單例,如果你是有代碼潔癖的開發者,那麼你肯定很討厭這樣,單例很容易造成代碼的散落和結構不清晰。
源碼的解析只是把最重要的加載流程走了一遍,有一些比較細節的地方沒有關注,如果你有需要,可以自己跟著這個主線debug一下就能查找到。
手機購買的時候自身帶了很多軟件,能刪除嗎?下面安下網小編教大家幾招用來刪除手機預裝軟件。 1、首先百度搜索360手機助手,然後下載安裝
第三方支付第三方支付指的是第三方平台與各銀行簽約,在買方與賣方之間實現中介擔保,從而增強了支付交易的安全性。國內常用的支付平台主要是支付寶和微信支付,其中支付寶的市場份額
這一篇我們來學點新的東西。做項目的時候應該碰到這種問題:根據不同條件顯示或者隱藏一個控件或者布局,我們能想到的第一個方法就是 調用View.setVisibility()
最近做一個頁面,反饋問題頁面,有個用戶上傳問題圖片的功能。本來很笨的想把系統的所有圖片列出來,然後讓用戶選擇,後來發現原來可以直接打開手機所有圖片的api的。效果如圖:給