Android框架原始碼解析之(四)Picasso
這次要分析的原始碼是 Picasso 2.5.2 ,四年前的版本,用eclipse寫的,但不影響這次我們對其原始碼的分析
地址:https://github.com/square/picasso/tree/picasso-parent-2.5.2
Picasso的簡單使用
Picasso.with(this)
.load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg")
.into(mImageView);
清晰簡潔
Picasso的原始碼分析
首先看一下基本流程
Picasso的總體流程:
1)將請求封裝為Request物件,然後將Request物件進一步封裝為Action(ImageAction)物件。
2)將Action(ImageAction)物件交給Dispather進行分發
3)最終將action交給BitmapHunter這個Runnable作為線上程池中執行緒的工作的單元(具體的是講action持有的當前Reqeuest物件)
4)由RequestHandler來處理當前request,呼叫其load方法將載入完成後的圖片交給PicassoDrawable顯示圖片。
程式碼流程如下:Picasso->load->建立request->建立action->Dispatcher分發action->RequestHandler的load方法處理具體的請求->PicassoDrawable顯示圖片。
下面來看細節:
1、Picasso.with()
首先看一下Picasso的構造方法
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. //使用者自定義RequestHandler的數量 int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0); List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>(builtInHandlers + extraCount); //新增各種RequestHandler //先新增ResourceRequestHandler // 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<Object, Action>(); this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>(); this.indicatorsEnabled = indicatorsEnabled; this.loggingEnabled = loggingEnabled; this.referenceQueue = new ReferenceQueue<Object>(); this.cleanupThread = new CleanupThread(referenceQueue, HANDLER); this.cleanupThread.start(); }
with()函式通過雙重檢查鎖模式返回預設Picasso預設例項
//通過雙重檢查鎖模式 返回Picasso單例物件
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
//通過建造者模式,對Picasso進行系統預設設定
singleton = new Builder(context).build();
}
}
}
return singleton;
}
通過建造者模式,對Picasso進行系統預設設定
/** Fluent API for creating {@link Picasso} instances. */
@SuppressWarnings("UnusedDeclaration") // Public API.
public static class Builder {
private final Context context;
private Downloader downloader;//配置自定義的義圖片下載類
private ExecutorService service;//配置自定義的執行緒池
private Cache cache;//配置快取策略
private Listener listener;//配置圖片下載監聽
private RequestTransformer transformer;//配置圖片自定義轉換器
private List<RequestHandler> requestHandlers;//配置RequestHandler 集合
private Bitmap.Config defaultBitmapConfig;//配置預設圖片設定
private boolean indicatorsEnabled;
private boolean loggingEnabled;
/** Start building a new {@link Picasso} instance. */
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
/**
* Specify the default {@link Bitmap.Config} used when decoding images. This can be overridden
* on a per-request basis using {@link RequestCreator#config(Bitmap.Config) config(..)}.
*/
public Builder defaultBitmapConfig(Bitmap.Config bitmapConfig) {
if (bitmapConfig == null) {
throw new IllegalArgumentException("Bitmap config must not be null.");
}
this.defaultBitmapConfig = bitmapConfig;
return this;
}
/** Specify the {@link Downloader} that will be used for downloading images. */
public Builder downloader(Downloader downloader) {
if (downloader == null) {
throw new IllegalArgumentException("Downloader must not be null.");
}
if (this.downloader != null) {
throw new IllegalStateException("Downloader already set.");
}
this.downloader = downloader;
return this;
}
/**
* Specify the executor service for loading images in the background.
* <p>
* Note: Calling {@link Picasso#shutdown() shutdown()} will not shutdown supplied executors.
*/
public Builder executor(ExecutorService executorService) {
if (executorService == null) {
throw new IllegalArgumentException("Executor service must not be null.");
}
if (this.service != null) {
throw new IllegalStateException("Executor service already set.");
}
this.service = executorService;
return this;
}
/** Specify the memory cache used for the most recent images. */
public Builder memoryCache(Cache memoryCache) {
if (memoryCache == null) {
throw new IllegalArgumentException("Memory cache must not be null.");
}
if (this.cache != null) {
throw new IllegalStateException("Memory cache already set.");
}
this.cache = memoryCache;
return this;
}
/** Specify a listener for interesting events. */
public Builder listener(Listener listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener must not be null.");
}
if (this.listener != null) {
throw new IllegalStateException("Listener already set.");
}
this.listener = listener;
return this;
}
/**
* Specify a transformer for all incoming requests.
* <p>
* <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
* way at any time.
*/
public Builder requestTransformer(RequestTransformer transformer) {
if (transformer == null) {
throw new IllegalArgumentException("Transformer must not be null.");
}
if (this.transformer != null) {
throw new IllegalStateException("Transformer already set.");
}
this.transformer = transformer;
return this;
}
/** Register a {@link RequestHandler}. */
public Builder addRequestHandler(RequestHandler requestHandler) {
if (requestHandler == null) {
throw new IllegalArgumentException("RequestHandler must not be null.");
}
if (requestHandlers == null) {
requestHandlers = new ArrayList<RequestHandler>();
}
if (requestHandlers.contains(requestHandler)) {
throw new IllegalStateException("RequestHandler already registered.");
}
requestHandlers.add(requestHandler);
return this;
}
/**
* @deprecated Use {@link #indicatorsEnabled(boolean)} instead.
* Whether debugging is enabled or not.
*/
@Deprecated public Builder debugging(boolean debugging) {
return indicatorsEnabled(debugging);
}
/** Toggle whether to display debug indicators on images. */
public Builder indicatorsEnabled(boolean enabled) {
this.indicatorsEnabled = enabled;
return this;
}
/**
* Toggle whether debug logging is enabled.
* <p>
* <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only
* be used for debugging purposes. Do NOT pass {@code BuildConfig.DEBUG}.
*/
public Builder loggingEnabled(boolean enabled) {
this.loggingEnabled = enabled;
return this;
}
/** Create the {@link Picasso} instance. */
//所有屬性設定成預設
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(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);
}
}
可以看出with()函式主要 使用單例模式(雙重檢查鎖)建立Picasso例項,並使用建造者模式 對Picasso進行初始化配置。
2、Picasso.load()
//建立RequestCreator
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
/**
* Start an image request using the specified path. This is a convenience method for calling
* {@link #load(Uri)}.
* <p>
* This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
* (prefixed with {@code content:}), or android resource (prefixed with {@code
* android.resource:}.
* <p>
* Passing {@code null} as a {@code path} will not trigger any request but will set a
* placeholder, if one is specified.
*
* @see #load(Uri)
* @see #load(File)
* @see #load(int)
* @throws IllegalArgumentException if {@code path} is empty or blank string.
*/
public RequestCreator load(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));
}
/**
* Start an image request using the specified image file. This is a convenience method for
* calling {@link #load(Uri)}.
* <p>
* Passing {@code null} as a {@code file} will not trigger any request but will set a
* placeholder, if one is specified.
* <p>
* Equivalent to calling {@link #load(Uri) load(Uri.fromFile(file))}.
*
* @see #load(Uri)
* @see #load(String)
* @see #load(int)
*/
public RequestCreator load(File file) {
if (file == null) {
return new RequestCreator(this, null, 0);
}
return load(Uri.fromFile(file));
}
/**
* Start an image request using the specified drawable resource ID.
*
* @see #load(Uri)
* @see #load(String)
* @see #load(File)
*/
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}
可以看出這幾種過載方法 目的都是為了建立RequestCreator 物件
//讓我們來看看RequestCreator的構造方法
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;
//接著建立了Request物件
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
呼叫load方法的時候實際上並沒有對圖片資源進行載入,只是簡單返回了一個RequestCreator物件,該物件在初始化的時候初始化了Request的Builder物件
3、RequestCreator.into()
使用過Picasso的都知道,我們呼叫RequestCreator的into方法來完成工作的,那麼就先簡單的分析RequestCreator的into系列過載方法之一進行說明
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//檢查是否在主執行緒
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
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());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
//建立Request請求
Request request = createRequest(started);
//建立請求的key
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 (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//建立Action,實際上是一個ImageViewAction
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//提交Action
picasso.enqueueAndSubmit(action);
}
繼續檢視 picasso.enqueueAndSubmit(action)
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);
}
繼續跟進 dispatcher.dispatchSubmit(action);
void dispatchSubmit(Action action) {
//這裡呼叫了handler傳送訊息
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;
}
繼續跟進dispatcher.performSubmit(action);
void performSubmit(Action action) {
performSubmit(action, true);
}
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;
}
//BitmapHunter獲取了請求的key,Hunter是一個runnable
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
//重點在這 forRequest();
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());
}
}
繼續跟進forRequest();
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
//獲取當前請求物件
Request request = action.getRequest();
//獲取picasso內建的RequestHandler物件和自定義的RequestHandler物件
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
//判斷內建物件或者自定義的物件哪一個物件能處理當前請求
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {//如果能處理當前請求
//返回一個bitMapHandler
//把當前ReqeuestHandler物件交給BitmapHunter
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
//說明當前請求無效
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
上面forRequest方法也很簡單:主要執行了一下邏輯
1、獲取當前請求Reqeuest物件
2、獲取Picasso內建的RequestHandler以及自定義的RequestHandler集合。
3、判斷集合中哪一個RequestHandler物件可以對當前Reqeuest進行處理(canHanlderReqest返回true)
4、把集合中能處理當前Request的RequestHandler物件交給BitMapHunter。
Action的簡單說明:
Action所有的屬性如下:
final Picasso picasso;//持有上文單利引用
final Request request;//上文提到的request
final WeakReference<T> target;//target可能是ImageView,或者優先理解為ImageView,為弱引用,確保target被回收的時候不受影響
final boolean noFade;
final int memoryPolicy;//快取策略
final int networkPolicy;//
final int errorResId;
final Drawable errorDrawable;
final String key;
final Object tag;
boolean willReplay;
boolean cancelled;
Action的主要職責就是:對圖片進行載入,配置圖片的檔案快取和記憶體快取策略以及是否重新載入等邏輯。使得責任分明,調理清晰。
Action是一個抽象的泛型類,提供了complete和error兩個抽象方法
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error();
它的子類又如下幾個:
GetAction、FetchAction、ImageViewAction、TargetAction在此處我們提交的是ImageViewAction.
讓我們簡單的看一下ImageViewAction的complete方法:
@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);
//設定callback
if (callback != null) {
callback.onSuccess();
}
}
可以發現最終是由PicassoDrawable來完成圖片的顯示,所以繼續跟進:
PicasDrawable是BitmapDrawable的子類:
final class PicassoDrawable extends BitmapDrawable {
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
//獲取drawable
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
//建立
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
//顯示
target.setImageDrawable(drawable);
}
//最終將圖片顯示出來
}
RequestHandler
該類用來處理不同來源的圖片,是個抽象類。
在picasso只是用面向物件的方式處理來自網路(NetworkRequestHandler),Resoure資源圖片(ResourceRequestHandler),asset檔案中的圖片(AssetRequestHandler)等等若干個Handler。在初始化Picasso的時候,Picasso預設實現的RequestHandler的上述子類是預載入到一個集合中去的!
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...........................
/使用者自定義RequestHandler的數量
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(builtInHandlers + extraCount);
//新增各種RequestHandler
//先新增ResourceRequestHandler
// 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);
}
Picasso自己實現的handler在構造器初始化的時候就把自己實現的RequestHander的子類預先初始化好(姑且稱之為Picasso內建的ReqeustHandler物件),當然使用者也可以實現自己的RequestHandler通過Picasso.Builder.addRequestHandler來新增進去,當然大多數情況不需要自己提供ReqeuestHandler的實現。
那Picasso怎麼知道是來自於哪個種類的圖片呢?這就是ReqeuestHandler的功能了!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.
*
*/
//在canHandleRequest方法返回true的時候,就用該RequestHandler的實現類的load方法來載入圖片!
public abstract Result load(Request request, int networkPolicy) throws IOException;
簡單分析一下
Picasso怎麼處理網路Reqeust的:NetworkRequestHandler
//判斷當前請求的圖片資源是否來自於伺服器或者網路
public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
//NetWorkReqesutHandler對load的處理
public Result load(Request request, int networkPolicy) throws IOException {
//下載圖片資源,返回一個Response物件
Response response = downloader.load(request.uri, request.networkPolicy);
if (response == null) {
return null;
}
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
。。。此處有省略程式碼。。。
return new Result(is, loadedFrom);
}
Picasso怎麼處理ResourceReqeust的:ResourceRequestHandler
@Override
public boolean canHandleRequest(Request data) {
//若資源id不為0,則判斷為為Resource,能載入
if (data.resourceId != 0) {
return true;
}
//判斷檔案scheme是否為 RESOURCE
return SCHEME_ANDROID_RESOURCE.equals(data.uri.getScheme());
}
@Override
public Result load(Request request, int networkPolicy) throws IOException {
//載入Resource檔案(R檔案引用)
Resources res = Utils.getResources(context, request);
int id = Utils.getResourceId(res, request);
return new Result(decodeResource(res, id, request), DISK);
}
Picasso怎麼處理FileReqeust的:FileRequestHandler
@Override public boolean canHandleRequest(Request data) {
//判斷檔案scheme是否為FILE
return SCHEME_FILE.equals(data.uri.getScheme());
}
@Override public Result load(Request request, int networkPolicy) throws IOException {
return new Result(null, getInputStream(request), DISK, getFileExifRotation(request.uri));
}
其他檔案來源均類似
注意此時Picasso並沒有立即呼叫RequestHandler物件的load方法進行處理,而是繼續呼叫 service.submit(hunter);方法來線上程池中對BitmapHunter這個Runnable進行處理,所以我們不難猜測出來在run方法中必然呼叫了RequestHandler的load方法!
那麼就繼續追蹤BimapHunter的run方法發現其呼叫了hunt()方法,那麼hunt()方法是由做了什麼
//BitmapHunter的run方法
public void run() {
result = hunt();//獲取執行結果
}
//注意該方法返回了一個bitmap
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//獲取讀取記憶體程式碼省略
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
//此處真是呼叫了load方法進行處理
RequestHandler.Result result = requestH andler.load(data, networkPolicy);
....此處省略大量程式碼....
return bitmap;
}
在hunt()方法中正式呼叫了forReqeuset過濾的ReqesutHandler物件的load方法完成了核心業務功能!
上面簡單的介紹了一下Picasso的工作流程,,希望能對大家有所幫助~~~