具體點(diǎn)說(shuō),圖片顯示到界面上這個(gè)過(guò)程中可能會(huì)遇到這些情況:
加載的圖片可能有網(wǎng)絡(luò)、本地等多種來(lái)源;
如果是網(wǎng)絡(luò)的話,就得先下載下來(lái);
下載過(guò)程中可能需要暫停、恢復(fù)或者取消;
下載后需要解碼、對(duì)圖片進(jìn)行一些額外操作(比如裁剪、轉(zhuǎn)變等);
最好還有個(gè)緩存系統(tǒng),避免每次都去網(wǎng)絡(luò)請(qǐng)求;
為了實(shí)現(xiàn)性能監(jiān)控,最好再有個(gè)數(shù)據(jù)統(tǒng)計(jì)功能…
有了以上需求,根據(jù)職責(zé)分離的原則,我們可以定義一些核心類(lèi)來(lái)完成上述功能:
請(qǐng)求信息類(lèi):其中包含了所有可以配置的選項(xiàng),比如圖片地址、要進(jìn)行的操作等
圖片獲取類(lèi):根據(jù)不同的來(lái)源去不同地方獲取,比如網(wǎng)絡(luò)、本地、內(nèi)存等
調(diào)度器類(lèi):實(shí)現(xiàn)圖片獲取的入隊(duì)、執(zhí)行、完成、取消、暫停等
圖片處理類(lèi):圖片拿到后進(jìn)行解碼、反轉(zhuǎn)、裁剪等
緩存類(lèi):圖片的內(nèi)存、磁盤(pán)緩存控制
監(jiān)控類(lèi):統(tǒng)計(jì)核心數(shù)據(jù),比如當(dāng)前內(nèi)存、磁盤(pán)緩存的大小、某個(gè)圖片的加載時(shí)間等
認(rèn)識(shí)核心 API

我給 Picasso 文件夾結(jié)構(gòu)進(jìn)行了調(diào)整,變成了這樣:

主要分為幾個(gè)關(guān)鍵部分:
request 文件夾中的:請(qǐng)求信息相關(guān)的類(lèi)
action 文件夾中的:加載行為相關(guān)的類(lèi)
handler 文件夾中的:圖片獲取具體處理的類(lèi)
Dispatcher:調(diào)度器
BitmapHunter:耗時(shí)任務(wù)執(zhí)行者
Picasso:暴露給用戶(hù)的類(lèi)
請(qǐng)求信息相關(guān)的類(lèi)

