Picasso 是 Android 開發中最受歡迎的圖片請求載入框架之一 ,它誕生於 2013 年,距今已有五年的生命。在這五年間 Picasso 釋出過 21 個版本更新,而最近的一次更新為今年的 3 月 8 日,更新的版本號為 2.71828(文中統稱為新版),該版本離上一次釋出更新相隔了三年。本文主要分析新版 Picasso 的原始碼實現和它的一些 API 變化。
1. 新版 Picasso 的使用
新版 Picasso 最直觀的變化就是在 App 中的呼叫方式為:
Picasso.get().load(url).into(imageView);
複製程式碼
該呼叫跟原來版本的呼叫區別是,沒有了需要傳 Context 的 with 方法,取而代之是一個不需要傳參的 get 方法來獲取全域性唯一 Picasso 例項。
本文也主要通過分析 Picasso.get().load(url).into(imageView)
該句呼叫的來龍去脈來理清新版 Picasso 框架的實現原理。
2. Picasso 例項的獲取
Picasso 例項是通過呼叫 Picasso 類中的靜態方法 get 獲取的,該方法也是新版 Picasso 的入口,我們從該方法開始看起:
static volatile Picasso singleton = null;
public static Picasso get() {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
if (PicassoProvider.context == null) {
throw new IllegalStateException("context == null");
}
singleton = new Builder(PicassoProvider.context).build();
}
}
}
return singleton;
}
複製程式碼
上面這段程式碼是一個非常經典的雙重檢查鎖模式 (Double Checked Locking Pattern)。
首先進入 get 方法即檢查一次 Picasso 例項 singleton 是否為 null,不為 null 就可以直接返回該例項。
接著進入到同步塊,因為可能會一個執行緒進入同步塊後建立完物件後退出,另一個執行緒又緊接著進入同步塊,因此進入同步塊後需要再檢查一次 singleton 是否為 null,如果還為 null,這時候可以開始初始化該例項。
最後靜態變數 singleton 需要用 volatile 關鍵字來修飾,目的是為了防止重排序。
2.1 使用 ContentProvider 獲取 Context
新版 Picasso 提供給我們的入口方法 get 不需要傳 Context,因為它使用的是 PicassoProvider 類中的 context,我們看一下 PicassoProvider 類:
public final class PicassoProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak") static Context context;
@Override public boolean onCreate() {
context = getContext();
return true;
}
}
複製程式碼
PicassoProvider 類繼承自 ContentProvider,除了 onCreate 方法,其他方法都是預設實現 (為了節省篇幅,省略了該部分程式碼),而 onCreate 方法也只是呼叫 getContext 方法並賦值給靜態變數 context,然後返回 true 表示成功載入了該 ContentProvider。
Picasso 這麼做的理由是,只要將 PicassoProvider 在 AndroidManifest 檔案中註冊,那麼 App 在啟動的時候,系統就會自動回撥 PicassoProvider 的 onCreate 方法,因此也就自動獲取到了 Context。
2.2 Picasso 例項的建立
接著回到 Picasso.get 方法中,Picasso 例項通過該句程式碼建立:
singleton = new Builder(PicassoProvider.context).build();
複製程式碼
這裡使用到了常用的 Builder 設計模式。當一個類的屬性過多,通過建構函式構造一個物件過於複雜時,可以選擇使用 Builder 設計模式來簡化物件的構造過程。
看下 Picasso 類中靜態內部類 Builder 的建構函式:
public Builder(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
複製程式碼
該建構函式確保傳進來的 Context 例項不為 null,然後獲取全域性的 Application Context。
接著是 Picasso.Builder 類中的 build 方法:
private final Context context;
private Downloader downloader;
private ExecutorService service;
private Cache cache;
private Listener listener;
private RequestTransformer transformer;
private List<RequestHandler> requestHandlers;
private Bitmap.Config defaultBitmapConfig;
public Picasso build() {
Context context = this.context;
// 配置下載器 Downloader,用於從網路下載圖片資源,預設為 OkHttp3Downloader
if (downloader == null) {
downloader = new OkHttp3Downloader(context);
}
// 配置快取 Cache,用來儲存最近檢視使用的圖片,預設為 LruCache
if (cache == null) {
cache = new LruCache(context);
}
// 配置 ExecutorService,預設為 PicassoExecutorService
// 後面 Bitmap 的獲取任務就在該執行緒池中完成
if (service == null) {
service = new PicassoExecutorService();
}
// 配置 RequestTransformer 例項
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
// 建立 Stats 例項,Stats 類用來進行一些統計,如快取命中數,圖片下載數等
Stats stats = new Stats(cache);
// 建立 Dispatcher 例項,Dispatcher 類顧名思義,它的作用就是用來分發處理
// 各種圖片操作事件的如提交圖片請求事件,圖片獲取完成事件等;
// 傳入前面配置好的物件和 HANDLER 例項給 Dispatcher 類建構函式
// 該 HANDLER 在主執行緒接收處理事件,後面獲取到 Bitmap 後需要回撥到
// 該 HANDLER 的 handleMessage 方法中以便將 Bitmap 切換回主執行緒顯示
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
// 傳入前面配置好的一系列引數,建立 Picasso 例項
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
複製程式碼
該方法的邏輯與相關類作用已在註釋中進行了說明。
接下來看 Picasso 類的建構函式:
private final Listener listener;
private final RequestTransformer requestTransformer;
private final CleanupThread cleanupThread;
private final List<RequestHandler> requestHandlers;
final Context context;
final Dispatcher dispatcher;
final Cache cache;
final Stats stats;
final Map<Object, Action> targetToAction;
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
final ReferenceQueue<Object> referenceQueue;
final Bitmap.Config defaultBitmapConfig;
boolean indicatorsEnabled;
volatile boolean loggingEnabled;
boolean shutdown;
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
// 一些賦值操作
this.context = context;
this.dispatcher = dispatcher;
this.cache = cache;
this.listener = listener;
this.requestTransformer = requestTransformer;
this.defaultBitmapConfig = defaultBitmapConfig;
// Picasso 預設包含七個內建 RequestHandler 分別用來處理七種不同型別的請求
// 你也可以自己繼承 RequestHandler 類來處理你的自定義請求
// 自定義請求放在 extraRequestHandlers 中
int builtInHandlers = 7;
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
// 新增 ResourceRequestHandler,用於處理載入圖片資源 id 的情況
// ResourceRequestHandler 需要第一個進行新增
// 避免其他的 RequestHandler 檢查 (request.resourceId != 0) 的情況
allRequestHandlers.add(new ResourceRequestHandler(context));
// 然後新增自定義的 RequestHandler (如果有的話)
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);
}
// 新增 ContactsPhotoRequestHandler,用於處理手機聯絡人圖片
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
// 新增 MediaStoreRequestHandler,用於處理 content://media/ 開頭的 URI
allRequestHandlers.add(new MediaStoreRequestHandler(context));
// 新增 ContentStreamRequestHandler,用於處理 scheme 為 content 的 URI
allRequestHandlers.add(new ContentStreamRequestHandler(context));
// 新增 AssetRequestHandler,用於處理 file:///android_asset/ 開頭的 URI
allRequestHandlers.add(new AssetRequestHandler(context));
// 新增 FileRequestHandler,用於處理 scheme 為 file 的 URI
allRequestHandlers.add(new FileRequestHandler(context));
// 新增 NetworkRequestHandler,用於處理 http 或 https 圖片 url
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
// 呼叫 Collections 的靜態方法 unmodifiableList
// 返回一個不能進行修改操作的 List 例項,防止 requestHandlers 被修改
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.stats = stats;
this.targetToAction = new WeakHashMap<>();
this.targetToDeferredRequestCreator = new WeakHashMap<>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
this.referenceQueue = new ReferenceQueue<>();
this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
this.cleanupThread.start();
}
複製程式碼
到這裡 Picasso 例項就建立完畢了。
2.3 Dispatcher 例項的建立
Picasso.Builder 類的 build 方法在建立 Picasso 例項前先建立了 Dispatcher 類的例項,Dispatcher 類對後面分發處理圖片事件至關重要,這裡先看一下它的建構函式:
final DispatcherThread dispatcherThread;
final Context context;
final ExecutorService service;
final Downloader downloader;
final Map<String, BitmapHunter> hunterMap;
final Map<Object, Action> failedActions;
final Map<Object, Action> pausedActions;
final Set<Object> pausedTags;
final Handler handler;
final Handler mainThreadHandler;
final Cache cache;
final Stats stats;
final List<BitmapHunter> batch;
final NetworkBroadcastReceiver receiver;
final boolean scansNetworkChanges;
boolean airplaneMode;
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
// 建立靜態內部類 DispatcherThread 的例項並啟動
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
this.context = context;
this.service = service;
this.hunterMap = new LinkedHashMap<>();
this.failedActions = new WeakHashMap<>();
this.pausedActions = new WeakHashMap<>();
this.pausedTags = new LinkedHashSet<>();
// 建立靜態內部類 DispatcherHandler 的例項
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
this.downloader = downloader;
// 儲存前面 Picasso 類傳進來的主執行緒 HANDLER
this.mainThreadHandler = mainThreadHandler;
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<>(4);
}
複製程式碼
該建構函式首先建立了 DispatcherThread 例項,而後面 DispatcherHandler 例項的建立用到了 DispatcherThread 中的 Looper。
DispatcherThread 和 DispatcherHandler 都是 Dispatcher 中的靜態內部類:
static class DispatcherThread extends HandlerThread {
DispatcherThread() {
super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
}
}
複製程式碼
private static class DispatcherHandler extends Handler {
private final Dispatcher dispatcher;
DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}
複製程式碼
可以看到,DispatcherThread 繼承自 HandlerThread,而 DispatcherHandler 例項是通過 DispatcherThread 的 Looper 建立的,因此 DispatcherHandler 傳送的訊息將切換到工作執行緒 (即 DispatcherThread) 中處理,即 DispatcherHandler 的 handleMessage 方法會在工作執行緒中執行。
3. Picasso 類中的 load 方法
獲取到 Picasso 例項後,緊接著呼叫 Picasso 類的 load 方法,該方法主要作用就是建立並返回一個 RequestCreator 例項。
RequestCreator 類的主要作用就是建立 Request 物件,並提供了一系列的 into 方法來開始圖片請求。
先來看一下 Picasso 類中的 load 方法:
public RequestCreator load(@Nullable String path) {
// 如果傳進來的 path 為 null,建立並返回一個
// Uri 為 null 的 RequestCreator 物件。
if (path == null) {
return new RequestCreator(this, null, 0);
}
// 如果 path 為空字串,丟擲異常。
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
複製程式碼
最後 path 不為 null 也不為空字串,則呼叫 Uri.parse(path) 方法對 path 進行解析並返回 一個 Uri 物件傳給 load(@Nullable Uri uri) 方法。
public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}
複製程式碼
該方法建立並返回一個 RequestCreator 例項,看一下 RequestCreator 類的建構函式:
private final Picasso picasso;
private final Request.Builder data;
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
複製程式碼
可以看到 RequestCreator 物件持有了 Picasso 的一份引用,然後建立了 Request.Builder 類的例項 data,這裡又用到了 Builder 設計模式。
Request.Builder 的建構函式為:
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
this.uri = uri;
this.resourceId = resourceId;
this.config = bitmapConfig;
}
複製程式碼
到這裡 RequestCreator 就建立完成了,接下來就可以呼叫 RequestCreator 類中的許多方法,如 placeholder 方法,centerCrop 方法等,我們這裡直接前往 into 方法。
4. RequestCreator 類的 into 方法
經過 load 方法後,緊接著就來到了 into 方法,該方法是整個 API 呼叫流程的最後一步,可以說是整個呼叫流程的重頭戲,因此篇幅也比較大。
我們往 into 方法傳的是 ImageView 例項,看一下該方法原始碼:
public void into(ImageView target) {
into(target, null);
}
複製程式碼
public void into(ImageView target, Callback callback) {
// 記錄開始處理的時間戳
long started = System.nanoTime();
// 檢查當前方法是否在主執行緒進行呼叫,如果不是丟擲異常
checkMain();
// ImageView 例項 target 不能為 null,否則拋異常
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
// data 即前面的 Request.Builder 例項
// 如果 data 中沒有圖片(例如傳入的 path 為 null)
// 直接對該 target 取消請求,並設定佔點陣圖如果有設定 placeholder
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
// 建立 Request 例項
Request request = createRequest(started);
// 為當前 Request 生成一個 requestKey,用來標記 Request
String requestKey = createKey(request);
// 如果當前的 memoryPolicy 允許從快取中讀取圖片
// 從 Cache 中獲取 requestKey 對應的 Bitmap,如果該 Bitmap 存在
// 則取消當前請求,直接為 target 設定該 Bitmap
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
return;
}
}
// 前面快取中沒有查詢到圖片,從這裡開始請求
// 先設定 placeholder 如果有配置的話
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 建立一個 ImageViewAction 的例項
Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
// 向 picasso 提交該 Action 例項
picasso.enqueueAndSubmit(action);
}
複製程式碼
這裡省略了部分與 Picasso.get().load(uri).into(imageView)
呼叫不相關的程式碼,程式碼中的註釋只是大致邏輯流程,接下來按 into 方法的程式碼邏輯對一些細節進行分析。
4.1 設定佔點陣圖 (placeholder) 的實現
從 into 方法中可以看到,有兩處需要設定佔點陣圖,設定佔點陣圖的邏輯是:
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
複製程式碼
看一下 getPlaceholderDrawable 方法:
private Drawable getPlaceholderDrawable() {
if (placeholderResId != 0) {
if (Build.VERSION.SDK_INT >= 21) {
return picasso.context.getDrawable(placeholderResId);
} else if (Build.VERSION.SDK_INT >= 16) {
return picasso.context.getResources().getDrawable(placeholderResId);
} else {
TypedValue value = new TypedValue();
picasso.context.getResources().getValue(placeholderResId, value, true);
return picasso.context.getResources().getDrawable(value.resourceId);
}
} else {
return placeholderDrawable;
}
}
複製程式碼
該方法根據當前執行的 Android SDK 版本進行不同的方法呼叫來通過 placeholderResId 獲取到一個 Drawable 例項。
獲取到佔點陣圖 Drawable 後接下來就可以進行設定了,setPlaceholder 方法為 PicassoDrawable 類中的靜態方法,呼叫該方法後 ImageView 就可以顯示佔點陣圖了。
static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
target.setImageDrawable(placeholderDrawable);
if (target.getDrawable() instanceof Animatable) {
((Animatable) target.getDrawable()).start();
}
}
複製程式碼
4.2 Request 例項的建立
Request 物件是通過 createRequest 方法建立的:
private static final AtomicInteger nextId = new AtomicInteger();
private Request createRequest(long started) {
// 為 Request 例項分配下一個 id
int id = nextId.getAndIncrement();
// 建立 Request 例項
Request request = data.build();
request.id = id;
request.started = started;
return request;
}
複製程式碼
Request 例項通過呼叫 Request.Builder 類的 build 方法建立:
public Request build() {
// 先驗證當前的配置引數是否合法
// centerInside 和 centerCrop 方法不能同時用
if (centerInside && centerCrop) {
throw new IllegalStateException("Center crop and center inside can not be used together.");
}
// centerCrop 方法需要與 resize 方法同用
if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
// centerInside 方法需要與 resize 方法同用
if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center inside requires calling resize with positive width and height.");
}
// 設定 priority
if (priority == null) {
priority = Priority.NORMAL;
}
// 建立 Request 例項
return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
}
複製程式碼
該方法在最後一步建立了 Request 例項,看一下 Request 類的建構函式:
public final Uri uri;
public final int resourceId;
public final String stableKey;
public final List<Transformation> transformations;
public final int targetWidth;
public final int targetHeight;
public final boolean centerCrop;
public final int centerCropGravity;
public final boolean centerInside;
public final boolean onlyScaleDown;
public final float rotationDegrees;
public final float rotationPivotX;
public final float rotationPivotY;
public final boolean hasRotationPivot;
public final boolean purgeable;
public final Bitmap.Config config;
public final Priority priority;
private Request(Uri uri, int resourceId, String stableKey, List<Transformation> transformations,
int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside,
int centerCropGravity, boolean onlyScaleDown, float rotationDegrees,
float rotationPivotX, float rotationPivotY, boolean hasRotationPivot,
boolean purgeable, Bitmap.Config config, Priority priority) {
// 圖片的 Uri,與 resourceId 不能共存
this.uri = uri;
// 圖片的 resourceId,與 Uri 不能共存
this.resourceId = resourceId;
this.stableKey = stableKey;
// 用來對 Bitmap 進行轉換的一系列 Transformation
if (transformations == null) {
this.transformations = null;
} else {
this.transformations = unmodifiableList(transformations);
}
// resize 方法設定的圖片寬度和高度
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
// 圖片 scaleType 是否為 centerCrop,與 centerInside 不共存
this.centerCrop = centerCrop;
// 圖片 scaleType 是否為 centerInside,與 centerCrop 不共存
this.centerInside = centerInside;
// 如果設定了 centerCrop,centerCropGravity 用來設定中心的偏移量
this.centerCropGravity = centerCropGravity;
this.onlyScaleDown = onlyScaleDown;
// 圖片旋轉的度數
this.rotationDegrees = rotationDegrees;
this.rotationPivotX = rotationPivotX;
this.rotationPivotY = rotationPivotY;
this.hasRotationPivot = hasRotationPivot;
this.purgeable = purgeable;
this.config = config;
// 當前請求的優先順序
this.priority = priority;
}
複製程式碼
Request 例項建立完畢。
4.3 requestKey 的建立
requestKey 用來標識一個 Request,requestKey 通過呼叫 createKey 方法實現:
static final StringBuilder MAIN_THREAD_KEY_BUILDER = new StringBuilder();
static String createKey(Request data) {
String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
MAIN_THREAD_KEY_BUILDER.setLength(0);
return result;
}
複製程式碼
static String createKey(Request data, StringBuilder builder) {
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
builder.append(KEY_SEPARATOR);
if (data.rotationDegrees != 0) {
builder.append("rotation:").append(data.rotationDegrees);
if (data.hasRotationPivot) {
builder.append(`@`).append(data.rotationPivotX).append(`x`).append(data.rotationPivotY);
}
builder.append(KEY_SEPARATOR);
}
if (data.hasSize()) {
builder.append("resize:").append(data.targetWidth).append(`x`).append(data.targetHeight);
builder.append(KEY_SEPARATOR);
}
if (data.centerCrop) {
builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}
if (data.transformations != null) {
for (int i = 0, count = data.transformations.size(); i < count; i++) {
builder.append(data.transformations.get(i).key());
builder.append(KEY_SEPARATOR);
}
}
return builder.toString();
}
複製程式碼
該方法根據當前 Request 配置的引數來生成對應的 requestKey。
4.4 MemoryPolicy 的實現
into 方法通過呼叫 shouldReadFromMemoryCache 方法來判斷是否應該從 Cache 中讀取當前 requestKey 對應的 Bitmap。
shouldReadFromMemoryCache 方法是列舉型別 MemoryPolicy 中的一個靜態方法,MemoryPolicy 原始碼如下:
public enum MemoryPolicy {
NO_CACHE(1 << 0),
NO_STORE(1 << 1);
static boolean shouldReadFromMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
}
static boolean shouldWriteToMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
}
final int index;
MemoryPolicy(int index) {
this.index = index;
}
}
複製程式碼
可以看到 MemoryPolicy 用到了一些位操作。
MemoryPolicy 共兩種列舉型別,NO_CACHE 和 NO_STORE,NO_CACHE 的 index 為 1 (二進位制為 1),NO_STORE 的 index 為 2 (二進位制為 10)。
shouldReadFromMemoryCache 方法返回 true 如果 (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0
,即 memoryPolicy 為 0 。返回 true 表示當前 memoryPolicy 允許從 Cache 中讀取圖片。
shouldWriteToMemoryCache 方法在滿足 (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0
的條件下返回 true,即 memoryPolicy 為 0。返回 true 表示當前 memoryPolicy 允許向 Cache 中寫入圖片。
而在沒有配置 memoryPolicy 的情況下,memoryPolicy 預設為 0,因此這兩個方法這裡都會返回 true。
4.5 Action 例項的建立
Picasso 將一次圖片獲取活動封裝成一個 Action 例項。
Action 為抽象類,包含兩個必須實現的抽象方法,complete 方法和 error 方法,分別表示該次圖片獲取活動完成或出錯。
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error(Exception e);
複製程式碼
Picasso 提供了不同的 Action 子類來對應不同的圖片獲取活動。
我們這裡用到的是 ImageViewAction,ImageViewAction 用於將獲取到的 Bitmap 載入到 ImageView 中:
class ImageViewAction extends Action<ImageView> {
Callback callback;
ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
Callback callback, boolean noFade) {
super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
tag, noFade);
this.callback = callback;
}
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
// 獲取要載入圖片進去的 ImageView
ImageView target = this.target.get();
if (target == null) return;
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
// 為 target 設定 Bitmap
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
// 回撥 callback
if (callback != null) callback.onSuccess();
}
@Override public void error(Exception e) {
ImageView target = this.target.get();
if (target == null) return;
// 獲取佔點陣圖 Drawable 例項,如果該 Drawable 實現了 Animatable 介面
// 這時候就應該停止該 Animatable
Drawable placeholder = target.getDrawable();
if (placeholder instanceof Animatable) ((Animatable) placeholder).stop();
// 設定錯誤情況下的圖片
if (errorResId != 0) {
target.setImageResource(errorResId);
} else if (errorDrawable != null) {
target.setImageDrawable(errorDrawable);
}
if (callback != null) callback.onError(e);
}
@Override void cancel() {
super.cancel();
if (callback != null) callback = null;
}
}
複製程式碼
可以看到 ImageViewAction 類繼承自 Action,泛型引數為 ImageView 作為該 Action 的 target。
ImageViewAction 實現了 complete 方法和 error 方法並重寫了 cancel 方法。
4.6 Action 例項的提交
Action 例項建立完畢後,就可以呼叫 Picasso 類中的 enqueueAndSubmit 方法提交該 Action 例項了,然後從這裡開始一次圖片獲取活動,由於這部分程式碼過於龐大,從 enqueueAndSubmit 方法開始放在下一節分析。
5. Picasso 處理 Action 的實現
Picasso 處理 Action 從 Picasso 類的 enqueueAndSubmit 方法開始:
void enqueueAndSubmit(Action action) {
// 省略部分程式碼...
// 呼叫 submit 方法提交該 Action
submit(action);
}
複製程式碼
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
複製程式碼
submit 方法又呼叫了 Dispatcher 類的 dispatchSubmit 方法,Picasso 類這時候就將此 Action 交接給 Dispatcher 類進行處理。
5.1 Dispatcher 類提交 Action 的實現
Picasso 通過呼叫 Dispatcher 類的 dispatchSubmit 方法開始提交該 Action 例項:
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
複製程式碼
該方法使用 handler 傳送了一條 REQUEST_SUBMIT 資訊,我們在 2.3 小節中可以看到,該 handler 即 DispatcherHandler 例項,傳送該訊息後會在工作執行緒回撥到 DispatcherHandler 中的 handleMessage 方法,然後從該方法接著會呼叫 Dispatcher 類的 performSubmit 方法:
void performSubmit(Action action) {
performSubmit(action, true);
}
複製程式碼
void performSubmit(Action action, boolean dismissFailed) {
// 建立 BitmapHunter 例項
BitmapHunter hunter = forRequest(action.getPicasso(), this, cache, stats, action);
// 使用配置好的 PicassoExecutorService 提交該 BitmapHunter 例項
hunter.future = service.submit(hunter);
}
複製程式碼
上面的程式碼做了一些簡化,刪除了與當前邏輯無關的程式碼。
該方法通過呼叫 forRequest 方法來建立 BitmapHunter 例項,而 forRequest 方法是 BitmapHunter 類中的靜態方法。
從上面的程式碼可以看到,接下來的工作就交給 BitmapHunter 來完成了。
5.2 BitmapHunter 類的實現
BitmapHunter 類的主要職責是結合 Action 和 RequestHandler 來獲取 Bitmap 例項。
BitmapHunter 提供了靜態方法 forRequest 來建立例項:
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
// 獲取 Action 中的 Request 物件
Request request = action.getRequest();
// 獲取 Picasso 配置的全部 RequestHandler
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
// 從下標 0 開始迭代全部 RequestHandler,如果該 RequestHandler 能
// 處理該 Request,則用該 RequestHandler 建立 BitmapHunter 例項並返回。
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
// 沒有 RequestHandler 能處理該 Request,傳入 ERRORING_HANDLER
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
複製程式碼
這裡的 requestHandlers 部分用到了責任鏈模式 (Chains of Responsibility)。
接著看下 BitmapHunter 的建構函式:
final int sequence;
final Picasso picasso;
final Dispatcher dispatcher;
final Cache cache;
final Stats stats;
final String key;
final Request data;
final int memoryPolicy;
int networkPolicy;
final RequestHandler requestHandler;
Action action;
List<Action> actions;
Bitmap result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifOrientation;
int retryCount;
Priority priority;
BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,
RequestHandler requestHandler) {
this.sequence = SEQUENCE_GENERATOR.incrementAndGet();
this.picasso = picasso;
this.dispatcher = dispatcher;
this.cache = cache;
this.stats = stats;
this.action = action;
this.key = action.getKey();
this.data = action.getRequest();
this.priority = action.getPriority();
this.memoryPolicy = action.getMemoryPolicy();
this.networkPolicy = action.getNetworkPolicy();
this.requestHandler = requestHandler;
this.retryCount = requestHandler.getRetryCount();
}
複製程式碼
BitmapHunter 類實現了 Runnable 介面,因此當前面呼叫 service.submit(hunter) ,BitmapHunter 類中的 run 方法會線上程池中執行:
@Override public void run() {
try {
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (Exception e) {
// 處理一堆異常...
}
}
複製程式碼
run 方法主要通過呼叫 hunt 方法獲取返回的 Bitmap 並賦值給 result,如果 result 不為 null,則呼叫 Dispatcher 類的 dispatchComplete 方法,否則呼叫 dispatchFailed 方法。
因此獲取 Bitmap 的具體邏輯就在 hunt 方法中完成:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
// 先從快取中查詢 key 對應的 Bitmap,該 key 即之前建立的 requestKey
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
return bitmap;
}
}
// 呼叫 RequestHandler 的 load 方法獲取 RequestHandler.Result 例項
// Picasso 將 RequestHandler 載入的結果封裝成一個 Result 物件
// 我們這裡呼叫的是 NetworkRequestHandler 類中的 load 方法
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
// result 中的 bitmap 為 null,將 source 中的位元組流編碼成 Bitmap
if (bitmap == null) {
Source source = result.getSource();
try {
bitmap = decodeStream(source, data);
} finally {
try {
source.close();
} catch (IOException ignored) {
}
}
}
}
return bitmap;
}
複製程式碼
hunt 方法結束,返回獲取到的 Bitmap 例項,回到 BitmapHunter 的 run 方法,這時候如果返回的 Bitmap 例項不為null,就呼叫 Dispatcher 類中的 dispatchComplete 方法,這樣剩下的工作又交接給了 Dispatcher。
5.3 Dispatcher 類請求完成的實現
BitmapHunter 獲取到不為 null 的 Bitmap 例項後,呼叫 Dispatcher 類中的 dispatchComplete 方法分發其完成事件:
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
複製程式碼
handler 傳送該訊息後,接著又在工作執行緒中回撥到了 Dispatcher 類的 performComplete 方法:
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
batch(hunter);
}
複製程式碼
這時候 Picasso 將 Bitmap 存入到了快取中,然後呼叫 batch 方法:
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
if (hunter.result != null) {
hunter.result.prepareToDraw();
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
複製程式碼
handler 最後傳送了 HUNTER_DELAY_NEXT_BATCH 訊息,傳送該訊息後又回撥到了 Dispatcher 類的 performBatchComplete 方法:
void performBatchComplete() {
batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
}
複製程式碼
這時候傳送訊息的是 mainThreadHandler,切換到主執行緒來了,傳送的訊息為 HUNTER_BATCH_COMPLETE,該訊息回撥到了 Picasso 類中的 HANDLER 例項中:
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
};
複製程式碼
對每個 BitmapHunter 例項呼叫 Picasso 類中的 complete 方法:
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
if (single == null) return;
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();
deliverAction(result, from, single, exception);
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}
複製程式碼
private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
if (action.isCancelled()) {
return;
}
if (result != null) {
if (from == null) {
throw new AssertionError("LoadedFrom cannot be null.");
}
action.complete(result, from);
}
} else {
action.error(e);
}
}
複製程式碼
我們之前提交的是 ImageViewAction 例項,因此這時候會回撥到 ImageViewAction 類的 complete 方法:
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
ImageView target = this.target.get();
if (target == null) return;
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}
複製程式碼
最後 setBitmap:
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof Animatable) {
((Animatable) placeholder).stop();
}
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
}
複製程式碼
結束該方法後,這時候圖片終於成功在介面顯示了,整個呼叫流程到此也終於結束。