前一篇文章講了Picasso的詳細用法,Picasso 是一個強大的圖片載入快取框架,一個非常優秀的開源庫,學習一個優秀的開源庫,,我們不僅僅是學習它的用法,停留在使用API層面,我們也要試著去閱讀原始碼,有兩個方面的原因,第一,熟悉了原始碼我們才能更好的駕馭,專案中做我們需要的定製。第二,學習它的設計思想、編碼風格、程式碼的架構,然後在專案中對這些好的思想和架構加以實踐,變成自己的知識,這樣才會對我們有更多的提升和幫助。這也是我們學習的目的,因此這篇文章對Picasso 的原始碼和流程做一個分析。
一、Picasso 載入圖片流程圖
上面就是Picasso載入圖片的流程,圖畫的醜,各位見諒。
二、重要的類介紹
(0)Picasso
: 圖片載入、轉換、快取的管理類。單列模式 ,通過with
方法獲取例項,也是載入圖片的入口。
(1)RequestCreator
: Request構建類,Builder 模式,採用鏈式設定該Request的屬性(如佔點陣圖、快取策略、裁剪規則、顯示大小、優先順序等等)。最後呼叫build()
方法生成一個請求(Request)。
(2)DeferredRequestCreator:
RequestCreator的包裝類,當建立請求的時候還不能獲取ImageView的寬和高的時候,則建立一個DeferredRequestCreator,DeferredRequestCreator裡對 target 設定監聽,直到可以獲取到寬和高的時候重新執行請求建立。
(3) Action
: 請求包裝類,儲存了該請求和RequestCreator設定的這些屬性,最終提交給執行緒執行下載。
(4)Dispatcher
:分發器,分發執行各種請求、分發結果等等。
(5)PicassoExecutorService
:Picasso使用的執行緒池,預設池大小為3。
(6)LruCache
:一個使用最近最少使用策略的記憶體快取。
(7)BitmapHunter
:這是Picasso的一個核心的類,開啟執行緒執行下載,獲取結果後解碼成Bitmap,然後做一些轉換操作如圖片旋轉、裁剪等,如果請求設定了轉換器Transformation,也會在BitmapHunter裡執行這些轉換操作。
(8)NetworkRequestHandler
:網路請求處理器,如果圖片需要從網路下載,則用這個處理器處理。
(9)FileRequestHandler
:檔案請求處理器,如果請求的是一張存在檔案中的圖片,則用這個處理器處理。
(10)AssetRequestHandler
: Asset 資源圖片處理器,如果是載入asset目錄下的圖片,則用這個處理器處理。
(11)ResourceRequestHandler
:Resource資源圖片處理器,如果是載入res下的圖片,則用這個處理器處理。
(12)ContentStreamRequestHandler
: ContentProvider 處理器,如果是ContentProvider提供的圖片,則用這個處理器處理
(13)MediaStoreRequestHandler
: MediaStore 請求處理器,如果圖片是存在MediaStore上的則用這個處理器處理。
(14)ContactsPhotoRequestHandler
:ContactsPhoto 請求處理器,如果載入com.android.contacts/ 下的tu圖片用這個處理器處理。如:
// e.g. content://com.android.contacts/contacts/38)
//匹配的路徑如下:
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", ID_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", ID_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", ID_THUMBNAIL);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", ID_CONTACT);
matcher.addURI(ContactsContract.AUTHORITY, "display_photo/#", ID_DISPLAY_PHOTO);
}複製程式碼
上面8-14 是預設的提供的幾個處理器,分別處理不同來源的請求。
(15)Response
: 返回的結果資訊,Stream流或者Bitmap。
(16)Request
: 請求實體類,儲存了應用在圖片上的資訊。
(17)Target
:圖片載入的監聽器介面,有3個回撥方法,onPrepareLoad 在請求提交前回撥,onBitmapLoaded 請求成功回撥,並返回Bitmap,onBitmapFailed請求失敗回撥。
(18)PicassoDrawable
:繼承BitmapDrawable,實現了過渡動畫和圖片來源的標識(就是圖片來源的指示器,要呼叫 setIndicatorsEnabled(true)
方法才生效),請求成功後都會包裝成BitmapDrawable顯示到ImageView 上。
(19)OkHttpDownloader
:用OkHttp實現的圖片下載器,預設就是用的這個下載器。
(20)UrlConnectionDownloader
:使用HttpURLConnection 實現的下載器。
(21)MemoryPolicy
: 記憶體快取策略,一個列舉型別。
(22)NetworkPolicy
: 磁碟快取策略,一個列舉型別。
(23) Stats
: 這個類相當於日誌記錄,會記錄如:記憶體快取的命中次數,丟失次數,下載次數,轉換次數等等,我們可以通過StatsSnapshot
類將日誌列印出來,看一下整個專案的圖片載入情況。
(24)StatsSnapshot
:狀態快照,和上面的Stats
對應,列印Stats
紀錄的資訊。
以上就是Picasso 的一些關鍵的類的介紹(還有一些簡單的沒有列舉)。
三、流程分析
上一節介紹了Picasso 的一些關鍵類,接下來就以載入網路圖片為例,分析Picasso載入圖片的整個流程
Picasso.with(this).load(URL)
.placeholder(R.drawable.default_bg)
.error(R.drawable.error_iamge)
.into(mBlurImage);複製程式碼
1, 獲取Picasso instance
首先要獲取一個Picasso物件,採用的單例模式
//單例模式獲取Picasso 物件
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
// 真正new 的地方在build()方法裡
public Picasso build() {
Context context = this.context;
if (downloader == null) {
//配置預設的下載器,首先通過反射獲取OkhttpClient,如果獲取到了,就使用OkHttpDwownloader作為預設下載器
//如果獲取不到就使用UrlConnectionDownloader作為預設下載器
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
// 配置記憶體快取,大小為手機記憶體的15%
cache = new LruCache(context);
}
if (service == null) {
// 配置Picaso 執行緒池,核心池大小為3
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);
}複製程式碼
2, 通過load方法生成一個RequestCreator
通過load方法生成一個RequestCreator,用鏈式api 來構建一個圖片下載請求
//load有幾個過載方法,引數為string和File 的過載最重都會包裝成一個Uri 呼叫這個方法
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
// 如果是載入資源id 的圖片會呼叫這個方法
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}複製程式碼
RequestCreator提供了很多的API 來構建請求,如展點陣圖、大小、轉換器、裁剪等等,這些API其實是為對應的屬性賦值,最終會在into方法中構建請求。
// 配置佔點陣圖,在載入圖片的時候顯示
public RequestCreator placeholder(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;
}
// 配置真正顯示的大小
public RequestCreator resize(int targetWidth, int targetHeight) {
data.resize(targetWidth, targetHeight);
return this;
}複製程式碼
3,into 新增顯示的View,並且提交下載請求
into方法裡面幹了3件事情:
1, 判斷是否設定了fit 屬性,如果設定了,再看是否能夠獲取ImageView 的寬高,如果獲取不到,生成一個DeferredRequestCreator(延遲的請求管理器),然後直接return,在DeferredRequestCreator中當監聽到可以獲取ImageView 的寬高的時候,再執行into方法。
2, 判斷是否從記憶體快取獲取圖片,如果沒有設定NO_CACHE,則從記憶體獲取,命中直接回撥CallBack 並且顯示圖片。
3, 如果快取未命中,則生成一個Action,並提交Action。
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
// 檢查是否在主執行緒
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//如果沒有url或者resourceId 則取消請求
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
//判斷是否設定了fit屬性
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());
}
//如果獲取不到寬高,生成一個DeferredRequestCreator(延遲的請求管理器),然後直接return,
//在DeferredRequestCreator中當監聽到可以獲取ImageView 的寬高的時候,再執行into方法。
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
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 (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//記憶體快取未命中或者設定了不從記憶體快取獲取,則生成一個Action ,提交執行。
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);// 提交請求
}複製程式碼
4, 提交、分發、執行請求。
會經過下面這一系列的操作,最重將Action 交給BitmapHunter 執行。
enqueueAndSubmit -> submit -> dispatchSubmit -> performSubmit:
//將action 儲存到了一個Map 中,目標View作為key
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);
}
//執行請求提交
//1, 先檢視儲存暫停tag表裡面沒有包含Action的tag,如果包含,則將Action 存到暫停Action表裡
//2,從BitmapHunter表裡查詢有沒有對應action的hunter,如果有直接attach
//3, 為這個請求生成一個BitmapHunter,提交給執行緒池執行
void performSubmit(Action action, boolean dismissFailed) {
// 先檢視儲存暫停tag表裡面沒有包含Action的tag,如果包含,則將Action 存到暫停Action表裡
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 hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
// 如果執行緒池北shutDown,直接return
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
// 為請求生成一個BitmapHunter
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());
}
}複製程式碼
5,指定對應的處理器(RequestHandler)
在上面執行的請求的performSubmit 方法裡,呼叫了forRequest 方法為對應的Action 生成一個BitmapHunter,裡面有一個重要的步驟,指定請求處理器(在上面一節介紹Picasso有7種請求處理器,看一下對應的程式碼:
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);
// 迴圈請求處理器列表,如果找到有能處理這個請求的請求處理器
// 則生成BitmapHunter
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}複製程式碼
從Picasso裡獲取一個處理器列表,然後迴圈列表,看是否有能處理該請求的處理器,如果有,則生成BitmapHunter,那麼這個請求處理器的列表在哪兒初始化的呢?請看原始碼:
// 1,首先呼叫了getRequestHandlers
List<RequestHandler> getRequestHandlers() {
return requestHandlers;
}
// 2 requestHandlers 列表是在Picasso 建構函式裡出實話的
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
....
//前面程式碼省略
// 新增了7個內建的請求處理器
// 如果你自己通過Builder添了額外的處理器,也會新增在這個列表裡面
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(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);
//後面程式碼省略
...
}複製程式碼
小結: 在Picasso 的建構函式裡 初始化了內建的7中請求處理器,然後在生成BitmapHunter的時候,迴圈列表,找到可以處理對應請求的處理器。
6, 重點:BitmapHunter (圖片捕獲器)
上一節重要類介紹的時候介紹過BitmapHunter,BitmapHunter繼承Runnable,其實就是開啟一個執行緒執行最終的下載。看一下原始碼:
1, run() 方法
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
// 呼叫hunt() 方法獲取最終結果
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);//如果為null,分發失敗的訊息
} else {
dispatcher.dispatchComplete(this);//如果不為null,分發成功的訊息
}
} catch (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (NetworkRequestHandler.ContentLengthException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (IOException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
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);
}
}複製程式碼
當將一個bitmapHunter submit 給一個執行緒池執行的時候,就會執行run() 方法,run裡面呼叫的是hunt方法來獲取結果,看一下hunt
方法:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
// 是否從記憶體快取獲取Bitmap
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;
}
}
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
// 請求處理器處理請求,獲取結果,Result裡可能是Bitmap,可能是Stream
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
//如果需要做轉換,則在這裡做轉換處理,如角度旋轉,裁剪等。
bitmap = transformResult(data, bitmap, exifRotation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
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;
}複製程式碼
7,Downloader 下載器下載圖片
上面的hunt方法獲取結果的時候,最終呼叫的是配置的處理器的load方法,如下:
RequestHandler.Result result = requestHandler.load(data, networkPolicy);複製程式碼
載入網路圖片用的是NetworkRequestHandler,匹配處理器,有個canHandleRequest 方法:
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}複製程式碼
判斷的條件是,Uri帶有"http://" 或者 https:// 字首則可以處理
我們接下來看一下NetworkRequestHandler的load方法:
@Override public Result load(Request request, int networkPolicy) throws IOException {
//最終呼叫downloader的load方法獲取結果
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;
}
// 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 && response.getContentLength() == 0) {
Utils.closeQuietly(is);
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && response.getContentLength() > 0) {
stats.dispatchDownloadFinished(response.getContentLength());
}
return new Result(is, loadedFrom);
}複製程式碼
NetworkRequestHandler最終是呼叫的downloader 的load方法下載圖片。內建了2個Downloader,OkhttpDownloader和UrlConnectionDownloader 。我們以UrlConnectionDownloader為例,來看一下load方法:
@Override public Response load(Uri uri, int networkPolicy) throws IOException {
// 如果SDK 版本大於等於14,安裝磁碟快取,用的是HttpResponseCache(快取http或者https的response到檔案系統)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installCacheIfNeeded(context);
}
HttpURLConnection connection = openConnection(uri);
//設定使用快取
connection.setUseCaches(true);
if (networkPolicy != 0) {
String headerValue;
// 下面一段程式碼是設定快取策略
if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
headerValue = FORCE_CACHE;
} else {
StringBuilder builder = CACHE_HEADER_BUILDER.get();
builder.setLength(0);
if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
builder.append("no-cache");
}
if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
if (builder.length() > 0) {
builder.append(',');
}
builder.append("no-store");
}
headerValue = builder.toString();
}
connection.setRequestProperty("Cache-Control", headerValue);
}
int responseCode = connection.getResponseCode();
if (responseCode >= 300) {
connection.disconnect();
throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
networkPolicy, responseCode);
}
long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
// 最後獲取InputStream流包裝成Response返回
return new Response(connection.getInputStream(), fromCache, contentLength);
}複製程式碼
小結:梳理一下呼叫鏈, BitmapHunter -> NetworkRequestHandler -> UrlConnectionDownloader(也有可能是OkHttpDownloader),經過這一系列的呼叫,最後在BitmapHunter 的run 方法中就可以獲取到我們最終要的Bitmap。
8,返回結果並顯示在Target上
在BitmapHunter獲取結果後,分發器分發結果,通過Hander處理後,執行performComplete方法:
//1,
void performComplete(BitmapHunter hunter) {
// 這裡將結果快取到記憶體
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());// 請求完畢,將hunter從表中移除
batch(hunter);
if (hunter.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
}
}
// 2,然後將BitmapHunter新增到一個批處理列表,通過Hander傳送一個批處理訊息
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
// 3,最後執行performBatchComplete 方法,通過主執行緒的Handler送處理完成的訊息
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
// 4,最後在Picasso 中handleMessage,顯示圖片
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
@SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}
//後面程式碼省略
...
};
// 5,最後回撥到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 並顯示
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess(); 回撥callback
}
}複製程式碼
小結:通過上面一系列的方法呼叫, performComplete -> batch —> performBatchComplete -> handleMessage -> complete 把BitmapHunter中獲取到的結果回撥到主執行緒,並且顯示在Target上。
通過以上的8個步驟,就把圖片從載入到顯示的整個過程分析完了。
四,快取特別說明
記憶體快取很簡單,用的是LRUCache,大小為 手機記憶體的15% ,上面程式碼中已經分析過了,這裡不過多說明,這裡重點說一下Disk Cahce。Picasso記憶體了2個預設的下載器,UrlConnectionDownloader和OkHttpDownloader,它們的磁碟快取實現還是有一些差異的,看一下程式碼:
public OkHttpDownloader(final File cacheDir, final long maxSize) {
this(defaultOkHttpClient());
try {
client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));
} catch (IOException ignored) {
}
}複製程式碼
在OkHttpDownloader 的構造方法裡設定了磁碟快取,使用的okHttp 的 DiskLruCache 實現的。
然後看一下UrlConnectionDownloader的磁碟快取實現,程式碼:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installCacheIfNeeded(context);
}複製程式碼
private static void installCacheIfNeeded(Context context) {
// DCL + volatile should be safe after Java 5.
if (cache == null) {
try {
synchronized (lock) {
if (cache == null) {
cache = ResponseCacheIcs.install(context);
}
}
} catch (IOException ignored) {
}
}
}
private static class ResponseCacheIcs {
static Object install(Context context) throws IOException {
File cacheDir = Utils.createDefaultCacheDir(context);
HttpResponseCache cache = HttpResponseCache.getInstalled();
if (cache == null) {
long maxSize = Utils.calculateDiskCacheSize(cacheDir);
cache = HttpResponseCache.install(cacheDir, maxSize);
}
return cache;
}
static void close(Object cache) {
try {
((HttpResponseCache) cache).close();
} catch (IOException ignored) {
}
}
}複製程式碼
UrlConnectionDownloader 的磁碟快取是用HttpResponseCache實現的
儘管2種磁碟快取實現的方式不一樣,但是它們的最後結果都是一樣的:
1,磁碟快取的地址: 磁碟快取的地址在:data/data/your package name/cache/picasso-cache /
2,磁碟快取的大小:磁碟快取的大小為 手機磁碟大小的2% ,不超過50M不小於5M。
3, 快取的控制方式一樣:都是在請求的header設定Cache-Control
的值來控制是否快取。
快取清除:
有同學在前一篇文章(圖片載入框架-Picasso最詳細的使用指南)下面留言問怎麼清除快取,這裡統一說一下:
1, 清除記憶體快取:呼叫invalidate方法,如:
Picasso.with(this)
.invalidate("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg");複製程式碼
清除指定url 的記憶體快取。
但是Picasso沒有提供清除全部記憶體快取的方法,那就沒有辦法了嗎?辦法還是有的,LRUCahce 提供了clear方法的,只是Picasso沒有向外部提供這個介面,因此可以通過反射獲取到Picasso的cache欄位,然後呼叫clear方法清除。
2, 清除磁碟快取
很遺憾Picasso沒有提供清除磁碟快取的方法。它沒有提供方法我們就自己想辦法唄。
思路:很簡單,既然我們知道磁碟快取是存在:data/data/your package name/cache/picasso-cache 這個路徑下的,那我們把這個資料夾下面的所有檔案清除不就行了。
實現:
private void clearDiskCache(){
File cache = new File(this.getApplicationContext().getCacheDir(), "picasso-cache");
deleteFileOrDirectory(cache.getPath());
}
public static void deleteFileOrDirectory(String filePath){
if(TextUtils.isEmpty(filePath)){
return;
}
try {
File file = new File(filePath);
if(!file.exists()){
return;
}
if(file.isDirectory()){
File files[] = file.listFiles();
for(int i=0;i<files.length;i++){
deleteFileOrDirectory(files[i].getAbsolutePath());
}
}else{
file.delete();
Log.e("zhouwei","delete cache...");
}
}catch (Exception e){
e.printStackTrace();
}
}複製程式碼
好了,就用上面一段程式碼就可以實現刪除磁碟快取了。
最後
以上就是對Picasso的原始碼分析,程式碼中的關鍵部分也有新增註釋,到此,Picasso的使用和原始碼分析就講完了,還沒有看前一篇文章(圖片載入框架-Picasso最詳細的使用指南)的可以去看一下,如有問題,歡迎留言交流。