上圖中的 request 文件夾里放的是 Picasso 中構(gòu)建圖片請(qǐng)求信息相關(guān)的類(lèi),總共有五個(gè),我們來(lái)分別了解下它們。
首先看 Request.java的成員變量(直接看它的 Builder ):
/** Builder for creating {@link Request} instances. */
public static final class Builder {
private Uri uri;
private int resourceId;
private String stableKey;
private int targetWidth;
private int targetHeight;
private boolean centerCrop;
private int centerCropGravity;
private boolean centerInside;
private boolean onlyScaleDown;
private float rotationDegrees;
private float rotationPivotX;
private float rotationPivotY;
private boolean hasRotationPivot;
private boolean purgeable;
private List<Transformation> transformations;
private Bitmap.Config config;
private Priority priority;
//...
}
可以看到,Request 中放的是一個(gè)圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息、圖片配置信息以及優(yōu)先級(jí)等。
這里我們可以學(xué)習(xí)到的是:如果一個(gè)請(qǐng)求參數(shù)很多,我們最好用一個(gè)類(lèi)給它封裝起來(lái),避免在傳遞時(shí)傳遞多個(gè)參數(shù);如果經(jīng)常使用的話,還可以創(chuàng)建一個(gè)對(duì)象池,節(jié)省開(kāi)銷(xiāo)。
接著看第二個(gè)類(lèi) RequestCreator:
public class RequestCreator {
private static final AtomicInteger nextId = new AtomicInteger();
private final Picasso picasso;
private final Request.Builder data;
private boolean noFade;
private boolean deferred;
private boolean setPlaceholder = true;
private int placeholderResId;
private int errorResId;
private int memoryPolicy;
private int networkPolicy;
private Drawable placeholderDrawable;
private Drawable errorDrawable;
private Object tag;
//...
}
可以看到, RequestCreator 中包含了 Request.Builder,此外還有了些額外的信息,比如是否設(shè)置占位圖、是否有漸變動(dòng)畫(huà)、是否延遲處理、以及占位圖錯(cuò)誤圖資源 ID、內(nèi)存使用策略、網(wǎng)絡(luò)請(qǐng)求策略等。
RequestCreator 是相當(dāng)重要的一個(gè)類(lèi),我們后面會(huì)進(jìn)一步介紹它。
接著看第三個(gè)類(lèi) DeferredRequestCreator:
public class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
private final RequestCreator creator;
public @VisibleForTesting final WeakReference<ImageView> target;
@VisibleForTesting
public Callback callback;
//...
}
可以看到, DeferredRequestCreator 中引用了 RequestCreator,此外還有一個(gè)要加載的 ImageView 弱引用對(duì)象,還有一個(gè) Callback,它實(shí)現(xiàn)了 OnPreDrawListener 和 onAttachStateChangeListener 接口,這兩個(gè)接口的作用如下:
OnPreDrawListener:當(dāng)布局樹(shù)將要繪制前,會(huì)回調(diào)這個(gè)借口的 onPreDraw() 方法
onAttachStateChangeListener:當(dāng)布局綁定到一個(gè) window 或者解除綁定和一個(gè) window 時(shí)會(huì)調(diào)用
DeferredRequestCreator 中比較重要的就是這個(gè) onPreDraw() 方法:
@Override public boolean onPreDraw() {
ImageView target = this.target.get();
if (target == null) {
return true;
}
ViewTreeObserver vto = target.getViewTreeObserver();
if (!vto.isAlive()) {
return true;
}
int width = target.getWidth();
int height = target.getHeight();
if (width <= 0 || height <= 0) {
return true;
}
target.removeOnAttachStateChangeListener(this);
vto.removeOnPreDrawListener(this);
this.target.clear();
this.creator.unfit().resize(width, height).into(target, callback);
return true;
}
在加載網(wǎng)絡(luò)圖片后需要讓圖片的尺寸和目標(biāo) ImageView 一樣大時(shí)(即調(diào)用 RequestCreator.fit() 方法),會(huì)使用到 DeferredRequestCreator。
剩下的兩個(gè)枚舉 MemoryPolicy 和 NetworkPolicy 就簡(jiǎn)單多了。
MemoryPolicy 指定了兩種內(nèi)存緩存策略:不去內(nèi)存緩存里查找和不寫(xiě)入內(nèi)存緩存。
public enum MemoryPolicy {
//當(dāng)請(qǐng)求圖片時(shí)不去內(nèi)存緩存里找
NO_CACHE(1 << 0),
//拿到圖片后不寫(xiě)到內(nèi)存緩存里,一般用于一次性請(qǐng)求
NO_STORE(1 << 1);
public static boolean shouldReadFromMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
}
public static boolean shouldWriteToMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
}
}
NetworkPolicy 指定了三種網(wǎng)絡(luò)請(qǐng)求策略:
NO_CACHE: 跳過(guò)檢查磁盤(pán)緩存,強(qiáng)制請(qǐng)求網(wǎng)絡(luò)
NO_STORE: 拿到結(jié)果不寫(xiě)入磁盤(pán)緩存中
OFFLINE: 不請(qǐng)求網(wǎng)絡(luò),只能去磁盤(pán)緩存里查找
public enum NetworkPolicy {
NO_CACHE(1 << 0),
NO_STORE(1 << 1),
OFFLINE(1 << 2);
public static boolean shouldReadFromDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;
}
public static boolean shouldWriteToDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
}
public static boolean isOfflineOnly(int networkPolicy) {
return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
}
}
上面介紹了 Picasso 中關(guān)于請(qǐng)求信息的五個(gè)類(lèi),小結(jié)一下,它們的作用如下:
Request:保存一個(gè)圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息、圖片配置信息以及優(yōu)先級(jí)
RequestCreator:保存了一個(gè)圖片加載請(qǐng)求的完整信息,包括圖片信息、是否設(shè)置占位圖、是否有漸變動(dòng)畫(huà)、是否延遲處理、以及占位圖錯(cuò)誤圖資源 ID、內(nèi)存使用策略、網(wǎng)絡(luò)請(qǐng)求策略等
MemoryPolicy:定義了加載圖片時(shí)的兩種圖片緩存策略
NetworkPolicy:定義了加載圖片時(shí)的三種網(wǎng)絡(luò)請(qǐng)求策略
加載行為相關(guān)的類(lèi)
了解完請(qǐng)求信息相關(guān)的類(lèi)后,我們?cè)倏纯?action 文件夾下,關(guān)于加載行為的類(lèi)(這里的 “加載行為” 是我臨時(shí)起的名,可能不是很容易理解,稍后我再解釋一下)。

