Picasso(四into設定圖檔方法解析)
這就是具體的設定圖檔的操作
public void into (ImageView target, Callback callback){
long started = System.nanoTime();
// 通過目前的Looper來判斷是否在主線程
checkMain();
// 目前的ImageView不能為空
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
// 通過uri或者資源id判斷,如果沒有圖檔資訊就取消目前的請求,并設定占位圖檔
if (!data.hasImage()) {
// 取消請求
picasso.cancelRequest(target);
if (setPlaceholder) {
// 設定占位圖
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
// 是否延遲加載
if (deferred) {
// 這裡的邏輯就是尺寸是否合适,以及重新測量的流程
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
// 這裡主要是如果之前自定義了transform,會發生在這個方法(之前說過我們為您一般也不需要定義這個東西)
Request request = createRequest(started);
// 這裡其實主要是将request和key關聯,和我們之前說key可以了解為辨別就有了聯系
String requestKey = createKey(request);
// 是否在記憶體緩存中讀取資料
if (shouldReadFromMemoryCache(memoryPolicy)) {
// 從緩存中擷取bitmap
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
// 取到bitmap,就把網絡請求撤銷
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
// 成功回調這個方法,在基本使用中我們是使用過的
if (callback != null) {
callback.onSuccess();
}
return;
}
}
// 設定占位圖
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
// 最後開始送出任務
picasso.enqueueAndSubmit(action);
}
其中的cancelRequest方法回調用cancelExistingRequest(Action就在這裡簡單說明一下,其實它就是request的包裝類,還包括Picasso對象,緩存政策等)
void cancelExistingRequest(Object target) {
// 判斷是否在主線程
checkMain();
// 這個map是将請求對象和請求關聯起來(object 比如ImageView),移除請求對象
Action action = targetToAction.remove(target);
if (action != null) {
// 同時移除請求
action.cancel();
// 通過dispatcher分發器,用子線程分發器的handler來取消請求(關于這個,在get方法中描述過)
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
// 轉換成需要的ImageView
ImageView targetImageView = (ImageView) target;
// DeferredRequestCreator其實就是RequestCreator的包裝類,是為了對ImageView的
//監聽,跟進去,會發現getViewTreeObserver來監聽,擷取如具體的寬高等,是以也需要移除。
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
以上用到了兩個比較重要的類,BitmapHunter和Action,這裡我們簡單解釋以下
BitmapHunter實作了Runnable接口,我們要關注的是他的run方法
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
// 擷取圖檔(首先通過記憶體,不行在通過網絡,用okhttp3)
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (NetworkRequestHandler.ResponseException e) {
if (!NetworkPolicy.isOfflineOnly(e.networkPolicy) || e.code != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (IOException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(this);
} catch (Exception e) {
exception = e;
dispatcher.dispatchFailed(this);
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}
可以看到,在run方法中主要就是擷取我們需要的Bitmap,通過dispatcher調用方法,在子線程中通過Handler來進行操作。這裡我們需要了解的是,擷取圖檔,以及擷取成功後是否緩存,失敗後怎麼處理的一系列操作,這些都是通過Dispatcher中的handler來處理了的。
Action
這裡就不再贅述了,就是request的包裝類,其中包含了很多的其他東西,比如緩存政策,Picasso對象,等。
最後我們跟進去最後的送出任務
void enqueueAndSubmit(Action action) {
// 擷取請求操作的對象
Object target = action.getTarget();
// 判斷object和action是否比對(這個object可以是ImageView,而action又是
//request的包裝類,這一下,兩者的結合就比較明白了(請求操作對象和具體的請求)
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
// 發現不比對,取消請求
cancelExistingRequest(target);
// 将此時兩者關聯(也就是放在一個map集合當中)
targetToAction.put(target, action);
}
// 繼續送出
submit(action);
}
這個方法就是object和action的校驗檢查。最後我們繼續追蹤,會發現又是通過dispatcher所在子線程的handler來進行處理,最後一直調用performSubmit方法
void performSubmit(Action action, boolean dismissFailed) {
// 判斷是否延遲暫停加載(就是之前是否設定過暫停延遲的标記)
if (pausedTags.contains(action.getTag())) {
// 同樣将target和action關聯(此時存放發延遲加載),存放以便于後來的喚醒
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
// 擷取請求的圖檔捕獲器(就是一個runnable,說過,key可以當成bitmap和請求的标記)
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
// 這裡是對hunter的action初始化并進行健壯性判斷
hunter.attach(action);
return;
}
// 判斷目前線程池是否關閉
if (service.isShutdown()) {
// 如果關閉,列印日志,直接結束
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
// 這個方法主要是找到能夠處理相應請求request的requestHandler,并封裝成BitmapHuntere傳回
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
// 交給線程池處理(擷取圖檔),關注BitmapHunter的run方法(之前講過了)
hunter.future = service.submit(hunter);
// 關聯,将key和hunter關聯
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
當這一切都完成之後就可以設定圖檔,加載成功了。
總結
在這個方法中,我們需要了解和知道的是,我們首先進行一些判斷,是否在主線程,object的空指針和比對為題等,在一些健壯性判斷完成之後,開始建立請求送出,submit,以及我們的一些标記和建立聯系也都是貫穿建立的,也是通過分發handler來完成請求,從記憶體到網絡,最後送出成功,完成顯示。核心,就是任務的送出。
此次Picasso的感受:
這個設計非常nice的地方就是
1.通過建構者模式收集我們設定的屬性,同時完成一些初始化,我們最後就可以通過Builder擷取我們想要的屬性。
2.同時也是其他架構的一些共同點,就是封裝。确實這裡面很多的封裝類設計的非常好,比如Dispatcher,BitmapHunter,架構中非常突出的核心類。在傳遞的過程中,很精簡,類中包括了我們要的資訊,不需要過多的拆分,調用麻煩,或者不拆分,耦合嚴重。這是值得我們學習的。
2.同時也是最終要的,就是dispatcher和main的兩個handler,整個架構都是通過handler來完成事件的分發和傳遞。也就說明,以後在我們的項目中Handler确實要善用,合理的配置設定。