Picasso 是 Square 公司出品的一款十分優(yōu)秀的開(kāi)源圖片框架,也是目前 Android 開(kāi)發(fā)中十分流行的一款圖片加載框架。提到 Square 公司大家一定不會(huì)陌生,OkHttp、Retrofit、LeakCanary 等等 Android 開(kāi)發(fā)者十分熟悉的開(kāi)源庫(kù)都出自他們之手,個(gè)人認(rèn)為他們公司的開(kāi)源庫(kù)都十分值得研究,今天就讓我們來(lái)研究一下 Picasso 這款圖片加載框架。
Picasso 屬于三大圖片框架(Glide、Picasso、Fresco)之一。相比其他兩個(gè)框架,它的特點(diǎn)是輕量,占用的體積更少,同時(shí)功能相對(duì)來(lái)說(shuō)也比較完善。那么今天就來(lái)跟我一起分析一波 Picasso 這個(gè)圖片選擇框架的源碼。
此篇文章的源碼解析基于 2.71828 版本。
初始化
以我的閱讀源碼的習(xí)慣,都是從使用的時(shí)候的入口開(kāi)始入手,因此我們這里從 Picasso 類(lèi)入手。舊版本的 Picasso 使用了 with 方法作為入口,而在新版本中 with 方法則被 get 方法所替代,并且不再需要傳入 Context 參數(shù)。那么它是如何實(shí)現(xiàn)的呢?下面我們看到它的 get 方法:
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;
}
可以看到,這里是一個(gè)單例類(lèi),而它的 Context 則由一個(gè)沒(méi)有任何實(shí)現(xiàn)的 PicassoProvider 這個(gè) ContentProvider 來(lái)提供,從而使用戶不再需要傳入一個(gè) Context。
@RestrictTo(LIBRARY)
public final class PicassoProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak") static Context context;
@Override public boolean onCreate() {
context = getContext();
return true;
}
// ...省略 ContentProvider 的默認(rèn)實(shí)現(xiàn)
}
之后,它調(diào)用了 Builder 的 build 方法返回了一個(gè) Picasso 對(duì)象。我們先看到 Builder 的構(gòu)造方法:
public Builder(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
可以看到僅僅是判空并賦值。接著我們看看 build 方法:
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = new OkHttp3Downloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
build 方法中對(duì) downloader、cache 等變量進(jìn)行了初始化,同時(shí)返回了一個(gè)新的 Picasso 對(duì)象,前面的變量我們先不關(guān)心。先看到 Picasso 的構(gòu)造方法:
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;
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
// ResourceRequestHandler needs to be the first in the list to avoid
// forcing other RequestHandlers to perform null checks on request.uri
// to cover the (request.resourceId != 0) case.
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);
}
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
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();
}
可以看到,主要是對(duì) requestHandlers 這個(gè) List 進(jìn)行初始化以及各個(gè)變量進(jìn)行初始化。通過(guò)上面的幾個(gè)名字可以看出來(lái) RequestHandler 就是 Picasso 對(duì)各種類(lèi)型的圖片加載的抽象。通過(guò)實(shí)現(xiàn) RequestHandler 接口可以實(shí)現(xiàn)不同的圖片加載策略。
創(chuàng)建請(qǐng)求
之后我們調(diào)用了 load 方法并傳入了具體的參數(shù)。它有許多重載,可以傳入 Uri、String、File、resourceId 等等類(lèi)型的數(shù)據(jù)。
我們以 load(String) 為例:
public RequestCreator load(@Nullable String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
可以看到,它最終調(diào)用的還是 load(Uri) 方法。其實(shí)所有的其他重載最后都會(huì)指向 load(Uri) 方法,也就是說(shuō)我們-各種形式的數(shù)據(jù)源最后都是以 Uri 的形式存在于 Picasso 中。我們下面看到 load(Uri):
public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}
它構(gòu)造了一個(gè) RequestCreator 并返回。接下來(lái)我們看到 RequestCreator 的構(gòu)造方法:
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
它調(diào)用了 Request.Builder 的構(gòu)造方法來(lái)為 data 進(jìn)行賦值,我們看到這個(gè)構(gòu)造方法:
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
this.uri = uri;
this.resourceId = resourceId;
this.config = bitmapConfig;
}
可以看到,這里主要是對(duì) Bitmap.Config 等屬性進(jìn)行設(shè)置。
配置加載屬性
在我們創(chuàng)建了 RequestCreator 后,可以調(diào)用它的 placeholder、error 等等方法為本次加載設(shè)置占位圖、錯(cuò)誤圖等等各種屬性的設(shè)置,下面我們以 placeholder(int) 方法舉例:
public RequestCreator placeholder(@DrawableRes int placeholderResId) {
if (!setPlaceholder) {
throw new IllegalStateException("Already explicitly declared as no placeholder.");
}
if (placeholderResId == 0) {
throw new IllegalArgumentException("Placeholder image resource invalid.");
}
if (placeholderDrawable != null) {
throw new IllegalStateException("Placeholder image already set.");
}
this.placeholderResId = placeholderResId;
return this;
}
其實(shí)這里就是為 RequestCreator 中的這些屬性賦值。
那么所有通過(guò) RequestCreator 設(shè)定的屬性都是放在 RequestCreator 這個(gè)類(lèi)中的么?
其實(shí)不是的,與加載過(guò)程有關(guān)的屬性是放在 RequestCreator 中的,而與圖片相關(guān)的屬性則是放在 Request.Builder 中。
可能看到這里有點(diǎn)亂,大概解釋一下。
比如 placeholder、error、memoryPolicy 這些屬性就是與加載過(guò)程有關(guān)而與圖片無(wú)關(guān)的
而比如 resize、centerCrop 這些就是與圖片的顯示效果有關(guān)的屬性,也就是圖片相關(guān)屬性。
我們以 resize 為例來(lái)看看整體流程:
public RequestCreator resize(int targetWidth, int targetHeight) {
data.resize(targetWidth, targetHeight);
return this;
}
我們看到 Request.Builder 中的 resize 方法:
public Builder resize(@Px int targetWidth, @Px int targetHeight) {
if (targetWidth < 0) {
throw new IllegalArgumentException("Width must be positive number or 0.");
}
if (targetHeight < 0) {
throw new IllegalArgumentException("Height must be positive number or 0.");
}
if (targetHeight == 0 && targetWidth == 0) {
throw new IllegalArgumentException("At least one dimension has to be positive number.");
}
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
return this;
}
這里就是將 Request.Builder 中的一些屬性進(jìn)行了賦值。
加載圖片
當(dāng)屬性都設(shè)定完后,我們便可以調(diào)用 into 方法來(lái)加載圖片,我們看到 into(ImageView):
public void into(ImageView target) {
into(target, null);
}
它調(diào)用了 into(ImageView, Callback):
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
// 1
// 檢查是否在主線程
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (!data.hasImage()) {
// 之前設(shè)置的 uri 是否有數(shù)據(jù)(實(shí)際上也是判空)
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
// 2
if (deferred) {
// 是否自適應(yīng) Target 寬高
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);
}
// 3
Request request = createRequest(started);
String requestKey = createKey(request);
// 4
if (shouldReadFromMemoryCache(memoryPolicy)) {
// 從內(nèi)存緩存中獲取圖片
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
// 找到緩存的圖片
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;
}
}
// 5
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 6
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
這里代碼比較長(zhǎng),我們慢慢分析,先看看整體大體流程。
首先在注釋 1 處進(jìn)行了一系列判斷操作,具體可看注釋
之后在注釋 2 處,是 fit() 的具體實(shí)現(xiàn)。如果外部調(diào)用了 fit 使圖片自適應(yīng) target 的大小,則獲取 target 的大小并調(diào)用 resize 方法進(jìn)行設(shè)置。這里要特別注意的是如果寬高為 0 則說(shuō)明 ImageView 的尺寸還沒(méi)有獲取到,此時(shí)會(huì)延時(shí)該圖片請(qǐng)求直到獲取到 ImageView 的寬高。
之后 3 處構(gòu)建了一個(gè) Request,并調(diào)用 createKey 方法由該 Request 及其各種信息構(gòu)建了一個(gè) String 類(lèi)型的 key。
之后在注釋 4 處,在使用內(nèi)存緩存策略的情況下,先調(diào)用 quickMemoryCacheCheck 方法獲取到了內(nèi)存緩存中的 BitMap,如果找到則調(diào)用 setBitmap 方法將圖片應(yīng)用到 target 中。
然后在注釋 5 處,如果內(nèi)存沒(méi)有緩存,且設(shè)置了占位圖,則給它添加占位圖。
最后在注釋 6 處,構(gòu)造了一個(gè) Action 對(duì)象然后調(diào)用了 picasso 的 enqueueAndSubmit 進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
Request 的創(chuàng)建
首先,我們看看 Request 是如何創(chuàng)建的,看到 createRequest 方法:
private Request createRequest(long started) {
int id = nextId.getAndIncrement();
// 1
Request request = data.build();
request.id = id;
request.started = started;
boolean loggingEnabled = picasso.loggingEnabled;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
}
// 2
Request transformed = picasso.transformRequest(request);
if (transformed != request) {
// 3
// If the request was changed, copy over the id and timestamp from the orig
transformed.id = id;
transformed.started = started;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed)
}
}
return transformed;
}
可以看到,這里首先在注釋 1 處調(diào)用了 Request.Builder 的 build 方法創(chuàng)建了 Request,之后在注釋 2 處調(diào)用了 picasso 的 transformRequest 方法對(duì) Request 進(jìn)行轉(zhuǎn)換。
獲取 Request
我們先看看 Request.Builder 的 build 方法:
public Request build() {
if (centerInside && centerCrop) {
throw new IllegalStateException("Center crop and center inside can not be used together.");
}
if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center inside requires calling resize with positive width and height.");
}
if (priority == null) {
priority = Priority.NORMAL;
}
return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
}
這里就是創(chuàng)建 Request 對(duì)象并將各種 Request.Builder 中的屬性傳遞給這個(gè) Request 對(duì)象。
轉(zhuǎn)換 Request
然后我們?cè)倏纯?picasso 的 transformRequest 方法:
Request transformRequest(Request request) {
Request transformed = requestTransformer.transformRequest(request);
if (transformed == null) {
throw new IllegalStateException("Request transformer "
+ requestTransformer.getClass().getCanonicalName()
+ " returned null for "
+ request);
}
return transformed;
}
這里調(diào)用了 requestTransformer 的 transformRequest 方法來(lái)進(jìn)行轉(zhuǎn)換。而這個(gè) requestTrasformer 則是之前在 Picasso.Builder 中的 build 方法中初始化給 transformer 的 RequestTransformer.IDENTITY:
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
我們看看它的 transformRequest 的實(shí)現(xiàn):
RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
可以看到這里是返回了原始的 Request。
既然都是返回默認(rèn) Request,為什么 Picasso 還要在創(chuàng)建的時(shí)候添加這一步 transform 的過(guò)程呢?
其實(shí)這個(gè) transformer 我們是可以通過(guò) Builder 的 requestTransformer 方法來(lái)進(jìn)行設(shè)置的。也就是說(shuō)這里主要是提供給用戶對(duì) Request 進(jìn)行一些特殊處理的渠道,使得我們可以對(duì)圖片加載的過(guò)程進(jìn)行一定的擴(kuò)展與定制。這種設(shè)計(jì)是值得我們?nèi)W(xué)習(xí)的。
之后我們回到 Request 創(chuàng)建的部分,可以看到這里如果對(duì) Request 進(jìn)行了修改,在注釋 3 處會(huì)將原 Request 的 id 和 started 賦值過(guò)去,從而防止用戶對(duì)它們進(jìn)行修改。
key 的生成
我們?cè)賮?lái)看看 Request 的 key 是如何生成的:
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) {
//noinspection ForLoopReplaceableByForEach
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();
}
其實(shí)這里就是用一個(gè) StringBuilder 構(gòu)造了一個(gè) String,將 Request 中的各類(lèi)信息都存放于 key 中。這個(gè) key 其實(shí)就是用于內(nèi)存緩存中的 key。
圖片的加載
我們先不去查看內(nèi)存緩存的部分,留到后面來(lái)講解,我們先看看圖片是如何從網(wǎng)絡(luò)加載的。先看到 into 方法的下面這兩句:
Action action =
new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);
picasso.submit(action);
構(gòu)造 Action
我們先看到 FetchAction 的構(gòu)造方法:
FetchAction(Picasso picasso, Request data, int memoryPolicy, int networkPolicy, Object tag,
String key, Callback callback) {
super(picasso, null, data, memoryPolicy, networkPolicy, 0, null, key, tag, false);
this.target = new Object();
this.callback = callback;
}
調(diào)用了父類(lèi)的構(gòu)造方法:
Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
this.picasso = picasso;
this.request = request;
this.target =
target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
this.memoryPolicy = memoryPolicy;
this.networkPolicy = networkPolicy;
this.noFade = noFade;
this.errorResId = errorResId;
this.errorDrawable = errorDrawable;
this.key = key;
this.tag = (tag != null ? tag : this);
}
可以看出來(lái),Action 類(lèi)實(shí)際上就是一個(gè)攜帶了需要的信息的類(lèi)。
分發(fā) Action
接著,調(diào)用了 picasso 的 submit 方法:
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
這里調(diào)用了 dispatcher 的 dispatchSubmit 方法:
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
這里用到了一個(gè) DispatcherHandler 類(lèi)的對(duì)象調(diào)用 sendMessage 方法發(fā)送一條信息。這里的 DispatcherHandler 的作用主要是根據(jù)不同的調(diào)用將 Action 分發(fā)到不同的方法中。
下面我們看到 DispatcherHandler 的實(shí)現(xiàn),它是 Dispatcher 的一個(gè)內(nèi)部類(lèi):
@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;
}
case HUNTER_RETRY: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter, false);
break;
}
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}
case NETWORK_STATE_CHANGE: {
NetworkInfo info = (NetworkInfo) msg.obj;
dispatcher.performNetworkStateChange(info);
break;
}
case AIRPLANE_MODE_CHANGE: {
dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
這里根據(jù)不同的 Message 調(diào)用了不同的方法,我們的 submit 方法調(diào)用了 Dispatcher 中的 performSubmit 方法:
void performSubmit(Action action) {
performSubmit(action, true);
}
它調(diào)用了 performSubmit(Action, boolean) 方法:
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
// 1
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
// 2
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down")
}
return;
}
// 3
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
首先,在注釋 1 處根據(jù) Action 獲取到了其對(duì)應(yīng)的 BitmapHunter。
之后在注釋 2 處檢查 service 是否被殺掉。
然后在注釋 3 處,調(diào)用了 forRequest 獲取到了 Action 對(duì)應(yīng)的 BitmapHunter,然后調(diào)用了 service 的 submit 方法。
之后將該 action 與 BitmapHunter 放入了 hunterMap 中。
BitmapHunter 的獲取
我們看一下前面的步驟中 BitmapHunter 是如何獲取的,來(lái)到 forRequest方法:
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
// Index-based loop to avoid allocating an iterator.
//noinspection ForLoopReplaceableByForEach
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);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
這里主要是依次遍歷各個(gè) RequestHandler,找到可以處理該類(lèi) Request 的 Handler,并構(gòu)建 BitmapHunter。
我們先看看 RequestHunter 是如何判斷能否處理該類(lèi) Request 的,我們以 NetworkRequestHandler 舉例:
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
可以看到,它是通過(guò)判斷 uri 的 scheme 來(lái)判斷能否處理該類(lèi) Request 的。
我們接著看到 BitmapHunter 的構(gòu)造函數(shù):
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();
}
可以看到,這里主要是各種變量的賦值。
接著我們看到 service 的 submit 方法,這里的 service 是 PicassoExecutorService:
@Override
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
這里構(gòu)建了一個(gè) PicassoFutureTask,然后調(diào)用了 execute 方法
我們先看看 PicassoFutureTask 的構(gòu)造方法:
PicassoFutureTask(BitmapHunter hunter) {
super(hunter, null);
this.hunter = hunter;
}
PicassoFutureTask 是 FutureTask 的子類(lèi),這里主要是變量的賦值。
圖片資源的獲取
接著我們看到 execute 方法,這里其實(shí)是調(diào)用了 Java 自帶的 ThreadPoolExecutor 的 execute 方法。同時(shí)這里也說(shuō)明了這里是一個(gè)異步的過(guò)程。
其實(shí) BitmapHunter 是一個(gè) Runnable,當(dāng)調(diào)用了 execute 方法后便會(huì)執(zhí)行它的 run 方法。我們可以看到它的 run 方法:
@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
}
這里調(diào)用了 hunt 方法獲取到了結(jié)果 Bitmap,同時(shí)在后面根據(jù)不同的結(jié)果通過(guò) dispatcher 進(jìn)行結(jié)果的處理:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
// 1
if (shouldReadFromMemoryCache(memoryPolicy)) {
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
networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
// 3
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
// 4
if (bitmap == null) {
Source source = result.getSource();
try {
bitmap = decodeStream(source, data);
} finally {
try {
//noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
source.close();
} catch (IOException ignored) {
}
}
}
}
// 5
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
// 6
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
// 7
if (data.hasCustomTransformations()) {
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;
}
這里代碼很長(zhǎng),我們慢慢分析:
首先在注釋 1 處嘗試從內(nèi)存通過(guò) key 獲取對(duì)應(yīng) bitmap,若獲取到則直接返回。
之后在注釋 2 處,根據(jù) requestHandler 中的 retryCount 來(lái)判斷是否是網(wǎng)絡(luò)請(qǐng)求,從而獲取不同的 networkPolicy。若 retryCount 為 0 則為離線策略。
之后在注釋 3 處,通過(guò) requestHandler 的 load 方法進(jìn)行數(shù)據(jù)的加載,若數(shù)據(jù)加載成功則進(jìn)行一些變量的賦值,并獲取 bitmap。
若 bitmap 為空則說(shuō)明我們需要在注釋4處將其從流中 decode 出來(lái)。
之后在注釋 5 處就是 Picasso 的加載過(guò)程中支持用戶對(duì)圖片進(jìn)行定制后再應(yīng)用的具體實(shí)現(xiàn)了。這里首先判斷是否需要 transform。
在注釋 6 處判斷如果需要進(jìn)行矩陣變換(旋轉(zhuǎn),放大縮小等),則調(diào)用 transformResult 方法進(jìn)行變換。
在注釋 7 處判斷如果有自定義變換,則調(diào)用 applyCustomTransformations 進(jìn)行自定義變換。
這里的自定義變換比較類(lèi)似前面的自定義 Request 轉(zhuǎn)換,用戶可以在外部自定義 Transformation,并通過(guò) RequestCreator 的 transform 方法傳入,這樣就可以在圖片應(yīng)用前對(duì) Bitmap 進(jìn)行一些自定義 (如高斯模糊等)后再應(yīng)用于 target。這種設(shè)計(jì)是我們值得學(xué)習(xí)的。
RequestHandler 的實(shí)現(xiàn)
下面我們以網(wǎng)絡(luò)圖片對(duì)應(yīng)的 NetworkRequestHandler 為例看看它們的實(shí)現(xiàn),其他的子類(lèi)可以自己去了解。讓我們看到它的 load 方法:
@Override public Result load(Request request, int networkPolicy) throws IOException {
// 1
okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
// 2
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.");
}
if (loadedFrom == NETWORK && body.contentLength() > 0) {
stats.dispatchDownloadFinished(body.contentLength());
}
return new Result(body.source(), loadedFrom);
}
可以看到,這里是通過(guò) OkHttp3 來(lái)實(shí)現(xiàn)的圖片的加載。
首先調(diào)用 createRequest 方法創(chuàng)建了 OkHttp 的 Request。然后通過(guò)自己實(shí)現(xiàn)的 OkHttp3Downloader 的 load 方法來(lái)實(shí)現(xiàn)對(duì)這個(gè) Request 的下載請(qǐng)求。
之后根據(jù)緩存的相應(yīng)是否是空判斷數(shù)據(jù)的來(lái)源是從本地還是網(wǎng)絡(luò)。
最終構(gòu)造了一個(gè) Result 并返回。
OkHttp3.Request 的 創(chuàng)建
我們先看看如何將 Request 轉(zhuǎn)換為 OkHttp3.Request。讓我們看到 createRequest 方法:
private static okhttp3.Request createRequest(Request request, int networkPolicy) {
CacheControl cacheControl = null;
if (networkPolicy != 0) {
if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
cacheControl = CacheControl.FORCE_CACHE;
} else {
CacheControl.Builder builder = new CacheControl.Builder();
if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
builder.noCache();
}
if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
builder.noStore();
}
cacheControl = builder.build();
}
}
okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
return builder.build();
}
可以看到,首先根據(jù) Request 和 NetworkPolicy 的參數(shù)設(shè)置緩存的各種參數(shù),之后調(diào)用 okhttp3.Request.Builder 的構(gòu)造函數(shù)并傳入 uri 創(chuàng)建了 Request。
OkHttp3 數(shù)據(jù)的獲取
之后我們看到 OkHttp3Downloader 的 load 方法,看看數(shù)據(jù)獲取是如何實(shí)現(xiàn)的:
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
其實(shí)就是調(diào)用 OkHttpClient 的 newCall 方法并調(diào)用 execute 獲取一個(gè) Response。
結(jié)果的處理
前面提到,在 BitmapHunter 的 run 方法中根據(jù) hunt() 返回的結(jié)果成功與否調(diào)用了 dispatcher 的不同方法來(lái)進(jìn)行的結(jié)果處理,讓我們看看是如何處理的
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
我們先看到 dispatchComplete 方法,它最終通過(guò) handler 調(diào)用到了 performComplete 方法中:
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());
batch(hunter);
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
}
}
可以看到,這里如果獲取到了結(jié)果,且需要內(nèi)存緩存,則將其放入內(nèi)存緩存。然后將這個(gè) BitmapHunter 從 Map 中刪除。
之后我們看到 dispatchFailed 方法,它最終通過(guò) handler 調(diào)用到了 performError 方法:
void performError(BitmapHunter hunter, boolean willReplay) {
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter),
"for error" + (willReplay ? " (will replay)" : ""));
}
hunterMap.remove(hunter.getKey());
batch(hunter);
}
這里它將 BitmapHunter 從 Map 中移除,然后就沒(méi)有進(jìn)行其他處理了。
內(nèi)存緩存
為了優(yōu)化流量消耗,Picasso 加入了內(nèi)存緩存機(jī)制,下面我們來(lái)看看 Picasso 內(nèi)存緩存的實(shí)現(xiàn)。
就像其他部分一樣,它的內(nèi)存緩存也考慮到了擴(kuò)展性,給了用戶自己實(shí)現(xiàn)的接口。
我們可以調(diào)用 Picasso 類(lèi)的 memoryCache 方法為其設(shè)置 Cache 接口的子類(lèi),從而實(shí)現(xiàn)自己的內(nèi)存緩存。
若用戶不傳入指定緩存,則默認(rèn)使用 Picasso 自己實(shí)現(xiàn)的 LruCache。
具體的 LruCache 的設(shè)計(jì)這里不深入講解,有興趣的各位可以去了解一下 LRU 算法,以后可能可以專(zhuān)門(mén)開(kāi)一篇博客講講 LRU 算法。
Dispatcher 設(shè)計(jì)
其實(shí)從前面的講解中,你會(huì)發(fā)現(xiàn),其實(shí)如圖片的加載請(qǐng)求,緩存命中等等事件都是由一個(gè)叫 Dispatcher 的類(lèi)分發(fā)的,它內(nèi)部由 Handler 實(shí)現(xiàn),負(fù)責(zé)將請(qǐng)求封裝,并按優(yōu)先級(jí)排序,之后按照類(lèi)型分發(fā)。
這種設(shè)計(jì)也很值得我們學(xué)習(xí),它作為一個(gè)分發(fā)中心管理我們的各類(lèi)請(qǐng)求。使得我們的設(shè)計(jì)更為清晰,也使得庫(kù)更容易維護(hù)。
線程池設(shè)計(jì)
之前沒(méi)有提到的就是 Picasso 對(duì)線程池也有一些優(yōu)化,它自己實(shí)現(xiàn)了一個(gè) PicassoExecutorService 類(lèi),它可以根據(jù)當(dāng)前的網(wǎng)絡(luò)狀態(tài),采用不同的線程池?cái)?shù)量,從而使得網(wǎng)絡(luò)不會(huì)過(guò)于擁塞。
具體可以看下面這個(gè)方法:
void adjustThreadCount(NetworkInfo info) {
if (info == null || !info.isConnectedOrConnecting()) {
setThreadCount(DEFAULT_THREAD_COUNT);
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
case ConnectivityManager.TYPE_ETHERNET:
setThreadCount(4);
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
setThreadCount(3);
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
setThreadCount(2);
break;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
case TelephonyManager.NETWORK_TYPE_EDGE:
setThreadCount(1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
}
可以看到,線程池最大線程個(gè)數(shù)如下:
- 在 WIFI 網(wǎng)絡(luò)下,采用最多 4 個(gè)線程的線程池
- 在 4G 網(wǎng)絡(luò)下,采用最多 3 個(gè)線程的線程池
- 在 3G 網(wǎng)絡(luò)下,采用最多 2 個(gè)線程的線程池
- 在 2 G 網(wǎng)絡(luò)下,采用最多 1 個(gè)線程的線程池
總結(jié)
Picasso 是一個(gè)非常值得我們學(xué)習(xí)的輕量級(jí)圖片加載庫(kù),它采用 OkHttp3 來(lái)加載網(wǎng)絡(luò)圖片,并使用了二級(jí)內(nèi)存緩存來(lái)提高加載速度。它的 Dispatcher 思想以及對(duì)外部的擴(kuò)展開(kāi)放的思想十分值得我們學(xué)習(xí),這次源碼的閱讀還是給了我很大的啟發(fā)的。
當(dāng)然,由于篇幅有限,這篇文章并沒(méi)有包含 Picasso 的方方面面,它的代碼中還有如下的一些點(diǎn)在本文還沒(méi)有分析,讀者們有興趣的可以從下面的點(diǎn)去研究這個(gè)庫(kù):
- 圖片加載的暫停與取消
- 圖片的變換實(shí)現(xiàn)
- 請(qǐng)求的優(yōu)先級(jí)
- 監(jiān)控機(jī)制
- 本地資源的加載