public abstract class Action<T> {
public final Picasso picasso;
public final Request request;
public final WeakReference<T> target;
public final boolean noFade;
public final int memoryPolicy;
public final int networkPolicy;
public final int errorResId;
public final Drawable errorDrawable;
public final String key;
public final Object tag;
public boolean willReplay;
public boolean cancelled;
/**
* 圖片獲取到后要調(diào)用的方法
* @param result
* @param from
*/
public abstract void complete(Bitmap result, Picasso.LoadedFrom from);
/**
* 圖片獲取失敗后要調(diào)用的方法
* @param e
*/
public abstract void error(Exception e);
}
可以看到, Action 的成員變量里包含了一個(gè)圖片的請(qǐng)求信息和加載策略、錯(cuò)誤占位圖,同時(shí)定義了兩個(gè)抽象方法,這兩個(gè)方法的作用是當(dāng)圖片加載成功后會(huì)調(diào)用 complete()(參數(shù)是拿到的圖片和加載來(lái)源),加載失敗后會(huì)調(diào)用 eror(),子類(lèi)繼承后可以實(shí)現(xiàn)自己特定的操作。
前面提到這些 action 表示的是加載行為,所謂“加載行為”簡(jiǎn)單點(diǎn)說(shuō)就是“拿到圖片要干啥”
發(fā)起一個(gè)圖片加載請(qǐng)求的目的可能有多種,最常見(jiàn)的就是加載到圖片上,對(duì)應(yīng) Picasso 里的 ImageViewAction(加載完成時(shí)它會(huì)把圖片設(shè)置給 ImageView):
public class ImageViewAction extends Action<ImageView> {
Callback callback;
//加載成功,將圖片設(shè)置給 ImageView
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
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); //設(shè)置圖片
if (callback != null) {
callback.onSuccess();
}
}
//失敗時(shí)給 ImageView 設(shè)置錯(cuò)誤圖片
@Override public void error(Exception e) {
ImageView target = this.target.get();
if (target == null) {
return;
}
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);
}
}
}
除此外,Picasso 還提供了四種其他用途的加載行為類(lèi),源碼比較容易理解,這里就直接貼出作用:
FetchAction: 拿到圖片后會(huì)有個(gè)回調(diào),除此外不會(huì)將圖片顯示到界面上
Picasso.fetch() 方法會(huì)使用到它,這個(gè)方法在后臺(tái)線程異步加載圖片,只會(huì)將圖片保存到硬盤(pán)或者內(nèi)存上,不會(huì)顯示到界面上。如果你不久之后就用這個(gè)圖片,或者想要減少加載時(shí)間,你可以提前將圖片下載緩存起來(lái)。
GetAction:拿到圖片后不會(huì)有任何操作,不知道干啥的
Picasso.get() 方法會(huì)使用到它,這個(gè)方法會(huì)同步加載圖片并返回 Bitmap 對(duì)象,請(qǐng)確保你沒(méi)有在Ui線程里面使用.get() 方法。這將會(huì)阻塞UI!
RemoteViewsAction: 拿到圖片后設(shè)置給 RemoteView,有兩個(gè)實(shí)現(xiàn)類(lèi) AppWidgetAction 和 NotificationAction,分別對(duì)應(yīng)桌面插件和提醒欄
TargetAction:首先 Target 是 Picasso 中定義的一個(gè)接口,表示對(duì)圖片加載的監(jiān)聽(tīng);TargetAction 在拿到圖片后會(huì)調(diào)用 Target 接口的方法
接著介紹 handler 文件夾下的類(lèi),這個(gè)文件夾中類(lèi)的功能就是:處理去不同渠道加載圖片的請(qǐng)求。

