簡談原始碼-Picasso原始碼(v_2.71828)
Picasso.get().load(url).into(iv);
Picasso的常見使用步驟很簡單,下面我們概要的看看其實現。
1. 例項獲取get()
/* Picasso.class */
@SuppressLint("StaticFieldLeak") 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單例模式
1.1 context獲取
通過ContentProvider,在AndroidManifest 中註冊PicassoProvider,app啟動後呼叫onCreate()
即可獲取所需context例項。
public final class PicassoProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak") static Context context;
@Override public boolean onCreate () {
context = getContext();
return true;
}
}
1.2 Picasso建立
使用Builder Pattern建造者模式,將一個複雜的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示,簡單點說就是用多個簡單的物件一步一步構建成一個複雜的物件。
/* Picasso.class */
public Builder(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
具體建造過程如下。
/* Picasso.class */
public Picasso build() {
Context context = this.context;
// 配置下載器,下載網路圖片資源(預設使用OkHttp3Downloader)
if (downloader == null) {
downloader = new OkHttp3Downloader(context);
}
// 配置記憶體快取,提高圖片載入效率(預設使用LruCache[Least Recently Used])
// 記憶體快取大小為RAM的15%;磁碟快取大小為ROM的2%且範圍為5~50mb
if (cache == null) {
cache = new LruCache(context);
}
// 配置執行緒池
// 預設3個執行緒;使用wifi為4個,4G為3個,3G為2個,2G為1個
if (service == null) {
service = new PicassoExecutorService();
}
// 配置請求轉換器(預設不做處理,直接返回原請求)
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
// 統計:統計快取命中數量、圖片下載數量等
Stats stats = new Stats(cache);
// 分發器:由HANDLER分發圖片請求、請求完成等等事件
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
至此,Picasso.get()
過程完成。
2. 獲取請求load(url)
即通過請求建立器RequestCreator建立請求Request,通過Request可以執行placeholder(placeholderResId)方法設定佔位圖,error(errorResId)方法設定錯誤圖,noFade()方法取消圖片漸變效果等操作。
/* Picasso.class */
public RequestCreator load(@Nullable String path) {
...
return load(Uri.parse(path));
}
public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}
RequestCreator的構造方法如下,核心是通過建造者模式建造Request例項。
通過picasso可以發起請求、推遲(defer)請求、取消請求、檢查快取配置等操作;通過建立的Request例項data可以呼叫centerCrop()、centerInside()等方法。
boolean shutdown;
private final Picasso picasso;
private final Request.Builder data;
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);
}
3. 顯示into(iv)
核心邏輯為建立Action,即一次圖片請求的活動過程,然後分發該action。
/* RequestCreator.class */
/**
* 非同步請求圖片資源至使用者指定的ImageView
* 使用弱引用關聯ImageView,資源將會被自動回收
*/
public void into(ImageView target) {
into(target, null);
}
/**
* 若使用者指定Callback,為了防止Activity/Fragment回收,會是一個強引用;推薦及時呼叫cancelRequest()方法防止記憶體洩露
*/
public void into(ImageView target, Callback callback) {
// 記錄開始時間,並檢查執行緒是否安全
long started = System.nanoTime();
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
// 若Request例項為空,取消當前請求,設定佔位圖
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
// deferred預設是false的
if (deferred) {
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());
}
// 若獲取不到目標ImageView的寬高,就延遲請求,直到獲取寬高時再次呼叫into()方法
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
// 建立Request例項並標記
Request request = createRequest(started);
String requestKey = createKey(request);
// 控制從快取中讀取,若命中requestKey對應的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 (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 記憶體快取未命中,則建立Action例項,並提交至picasso
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
3.1 action的建立
上述程式碼中用到的關鍵活動類ImageViewAction就是繼承自Action,並重寫了Action中的方法complete()、error()、cancel()
,具體實現和方法註釋說明如下。
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;
}
// 圖片請求成功,使用PicassoDrawable將圖片顯示到指定的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);
if (callback != null) {
callback.onSuccess();
}
}
// 圖片請求失敗,判斷是否顯示佔位圖
@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);
}
}
// 取消本次請求
@Override void cancel() {
super.cancel();
if (callback != null) {
callback = null;
}
}
}
3.2 action的提交
picasso提交action,然後通過分發器dispatcher分發訊息。
/* Picasso.class */
void enqueueAndSubmit(Action action) {
...
submit(action);
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
3.3 action的分發
dispatcher通過Handler傳送action,交由圖片捕獲器BitmapHunter處理。
/* Dispatcher.class */
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
...
}
}
}
void performSubmit(Action action) {
performSubmit(action, true);
}
void performSubmit(Action action, boolean dismissFailed) {
...
// 剩下的工作將由BitmapHunter完成
BitmapHunter hunter = BitmapHunter.forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
...
}
3.3.1 BitmapHunter.forRequest()
獲取圖片捕獲器
/* BitmapHunter.class */
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
// 輪詢獲取能夠處理這次活動請求(action's request)的請求處理器
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
其中所有的請求處理器requestHandlers是在Picasso構造時初始化的,總共為7種。
Picasso(...) {
...
allRequestHandlers.add(new ResourceRequestHandler(context));
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));
...
}
看看網路請求處理器中的匹配規則,即Uri中包含"http"或"https"。
/* NetworkRequestHandler.class */
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return ("http".equals(scheme) || "https".equals(scheme));
}
3.3.2 service.submit(hunter)
開啟圖片捕獲器hunter執行緒,獲取bitmap
圖片捕獲器hunter是一個Runnable,hunter獲取後執行submit()
方法開啟hunter執行緒。
/* ExecutorService.class */
public Future<?> submit(Runnable task) {
...
execute(ftask);
return ftask;
}
hunter執行緒中的核心操作就是獲取圖片資源,然後分發獲取結果
/* BitmapHunter.class */
@Override public void run() {
Bitmap result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
...
}
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
// 從快取中讀取圖片資源
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
...
return bitmap;
}
// 快取未命中,使用請求處理器載入圖片資源
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
...
bitmap = result.getBitmap();
...
}
...
return bitmap;
}
本篇以請求網路圖片為背景展開介紹,load()
方法中使用下載器downloader來下載圖片,當前Picasso版本中的網路下載器貌似只有OkHttp3Downloader,沒有UrlConnectionDownloader了。
網路請求處理器NetworkRequestHandler中的load()
方法如下,即使用下載器下載圖片資源。
/* NetworkRequestHandler.class */
@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();
...
return new Result(body.source(), loadedFrom);
}
網路下載器OkHttp3Downloader中的load()
方法如下,請求完成即可獲取圖片資源。
/* OkHttp3Downloader.class */
@NonNull @Override public Response load(@NonNull Request request) throws IOException {