Picasso 作為 Android 上一個(gè)老牌的圖片加載庫,似乎近些年在 Glide 的 “打壓” 下已經(jīng)變的黯然失色旗闽,但作為 square 出品的優(yōu)秀框架神汹,其實(shí)現(xiàn)的架構(gòu)和思想仍然有許多值得借鑒和學(xué)習(xí)的地方曹仗,本文所使用的 Picasso 版本號為 2.71828,在 gradle 中依賴如下:
implementation 'com.squareup.picasso:picasso:2.71828'
Picasso 加載圖片的方式和 Glide 類似军掂,都是鏈?zhǔn)秸{(diào)用:
Picasso.get().load("").into(target); // 異步加載并設(shè)置到 target 上
Picasso.get().load("").get(); //同步加載轮蜕,不能在主線程調(diào)用
Picasso.get().load("").fetch(); // 異步加載
這里 Picasso 提供了三種方式:
-
into()
:參數(shù)可以是 View,如 ImageView 和 RemoteViews蝗锥,也可以是一個(gè) Target 類型的對象跃洛,等于是通過回調(diào)的方式來實(shí)現(xiàn)加載結(jié)果的處理 -
get()
:同步獲取 btimap 對象,會檢查當(dāng)前線程是否是主線程终议,如果是則直接會拋出異常 -
fetch()
:異步獲取 bitmap 對象汇竭,可以通過設(shè)置回調(diào)來監(jiān)聽是否加載成功
使用 into 加載的過程大致上可以分為三個(gè)階段:一是獲取 Picasso 對象,對應(yīng) Picasso.get()
穴张,這一步是初始化如線程池细燎、緩存等;二是請求封裝皂甘,對應(yīng) load(url)
玻驻,這一步則是對資源請求的封裝,例如占位圖偿枕、加載失敗圖璧瞬、tag、資源裁剪等益老;最后則是請求處理階段彪蓬,對應(yīng) into(ImageView)
,這一步處理的事情最為繁重捺萌,包括資源請求、資源處理等膘茎,下圖為 into(ImageView)
流程的的序列圖桃纯,接下來就通過圍繞這個(gè)序列圖來進(jìn)行分析:
獲取 Picasso 對象
這一節(jié)對應(yīng)于序列圖中的步驟 1、2披坏、3态坦。
靜態(tài)方法 get() 是通過 DCL 實(shí)現(xiàn)的單例模式,返回一個(gè)全局的 Picass 對象:
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;
}
這里 context 的獲取是通過 PicassoProvider 實(shí)現(xiàn)的棒拂, PicassoProvider 繼承自 ContentProvider伞梯,主要作用就是為 Picasso 提供 context 對象:
public final class PicassoProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak") static Context context;
@Override public boolean onCreate() {
context = getContext();
return true;
}
...
}
并在自己的 AndroidManifest 文件中注冊:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.picasso" >
<uses-sdk android:minSdkVersion="14" />
<application>
<provider
android:name="com.squareup.picasso.PicassoProvider"
android:authorities="${applicationId}.com.squareup.picasso"
android:exported="false" />
</application>
</manifest>
由于在進(jìn)程啟動(dòng)時(shí) ContentProvider 的加載是優(yōu)先于 Application 的加載玫氢,因此很多三方 SDK 也通常使用這種方式來自動(dòng)進(jìn)行初始化,通過在 aar 包中添加一個(gè)自定義的 ContentProvider谜诫,在其 onCreate() 方法中實(shí)現(xiàn)初始化邏輯漾峡。
除了通過 get() 方法,也可以通過 Picasso 的靜態(tài)內(nèi)部類 Builder 來自行構(gòu)建喻旷,主要提供了如下方法:
// 設(shè)置 Bitmap.Config
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
// 下載圖片的方式生逸,默認(rèn)使用 OkHttp3Downloader
Builder downloader(@NonNull Downloader downloader)
// 線程池,默認(rèn)為 PicassoExecutorService
public Builder executor(@NonNull ExecutorService executorService)
// 內(nèi)存緩存且预,默認(rèn)為 LruCache
public Builder memoryCache(@NonNull Cache memoryCache)
// 設(shè)置請求轉(zhuǎn)換器槽袄,只能有一個(gè)
public Builder requestTransformer(@NonNull RequestTransformer transformer)
// 添加請求處理器,可以有多個(gè)
public Builder addRequestHandler(@NonNull RequestHandler requestHandler)
下載器
對下載行為的抽象锋谐,提供 load() 方法來加載圖片并返回一個(gè) okhttp3.Response
對象遍尺,shutdown()
方法用于對資源進(jìn)行情理:
public interface Downloader {
@NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加載圖片
void shutdown();
}
默認(rèn)只有 OkHttp3Downloader 一個(gè)實(shí)現(xiàn),其內(nèi)部通過 OkHttp 進(jìn)行網(wǎng)絡(luò)請求下載圖片:
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
線程池
默認(rèn)使用的線程池為 PicassoExecutorService
涮拗,核心線程數(shù)和最大線程數(shù)數(shù)量相同乾戏,默認(rèn)都為 3 個(gè),但可根據(jù)網(wǎng)絡(luò)狀態(tài)進(jìn)行動(dòng)態(tài)調(diào)整:2G 網(wǎng)絡(luò)為 1 個(gè)多搀、3G 網(wǎng)絡(luò)為 2 個(gè)歧蕉、4G網(wǎng)絡(luò)為 3 個(gè)、WIFI 網(wǎng)絡(luò)為 4 個(gè)康铭。工作隊(duì)列為 PriorityBlockingQueue惯退,是一個(gè)無界阻塞隊(duì)列,可以通過自定義 compareTo() 來指定排序規(guī)則:
class PicassoExecutorService extends ThreadPoolExecutor {
private static final int DEFAULT_THREAD_COUNT = 3;
PicassoExecutorService() {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
}
private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
implements Comparable<PicassoFutureTask> {
...
@Override
public int compareTo(PicassoFutureTask other) {
Picasso.Priority p1 = hunter.getPriority();
Picasso.Priority p2 = other.hunter.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
}
}
}
compareTo()
中會首先判斷兩個(gè)請求的優(yōu)先級从藤,如果優(yōu)先級相同催跪,則會接著判斷添加的序號,sequence 是一個(gè)自增的 int 整型夷野,也就是說先入隊(duì)的請求先處理懊蒸,是一種先進(jìn)先出的方式。
內(nèi)存緩存器
Cache 接口提供了基本的對于緩存內(nèi)容的處理方法:
public interface Cache {
Bitmap get(String key);
void set(String key, Bitmap bitmap);
int size();
int maxSize(); //最大可緩存的字節(jié)數(shù)
void clear();
void clearKeyUri(String keyPrefix); //根據(jù)前綴移除對應(yīng)的緩存
}
其有 2 個(gè)實(shí)現(xiàn)類悯搔,NONE 和 LruCache骑丸,其中 NONE 是空實(shí)現(xiàn),LruCache 內(nèi)部是對 android.util.LruCache
的封裝妒貌,最大緩存大小為可用內(nèi)存大小的 1/7通危。
請求轉(zhuǎn)換器
RequestTransformer 可以理解為是 Picasso 向外提供的一個(gè)對請求進(jìn)行轉(zhuǎn)換的接口,用戶可以通過自定義一個(gè)轉(zhuǎn)換器來對生成的 Request 對象進(jìn)行處理灌曙,只能設(shè)置一個(gè)菊碟,默認(rèn)提供的實(shí)現(xiàn)為 IDENTITY 直接返回原 Request 對象,如下所示:
public interface RequestTransformer {
Request transformRequest(Request request);
RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
}
請求處理器
RequestHandler 是一個(gè)抽象類:
public abstract class RequestHandler {
public abstract boolean canHandleRequest(Request data);
public abstract Result load(Request request, int networkPolicy) throws IOException;
...
}
其作用就是在不同場景下加載圖片在刺,例如 Picasso 通過 AssetRequestHandler 來加載 asset 文件夾下的圖片資源逆害,用 NetworkRequestHandler 來加載網(wǎng)絡(luò)圖片資源等头镊,通過遍歷所有的 Handler,并調(diào)用它們的 canHandleRequest(Request data)
方法魄幕,如果返回 true相艇,則表示對應(yīng) Handler 可以處理該請求,則該請求就會交由這個(gè) Handler 處理梅垄。在 Picasso 的構(gòu)造函數(shù)中會添加默認(rèn) Handler 和用戶定義的 Handler:
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
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);
// 優(yōu)先添加用戶自定義的 handler
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));
...
}
請求封裝
這一節(jié)對應(yīng)于序列圖中的步驟 4厂捞、5、6队丝,主要作用就是封裝 Request 對象靡馁。
Picasso 提供了 4 種圖片來源的方法:
public RequestCreator load(@NonNull File file)
public RequestCreator load(@DrawableRes int resourceId)
public RequestCreator load(@Nullable Uri uri)
public RequestCreator load(@Nullable String path)
這里需要注意的是,傳入的參數(shù)不能為空机久,不然會直接拋一個(gè)異常臭墨,因此使用前的判空非常有必要。load()
方法會返回一個(gè) RequestCreator 對象膘盖,通過這個(gè)對象就可以來對請求做一些處理操作胧弛。RequestCreator 內(nèi)部持有一個(gè) Request 對象,所有關(guān)于請求的配置都在 Request 類中侠畔。
占位圖和失敗圖
占位圖支持 resId 和 drawable 兩種结缚,并且互斥,如果同時(shí)設(shè)置兩個(gè)則會拋出異常:
public RequestCreator placeholder(@DrawableRes int placeholderResId)
public RequestCreator noPlaceholder() //不設(shè)置占位圖软棺,與設(shè)置占位圖互斥
標(biāo)簽
public RequestCreator tag(@NonNull Object tag)
對一次請求進(jìn)行標(biāo)記红竭,可用于對請求進(jìn)行暫停、恢復(fù)喘落、取消等操作茵宪。
圖片處理
public RequestCreator fit() //調(diào)整圖片大小為 ImageView 大小
public RequestCreator resize(int targetWidth, int targetHeight)
public RequestCreator centerCrop()
public RequestCreator centerInside()
public RequestCreator onlyScaleDown() //僅在圖片大于 target 大小時(shí)對圖片進(jìn)行縮放處理
public RequestCreator rotate(float degrees) // bitmap 旋轉(zhuǎn)角度
public RequestCreator config(@NonNull Bitmap.Config config) // bitmap config
public RequestCreator transform(@NonNull Transformation transformation) // 對 bitmap 對象做自定義處理
public RequestCreator purgeable() // bitmap復(fù)用
public Builder transform(@NonNull Transformation transformation)
Transformation 用于對加載后的 bitmap 做處理:
public interface Transformation {
Bitmap transform(Bitmap source);
String key();
}
緩存策略
public RequestCreator stableKey(@NonNull String stableKey) // 設(shè)置緩存 key 值,擁有相同 key 值的資源將被視為是相同資源
public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, @NonNull MemoryPolicy... additional) // 內(nèi)存緩存策略
public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional) //磁盤緩存策略
MemoryPolicy 用于指定內(nèi)存緩存的策略:
public enum MemoryPolicy {
NO_CACHE(1 << 0), //請求時(shí)跳過內(nèi)存緩存中查詢
NO_STORE(1 << 1); //請求后跳過緩存到內(nèi)存中
...
}
NetworkPolicy 用于指定磁盤緩存的策略瘦棋,而 Picasso 中磁盤緩存是基于 OkHttp 的緩存來實(shí)現(xiàn):
public enum NetworkPolicy {
NO_CACHE(1 << 0), //跳過從磁盤緩存查詢稀火,直接通過網(wǎng)絡(luò)獲取資源
NO_STORE(1 << 1), //跳過寫入到磁盤緩存
OFFLINE(1 << 2); //跳過從網(wǎng)絡(luò)獲取,直接從磁盤緩存中獲取
}
優(yōu)先級
public RequestCreator priority(@NonNull Priority priority) // 請求優(yōu)先級赌朋,用于對請求任務(wù)進(jìn)行排序
public enum Priority {
LOW,
NORMAL,
HIGH
}
用于在前文說到的線程池阻塞隊(duì)列進(jìn)行請求任務(wù)排序凰狞。
加載動(dòng)畫
public RequestCreator noFade() // 加載圖片到 ImageView 時(shí)不顯示漸變動(dòng)畫
// com.squareup.picasso.PicassoDrawable#draw
@Override public void draw(Canvas canvas) {
if (!animating) {
super.draw(canvas);
} else {
float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
if (normalized >= 1f) {
animating = false;
placeholder = null;
super.draw(canvas);
} else {
if (placeholder != null) {
placeholder.draw(canvas);
}
// setAlpha will call invalidateSelf and drive the animation.
int partialAlpha = (int) (alpha * normalized);
super.setAlpha(partialAlpha);
super.draw(canvas);
super.setAlpha(alpha);
}
}
}
PicassoDrawable 繼承自 BitmapDrawable,通過重寫 onDraw() 方法實(shí)現(xiàn)了漸變過渡動(dòng)畫沛慢。
請求處理
對應(yīng)于序列圖中步驟 7 以后服球。
請求入隊(duì)
在 into()
中,首先會通過 createRequest()
來創(chuàng)建一個(gè) Request 請求對象颠焦,然后通過 createKey()
來創(chuàng)建請求的 key 值,這里 key 值的創(chuàng)建通過 stableKey往枣、uri伐庭、rotationDegrees粉渠、resize、centerCrop 等屬性組合而成圾另,因此即使請求的是同個(gè)資源霸株,如果其中有任何一個(gè)屬性有變化,由于生成的 key 值不同集乔,也會重新去請求去件。
public void into(ImageView target, Callback callback) {
...
Request request = createRequest(started);
String requestKey = createKey(request);
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;
}
}
...
Action action = new ImageViewAction(...);
picasso.enqueueAndSubmit(action);
}
如果沒有從內(nèi)存中讀取到,會先創(chuàng)建一個(gè) Action 對象扰路,Action 是一個(gè)抽象類:
abstract class Action<T> {
...
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error(Exception e);
...
}
它有幾個(gè)實(shí)現(xiàn)類:FetchAction尤溜、GetAction、ImageViewAction 等汗唱,分別對應(yīng)了 fetch()
宫莱、get()
、into(ImageView)
方法哩罪。接著會將這個(gè) action 對象通過 Picasso.enqueueAndSubmit()
重新又發(fā)送到了 Picasso 中:
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action);
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
請求發(fā)送
這里 action 對象最終會被傳入到 Dispatcher#performSubmit()
中:
//com.squareup.picasso.Dispatcher#performSubmit()
void performSubmit(Action action, boolean dismissFailed) {
//如果該請求已經(jīng)暫停授霸,則加入到 pausedActions 中
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
return;
}
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
//如果線程池已經(jīng)關(guān)閉,則對該請求不做處理
if (service.isShutdown()) {
return;
}
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//將請求封裝成為 Runnable 對象并發(fā)送到線程池中
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
...
}
forRequest()
會返回一個(gè) BitmapHunter 對象际插,它繼承自 Runnable碘耳,當(dāng) run()
執(zhí)行時(shí),會通過 hunt()
來獲取 Bitmap框弛,這里就是真正來解析 Bitmap 的地方辛辨,首先還是會去內(nèi)存里讀取,接著會調(diào)用 RequestHandler 的 load()
來加載資源功咒,然后對將加載得到的 bitmap 進(jìn)行處理愉阎,具體如何處理則取決于之前在 RequestCreator 中所設(shè)置,這一步處理完成后力奋,還要對用戶自定義的 Transformation 來進(jìn)行榜旦,簡化后的代碼流程如下:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//從內(nèi)存緩存讀取
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
}
//從 requestHandler 加載
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (bitmap != null) {
// 對 bitmap 做處理,如 fit
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
}
//用戶自定義 bitmap 處理邏輯
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
}
}
}
}
return bitmap;
}
看下 NetworkRequestHandler 中的 load()
邏輯:
@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);
}
Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
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);
}
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();
}
首先會調(diào)用 createRequest()
來創(chuàng)建一個(gè) Okhttp 的 Request 對象景殷,會根據(jù)之前定義的磁盤緩存邏輯來設(shè)置 Request 的 CacheControl溅呢。接著會將這個(gè)創(chuàng)建好的 Request 對象傳給 OkHttp3Downloader 來調(diào)用 okhttp3.Call.Factory#newCall()
發(fā)送網(wǎng)絡(luò)請求。
請求結(jié)果緩存
通過 BitmapHunter.hunt()
獲取到 Bitmap 后猿挚,會通過 Dispatcher#dispatchComplete()
來回調(diào)結(jié)果到 Dispatcher 中咐旧,在這里會對結(jié)果進(jìn)行緩存處理:
//com.squareup.picasso.Dispatcher#performComplete
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());
batch(hunter);
}
最終結(jié)果依次被回調(diào)到 ImageViewAction#complete()
或者是 ImageViewAction#error()
中來分別處理加載成功和失敗的邏輯,加載成功則將 bitmap 封裝成 PicassoDrawable 后設(shè)置給 ImageView绩蜻,加載失敗則顯示設(shè)置的失敗圖資源铣墨。
總結(jié)
二級緩存,LruCache 的內(nèi)存緩存和基于 Okhttp Cache 的磁盤緩存办绝,內(nèi)存緩存只會緩存最終處理后的 bitmap伊约,不會對原 bitmap 也進(jìn)行緩存
線程池線程數(shù)可根據(jù)網(wǎng)絡(luò)狀態(tài)進(jìn)行動(dòng)態(tài)切換
不支持 Gif 格式加載
記錄緩存命中率