其中 RequestHandler 是基類(lèi),我們先來(lái)看看它。
public abstract class RequestHandler {
/**
* Whether or not this {@link RequestHandler} can handle a request with the given {@link Request}.
*/
public abstract boolean canHandleRequest(Request data);
/**
* Loads an image for the given {@link Request}.
*
* @param request the data from which the image should be resolved.
* @param networkPolicy the {@link NetworkPolicy} for this request.
*/
@Nullable public abstract Result load(Request request, int networkPolicy) throws IOException;
RequestHandler 代碼也比較簡(jiǎn)單,除了幾個(gè)計(jì)算圖片尺寸的方法外,最關(guān)鍵的就是上述的兩個(gè)抽象方法:
boolean canHandleRequest(Request data) 表示當(dāng)前獲取類(lèi)能否處理這個(gè)請(qǐng)求,一般子類(lèi)會(huì)根據(jù)請(qǐng)求的 URI 來(lái)判斷
Result load(Request request, int networkPolicy) 表示根據(jù)網(wǎng)絡(luò)策略加載某個(gè)請(qǐng)求,返回加載結(jié)果
加載結(jié)果 Result 也比較簡(jiǎn)單:
public static final class Result {
private final Picasso.LoadedFrom loadedFrom; //從哪兒加載的(網(wǎng)絡(luò)、內(nèi)存、磁盤(pán))
private final Bitmap bitmap;
private final Source source; //okio 中定義的數(shù)據(jù)流類(lèi)
private final int exifOrientation; //圖片的旋轉(zhuǎn)方向
public Result(@NonNull Bitmap bitmap, @NonNull Picasso.LoadedFrom loadedFrom) {
this(checkNotNull(bitmap, "bitmap == null"), null, loadedFrom, 0);
}
public Result(@NonNull Source source, @NonNull Picasso.LoadedFrom loadedFrom) {
this(null, checkNotNull(source, "source == null"), loadedFrom, 0);
}
Result(
@Nullable Bitmap bitmap,
@Nullable Source source,
@NonNull Picasso.LoadedFrom loadedFrom,
int exifOrientation) {
if ((bitmap != null) == (source != null)) {
throw new AssertionError();
}
this.bitmap = bitmap;
this.source = source;
this.loadedFrom = checkNotNull(loadedFrom, "loadedFrom == null");
this.exifOrientation = exifOrientation;
}
}
RequestHandler 的子類(lèi)實(shí)現(xiàn)都比較簡(jiǎn)單,這里我們就選常見(jiàn)的處理網(wǎng)絡(luò)和文件請(qǐng)求的獲取類(lèi)來(lái)看看。
從名字就可以看出的從網(wǎng)絡(luò)獲取圖片處理類(lèi) NetworkRequestHandler:
public class NetworkRequestHandler extends RequestHandler {
private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
private final Downloader downloader;
private final Stats stats;
public NetworkRequestHandler(Downloader downloader, Stats stats) {
this.downloader = downloader;
this.stats = stats;
}
//根據(jù)請(qǐng)求 uri 的 scheme 判斷是否為 http/https 請(qǐng)求
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
//去網(wǎng)絡(luò)加載一個(gè)圖片
@Override public Result load(Request request, int networkPolicy) throws IOException {
okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
Response response = downloader.load(downloaderRequest);
ResponseBody body = response.body();
if (!response.isSuccessful()) {
body.close();
throw new ResponseException(response.code(), request.networkPolicy);
}
// Cache response is only null when the response comes fully from the network. Both completely
// cached and conditionally cached responses will have a non-null cache response.
Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
// Sometimes response content length is zero when requests are being replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
if (loadedFrom == DISK && body.contentLength() == 0) {
body.close();
throw new ContentLengthException("Received response with 0 content-length header.");
}
return new Result(body.source(), loadedFrom);
}
}
從上面的代碼我們可以看到,Picasso 使用 okhttp3 來(lái)完成下載的功能,其中的下載器 downloader 就是通過(guò)構(gòu)造一個(gè) okhttp.Call 來(lái)完成同步下載文件:
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
從文件獲取圖片的請(qǐng)求處理類(lèi) FileRequestHandler:
public class FileRequestHandler extends ContentStreamRequestHandler {
public FileRequestHandler(Context context) {
super(context);
}
@Override public boolean canHandleRequest(Request data) {
return SCHEME_FILE.equals(data.uri.getScheme());
}
@Override public Result load(Request request, int networkPolicy) throws IOException {
Source source = Okio.source(getInputStream(request));
return new Result(null, source, DISK, getFileExifRotation(request.uri));
}
InputStream getInputStream(Request request) throws FileNotFoundException {
ContentResolver contentResolver = context.getContentResolver();
return contentResolver.openInputStream(request.uri);
}
static int getFileExifRotation(Uri uri) throws IOException {
ExifInterface exifInterface = new ExifInterface(uri.getPath());
return exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
}
}
也很簡(jiǎn)單是吧,根據(jù) URI 獲取輸入流通過(guò) ContentResolver.openInputStream( Uri uri) 可以實(shí)現(xiàn),這個(gè)可以記一下以后可能會(huì)用到,拿到 IO 流后,接下來(lái)的的操作直接通過(guò) Okio 實(shí)現(xiàn)了。
通過(guò)這幾個(gè)圖片請(qǐng)求處理類(lèi)我們可以看到 Picasso 的設(shè)計(jì)多么精巧,每個(gè)類(lèi)即精簡(jiǎn)又功能獨(dú)立,我們?cè)陂_(kāi)發(fā)時(shí)最好可以參考這樣的代碼,先定義接口和基類(lèi),然后再考慮不同的實(shí)現(xiàn)。
分析完這些“大家族”后,剩下的就是一些單獨(dú)的類(lèi)了。
調(diào)度器 Dispatcher
調(diào)度器的角色在許多框架里可以看到,實(shí)際上在稍微復(fù)雜一點(diǎn)的業(yè)務(wù)邏輯,都需要這么一個(gè)調(diào)度器類(lèi),它負(fù)責(zé)業(yè)務(wù)邏輯在不同線程的切換、執(zhí)行、取消。
我們來(lái)看看 Picasso 中的調(diào)度器,首先看它的成員變量:
public class Dispatcher {
private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
private static final int BATCH_DELAY = 200; // ms
final DispatcherThread dispatcherThread; //HandlerThread,用于為子線程 Handler 準(zhǔn)備 Looper
final Context context;
final ExecutorService service; //線程池
final Downloader downloader; //下載器
final Map<String, BitmapHunter> hunterMap; //Action's key 和 圖片獵人 的關(guān)聯(lián)關(guān)系
final Map<Object, Action> failedActions; //失敗的操作 map
final Map<Object, Action> pausedActions; //暫停的操作 map
final Set<Object> pausedTags; //暫停的 tag
final Handler handler; //子線程的 Handler
final Handler mainThreadHandler; //ui 線程的 Handler
final Cache cache; //緩存
final Stats stats; //數(shù)據(jù)統(tǒng)計(jì)
final List<BitmapHunter> batch; //后面介紹,獲取圖片最核心的類(lèi)
final NetworkBroadcastReceiver receiver;
final boolean scansNetworkChanges;
boolean airplaneMode;
}
可以看到,Dispatcher 的成員變量有 HandlerThread,兩個(gè) Handler、線程池,下載器、BitmapHunter(我叫它“圖片獵手”,后面介紹它)、緩存、數(shù)據(jù)統(tǒng)計(jì)等等。
從 Picasso 的 Dispatcher 中,我們可以學(xué)到如何創(chuàng)建一個(gè)復(fù)雜業(yè)務(wù)的調(diào)度器。
復(fù)雜業(yè)務(wù)往往需要在子線程中進(jìn)行,于是需要用到線程池;線程之間切換需要用到 Handler,為了省去創(chuàng)建 Looper 的功夫,就需要使用 HandlerThread;此外還需要持有幾個(gè)列表、Map,來(lái)保存操作數(shù)據(jù)。
作為調(diào)度器,最重要的功能就是給外界提供各種功能的接口,一般我們都使用不同的常量來(lái)標(biāo)識(shí)不同的邏輯,在開(kāi)始寫(xiě)業(yè)務(wù)之前,最好先定好功能、確定常量。
我們來(lái)看看 Dispatcher 中定義的常量都代表著什么功能:
private static final int RETRY_DELAY = 500; //重試的延遲時(shí)間
private static final int AIRPLANE_MODE_ON = 1;
private static final int AIRPLANE_MODE_OFF = 0;
public static final int REQUEST_SUBMIT = 1; //提交請(qǐng)求
public static final int REQUEST_CANCEL = 2; //取消請(qǐng)求
public static final int REQUEST_GCED = 3; //請(qǐng)求被回收
public static final int HUNTER_COMPLETE = 4; //圖片獲取完成
public static final int HUNTER_RETRY = 5; //重試圖片獲取
public static final int HUNTER_DECODE_FAILED = 6; //圖片解碼失敗
public static final int HUNTER_DELAY_NEXT_BATCH = 7;
public static final int HUNTER_BATCH_COMPLETE = 8; //圖片批量獲取成功
public static final int NETWORK_STATE_CHANGE = 9; //網(wǎng)絡(luò)狀態(tài)改變
public static final int AIRPLANE_MODE_CHANGE = 10; //飛行模式改變
public static final int TAG_PAUSE = 11;
public static final int TAG_RESUME = 12;
public static final int REQUEST_BATCH_RESUME = 13;
上圖中對(duì)大多數(shù)操作的功能做了注釋。確定好功能后,就可以創(chuàng)建 Handler 了,它負(fù)責(zé)接收不同線程發(fā)出的請(qǐng)求。
Dispatcher 的內(nèi)部類(lèi) DispatcherHandler 是在子線程中執(zhí)行的 Handler:
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 REQUEST_CANCEL: {
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: {
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: {
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
//...
}
}
}
然后在 Dispatcher 中創(chuàng)建接受請(qǐng)求的方法:
public void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
public void dispatchCancel(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}
public void dispatchPauseTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}
public void dispatchResumeTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
}
最后就是創(chuàng)建處理請(qǐng)求的方法了,比如提交圖片獲取操作的方法:
public void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
return;
}
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
return;
}
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
}
具體一些方法如何實(shí)現(xiàn)的,我們后面再看。這里了解調(diào)度器的基本信息,掌握如何寫(xiě)一個(gè)調(diào)度器的流程即可
最核心的 圖片獵手 BitmapHunter
前面介紹了那么多 API,它們基本是各自實(shí)現(xiàn)一個(gè)單獨(dú)的模塊功能,Picasso 中的 BitmapHunter 是把這些組合起來(lái),具體實(shí)現(xiàn)圖片的獲取、解碼、轉(zhuǎn)換操作的類(lèi)。
public class BitmapHunter implements Runnable {
//解碼 bitmap 使用的全局鎖,確保一次只解碼一個(gè),避免內(nèi)存溢出
private static final Object DECODE_LOCK = new Object();
private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
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; //要執(zhí)行的操作列表
Bitmap result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifOrientation; // Determined during decoding of original resource.
int retryCount;
Priority priority;
}
可以看到, BitmapHunter 的成員變量有我們前面介紹的那些關(guān)鍵類(lèi)。同時(shí)它實(shí)現(xiàn)了 Runnable 接口,在 run() 方法中執(zhí)行耗時(shí)任務(wù):
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
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)); //保存內(nèi)存、緩存信息
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() 方法非常簡(jiǎn)單,調(diào)用 hunt() 方法后就是一長(zhǎng)串異常捕獲和調(diào)度,這里可以看出自定義異常的重要性,在復(fù)雜的 IO、網(wǎng)絡(luò)操作中,有很多產(chǎn)生異常的可能,在不同操作里拋出不同類(lèi)型的異常,有助于最后的排查、處理。
我們來(lái)看看完成主要任務(wù)的 hunt() 方法:
public Bitmap hunt() throws IOException {
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(memoryPolicy)) { //1.根據(jù)請(qǐng)求的緩存策略,判斷是否要讀取緩存
bitmap = cache.get(key);
if (bitmap != null) { //緩存中拿到就直接返回
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
//2.調(diào)用適當(dāng)?shù)?requestHandler 來(lái)處理圖片加載請(qǐng)求
networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) { //加載成功
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
//拿到圖片加載結(jié)果時(shí),有可能這個(gè)數(shù)據(jù)還沒(méi)有解碼,因此需要進(jìn)行解碼
if (bitmap == null) {
Source source = result.getSource();
try {
bitmap = decodeStream(source, data); //解碼操作
} finally {
try {
source.close();
} catch (IOException ignored) {
}
}
}
}
//3.拿到圖片加載結(jié)果后有解碼好的 bitmap,進(jìn)入下一步,轉(zhuǎn)換
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) { //進(jìn)行自定義的轉(zhuǎn)換
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
可以看到,BitmapHunter 中獲取圖片的 hunt() 方法的邏輯如下:
如果緩存策略允許去內(nèi)存緩存讀取,就去緩存里找,找到就返回
否則調(diào)用適當(dāng)?shù)?RequestHandler 去處理圖片加載請(qǐng)求
如果 RequestHandler 加載成功但是這個(gè)圖片數(shù)據(jù)還沒(méi)有解碼,就去解碼
拿到解碼后的圖片就進(jìn)入下一步,轉(zhuǎn)換
轉(zhuǎn)換有 Picasso 支持的轉(zhuǎn)換(比如裁剪什么的),也有自定義的
最后返回轉(zhuǎn)換后的圖片
整體類(lèi):

流程圖:
