具體點(diǎn)說屿讽,圖片顯示到界面上這個過程中可能會遇到這些情況:
加載的圖片可能有網(wǎng)絡(luò)、本地等多種來源贿讹;
如果是網(wǎng)絡(luò)的話渐逃,就得先下載下來;
下載過程中可能需要暫停民褂、恢復(fù)或者取消茄菊;
下載后需要解碼、對圖片進(jìn)行一些額外操作(比如裁剪赊堪、轉(zhuǎn)變等)面殖;
最好還有個緩存系統(tǒng),避免每次都去網(wǎng)絡(luò)請求哭廉;
為了實(shí)現(xiàn)性能監(jiān)控脊僚,最好再有個數(shù)據(jù)統(tǒng)計(jì)功能…
有了以上需求,根據(jù)職責(zé)分離的原則遵绰,我們可以定義一些核心類來完成上述功能:
請求信息類:其中包含了所有可以配置的選項(xiàng)辽幌,比如圖片地址、要進(jìn)行的操作等
圖片獲取類:根據(jù)不同的來源去不同地方獲取椿访,比如網(wǎng)絡(luò)乌企、本地、內(nèi)存等
調(diào)度器類:實(shí)現(xiàn)圖片獲取的入隊(duì)成玫、執(zhí)行加酵、完成、取消哭当、暫停等
圖片處理類:圖片拿到后進(jìn)行解碼猪腕、反轉(zhuǎn)、裁剪等
緩存類:圖片的內(nèi)存荣病、磁盤緩存控制
監(jiān)控類:統(tǒng)計(jì)核心數(shù)據(jù)码撰,比如當(dāng)前內(nèi)存、磁盤緩存的大小个盆、某個圖片的加載時間等
認(rèn)識核心 API
我給 Picasso 文件夾結(jié)構(gòu)進(jìn)行了調(diào)整脖岛,變成了這樣:
主要分為幾個關(guān)鍵部分:
request 文件夾中的:請求信息相關(guān)的類
action 文件夾中的:加載行為相關(guān)的類
handler 文件夾中的:圖片獲取具體處理的類
Dispatcher:調(diào)度器
BitmapHunter:耗時任務(wù)執(zhí)行者
Picasso:暴露給用戶的類
請求信息相關(guān)的類
上圖中的 request 文件夾里放的是 Picasso 中構(gòu)建圖片請求信息相關(guān)的類朵栖,總共有五個,我們來分別了解下它們柴梆。
首先看 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 中放的是一個圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息绍在、圖片配置信息以及優(yōu)先級等门扇。
這里我們可以學(xué)習(xí)到的是:如果一個請求參數(shù)很多,我們最好用一個類給它封裝起來偿渡,避免在傳遞時傳遞多個參數(shù)臼寄;如果經(jīng)常使用的話,還可以創(chuàng)建一個對象池溜宽,節(jié)省開銷吉拳。
接著看第二個類 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è)置占位圖、是否有漸變動畫嫉嘀、是否延遲處理炼邀、以及占位圖錯誤圖資源 ID、內(nèi)存使用策略剪侮、網(wǎng)絡(luò)請求策略等拭宁。
RequestCreator 是相當(dāng)重要的一個類,我們后面會進(jìn)一步介紹它票彪。
接著看第三個類 DeferredRequestCreator:
public class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
private final RequestCreator creator;
public @VisibleForTesting final WeakReference<ImageView> target;
@VisibleForTesting
public Callback callback;
//...
}
可以看到红淡, DeferredRequestCreator 中引用了 RequestCreator,此外還有一個要加載的 ImageView 弱引用對象降铸,還有一個 Callback,它實(shí)現(xiàn)了 OnPreDrawListener 和 onAttachStateChangeListener 接口摇零,這兩個接口的作用如下:
OnPreDrawListener:當(dāng)布局樹將要繪制前推掸,會回調(diào)這個借口的 onPreDraw() 方法
onAttachStateChangeListener:當(dāng)布局綁定到一個 window 或者解除綁定和一個 window 時會調(diào)用
DeferredRequestCreator 中比較重要的就是這個 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 一樣大時(即調(diào)用 RequestCreator.fit() 方法),會使用到 DeferredRequestCreator驻仅。
剩下的兩個枚舉 MemoryPolicy 和 NetworkPolicy 就簡單多了谅畅。
MemoryPolicy 指定了兩種內(nèi)存緩存策略:不去內(nèi)存緩存里查找和不寫入內(nèi)存緩存。
public enum MemoryPolicy {
//當(dāng)請求圖片時不去內(nèi)存緩存里找
NO_CACHE(1 << 0),
//拿到圖片后不寫到內(nèi)存緩存里噪服,一般用于一次性請求
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ò)請求策略:
NO_CACHE: 跳過檢查磁盤緩存毡泻,強(qiáng)制請求網(wǎng)絡(luò)
NO_STORE: 拿到結(jié)果不寫入磁盤緩存中
OFFLINE: 不請求網(wǎng)絡(luò),只能去磁盤緩存里查找
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)于請求信息的五個類粘优,小結(jié)一下仇味,它們的作用如下:
Request:保存一個圖片的本地信息呻顽、要進(jìn)行的轉(zhuǎn)換操作信息、圖片配置信息以及優(yōu)先級
RequestCreator:保存了一個圖片加載請求的完整信息丹墨,包括圖片信息廊遍、是否設(shè)置占位圖、是否有漸變動畫贩挣、是否延遲處理喉前、以及占位圖錯誤圖資源 ID、內(nèi)存使用策略王财、網(wǎng)絡(luò)請求策略等
MemoryPolicy:定義了加載圖片時的兩種圖片緩存策略
NetworkPolicy:定義了加載圖片時的三種網(wǎng)絡(luò)請求策略
加載行為相關(guān)的類
了解完請求信息相關(guān)的類后卵迂,我們再看看 action 文件夾下,關(guān)于加載行為的類(這里的 “加載行為” 是我臨時起的名绒净,可能不是很容易理解狭握,稍后我再解釋一下)。
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 的成員變量里包含了一個圖片的請求信息和加載策略论颅、錯誤占位圖,同時定義了兩個抽象方法囱嫩,這兩個方法的作用是當(dāng)圖片加載成功后會調(diào)用 complete()(參數(shù)是拿到的圖片和加載來源)恃疯,加載失敗后會調(diào)用 eror(),子類繼承后可以實(shí)現(xiàn)自己特定的操作墨闲。
前面提到這些 action 表示的是加載行為,所謂“加載行為”簡單點(diǎn)說就是“拿到圖片要干啥”
發(fā)起一個圖片加載請求的目的可能有多種鸳碧,最常見的就是加載到圖片上盾鳞,對應(yīng) Picasso 里的 ImageViewAction(加載完成時它會把圖片設(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();
}
}
//失敗時給 ImageView 設(shè)置錯誤圖片
@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 還提供了四種其他用途的加載行為類,源碼比較容易理解,這里就直接貼出作用:
FetchAction: 拿到圖片后會有個回調(diào),除此外不會將圖片顯示到界面上
Picasso.fetch() 方法會使用到它,這個方法在后臺線程異步加載圖片伦乔,只會將圖片保存到硬盤或者內(nèi)存上高帖,不會顯示到界面上儒将。如果你不久之后就用這個圖片刚操,或者想要減少加載時間,你可以提前將圖片下載緩存起來祝闻。
GetAction:拿到圖片后不會有任何操作占卧,不知道干啥的
Picasso.get() 方法會使用到它,這個方法會同步加載圖片并返回 Bitmap 對象联喘,請確保你沒有在Ui線程里面使用.get() 方法华蜒。這將會阻塞UI!
RemoteViewsAction: 拿到圖片后設(shè)置給 RemoteView豁遭,有兩個實(shí)現(xiàn)類 AppWidgetAction 和 NotificationAction叭喜,分別對應(yīng)桌面插件和提醒欄
TargetAction:首先 Target 是 Picasso 中定義的一個接口,表示對圖片加載的監(jiān)聽蓖谢;TargetAction 在拿到圖片后會調(diào)用 Target 接口的方法
接著介紹 handler 文件夾下的類捂蕴,這個文件夾中類的功能就是:處理去不同渠道加載圖片的請求。
其中 RequestHandler 是基類闪幽,我們先來看看它啥辨。
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 代碼也比較簡單,除了幾個計(jì)算圖片尺寸的方法外盯腌,最關(guān)鍵的就是上述的兩個抽象方法:
boolean canHandleRequest(Request data) 表示當(dāng)前獲取類能否處理這個請求溉知,一般子類會根據(jù)請求的 URI 來判斷
Result load(Request request, int networkPolicy) 表示根據(jù)網(wǎng)絡(luò)策略加載某個請求,返回加載結(jié)果
加載結(jié)果 Result 也比較簡單:
public static final class Result {
private final Picasso.LoadedFrom loadedFrom; //從哪兒加載的(網(wǎng)絡(luò)、內(nèi)存级乍、磁盤)
private final Bitmap bitmap;
private final Source source; //okio 中定義的數(shù)據(jù)流類
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 的子類實(shí)現(xiàn)都比較簡單舌劳,這里我們就選常見的處理網(wǎng)絡(luò)和文件請求的獲取類來看看。
從名字就可以看出的從網(wǎng)絡(luò)獲取圖片處理類 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ù)請求 uri 的 scheme 判斷是否為 http/https 請求
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
//去網(wǎng)絡(luò)加載一個圖片
@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 來完成下載的功能甚淡,其中的下載器 downloader 就是通過構(gòu)造一個 okhttp.Call 來完成同步下載文件:
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
從文件獲取圖片的請求處理類 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);
}
}
也很簡單是吧,根據(jù) URI 獲取輸入流通過 ContentResolver.openInputStream( Uri uri) 可以實(shí)現(xiàn)捅厂,這個可以記一下以后可能會用到贯卦,拿到 IO 流后,接下來的的操作直接通過 Okio 實(shí)現(xiàn)了恒傻。
通過這幾個圖片請求處理類我們可以看到 Picasso 的設(shè)計(jì)多么精巧脸侥,每個類即精簡又功能獨(dú)立,我們在開發(fā)時最好可以參考這樣的代碼盈厘,先定義接口和基類睁枕,然后再考慮不同的實(shí)現(xiàn)。
分析完這些“大家族”后沸手,剩下的就是一些單獨(dú)的類了外遇。
調(diào)度器 Dispatcher
調(diào)度器的角色在許多框架里可以看到,實(shí)際上在稍微復(fù)雜一點(diǎn)的業(yè)務(wù)邏輯契吉,都需要這么一個調(diào)度器類跳仿,它負(fù)責(zé)業(yè)務(wù)邏輯在不同線程的切換、執(zhí)行捐晶、取消菲语。
我們來看看 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; //后面介紹山上,獲取圖片最核心的類
final NetworkBroadcastReceiver receiver;
final boolean scansNetworkChanges;
boolean airplaneMode;
}
可以看到,Dispatcher 的成員變量有 HandlerThread英支,兩個 Handler佩憾、線程池,下載器干花、BitmapHunter(我叫它“圖片獵手”妄帘,后面介紹它)、緩存池凄、數(shù)據(jù)統(tǒng)計(jì)等等抡驼。
從 Picasso 的 Dispatcher 中,我們可以學(xué)到如何創(chuàng)建一個復(fù)雜業(yè)務(wù)的調(diào)度器肿仑。
復(fù)雜業(yè)務(wù)往往需要在子線程中進(jìn)行婶恼,于是需要用到線程池桑阶;線程之間切換需要用到 Handler柏副,為了省去創(chuàng)建 Looper 的功夫勾邦,就需要使用 HandlerThread;此外還需要持有幾個列表割择、Map眷篇,來保存操作數(shù)據(jù)。
作為調(diào)度器荔泳,最重要的功能就是給外界提供各種功能的接口蕉饼,一般我們都使用不同的常量來標(biāo)識不同的邏輯,在開始寫業(yè)務(wù)之前玛歌,最好先定好功能昧港、確定常量。
我們來看看 Dispatcher 中定義的常量都代表著什么功能:
private static final int RETRY_DELAY = 500; //重試的延遲時間
private static final int AIRPLANE_MODE_ON = 1;
private static final int AIRPLANE_MODE_OFF = 0;
public static final int REQUEST_SUBMIT = 1; //提交請求
public static final int REQUEST_CANCEL = 2; //取消請求
public static final int REQUEST_GCED = 3; //請求被回收
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;
上圖中對大多數(shù)操作的功能做了注釋支子。確定好功能后创肥,就可以創(chuàng)建 Handler 了,它負(fù)責(zé)接收不同線程發(fā)出的請求值朋。
Dispatcher 的內(nè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)建接受請求的方法:
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)建處理請求的方法了叹侄,比如提交圖片獲取操作的方法:
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)度器的基本信息趾代,掌握如何寫一個調(diào)度器的流程即可
最核心的 圖片獵手 BitmapHunter
前面介紹了那么多 API,它們基本是各自實(shí)現(xiàn)一個單獨(dú)的模塊功能丰辣,Picasso 中的 BitmapHunter 是把這些組合起來撒强,具體實(shí)現(xiàn)圖片的獲取、解碼笙什、轉(zhuǎn)換操作的類飘哨。
public class BitmapHunter implements Runnable {
//解碼 bitmap 使用的全局鎖,確保一次只解碼一個得湘,避免內(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)鍵類。同時它實(shí)現(xiàn)了 Runnable 接口淘正,在 run() 方法中執(zhí)行耗時任務(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() 方法非常簡單,調(diào)用 hunt() 方法后就是一長串異常捕獲和調(diào)度鸿吆,這里可以看出自定義異常的重要性囤采,在復(fù)雜的 IO、網(wǎng)絡(luò)操作中惩淳,有很多產(chǎn)生異常的可能蕉毯,在不同操作里拋出不同類型的異常乓搬,有助于最后的排查、處理代虾。
我們來看看完成主要任務(wù)的 hunt() 方法:
public Bitmap hunt() throws IOException {
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(memoryPolicy)) { //1.根據(jù)請求的緩存策略进肯,判斷是否要讀取緩存
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 來處理圖片加載請求
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ù)據(jù)還沒有解碼棉磨,因此需要進(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 去處理圖片加載請求
如果 RequestHandler 加載成功但是這個圖片數(shù)據(jù)還沒有解碼衙傀,就去解碼
拿到解碼后的圖片就進(jìn)入下一步抬吟,轉(zhuǎn)換
轉(zhuǎn)換有 Picasso 支持的轉(zhuǎn)換(比如裁剪什么的),也有自定義的
最后返回轉(zhuǎn)換后的圖片
整體類:
流程圖: