天天看點

Picasso(四into設定圖檔方法解析)

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确實要善用,合理的配置設定。