Android之圖片載入框架Picasso原始碼解析
個人開發的微信小程式,目前功能是書籍推薦,後續會完善一些新功能,希望大家多多支援!
前言
picasso是Square公司開源的一個Android圖形快取庫,地址http://square.github.io/picasso/,可以實現圖片下載和快取功能。僅僅只需要一行程式碼就能完全實現圖片的非同步載入,然而這裡我並不討論它的使用,只是圍繞這行件簡簡單單的程式碼,對它的原始碼進行梳理,閱讀picasso原始碼時,並不會非常深入,只是對它的整體流程有個清晰的認識,picasso在很早以前就使用過了,但沒有對它過多的瞭解,趁現在有時間就深入瞭解下,也不枉使用過它。
本篇原始碼解析是基於Picasso 2.5.2版本。
原始碼解析
說到閱讀原始碼,但原始碼這麼多,從哪裡入手呢?這裡我喜歡從使用的方法上入手,picasso載入圖片通常只需要下面這一行程式碼就能實現非同步載入:
Picasso.with(this).load("url").into(imageView);
好了,入口找到了,我們就可以一步步的瞭解它的實現原理,先從Picasso的靜態方法with說起,原始碼如下:
static volatile Picasso singleton = null; public static Picasso with(Context context) { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build();(1) } } } return singleton; }
上面with方法的作用是獲取Picasso例項,在獲取Picasso例項時,使用到了兩種設計模式,一是單例模式,二是建造者模式。使用單例模式時記憶體中只有一個例項從而達到減少記憶體的開支,降低系統的效能開銷,而實現單例模式的手段有很多,上面使用了基於volatile的雙重檢查鎖定的方案,這裡的singleton宣告為volatile型,為什麼需要volatile呢?如果不使用volatile修飾singleton,在多執行緒環境下使用是危險的,在(1)的程式碼可以被分成三部分,這裡面就涉及到重排序的問題,大家有空的話可以自信檢視,這裡只是講到了單例,隨便扯扯。Picasso例項的建立是通過Builder模式實現的,Builder模式使資料的構建和展示進行分離,在Builder類中的build方法,主要是對Picasso的一些引數進行配置。
Picasso.Builder: public Picasso build() { Context context = this.context; /** * 1、建立下載方式 */ if (downloader == null) { downloader = Utils.createDefaultDownloader(context); } /** * 2、構建記憶體快取策略(LruCache) */ if (cache == null) { cache = new LruCache(context); } /** * 3、構建執行緒池 */ if (service == null) { service = new PicassoExecutorService(); } /** * 4、請求轉換器,用於請求前更改請求資訊 */ if (transformer == null) { transformer = Picasso.RequestTransformer.IDENTITY; } /** * 5、快照,統計某一時間Picasso中的各項資料 */ Stats stats = new Stats(cache); /** * 6、分發器 */ Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); /** * 7、建立Picasso例項 */ return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); }
build方法主要是引數的配置,從上面可以看出配置的引數有哪些,這裡稍微瞭解下這些引數的作用。
網路載入方式
private Downloader downloader;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
downloader是Picasso的圖片網路載入方式,DownLoader是一個介面。
可以看出DownLoader介面定義了兩個方法,分別是load和shutdown方法,從方法名也可以看出load方法是用於載入圖片的,shutdown方法是對磁碟和其他資源進行清理,DownLoader介面的實現類有兩個,分別是UrlConnectionDownloader和OkHttpDownloader,這裡可以從Utils的靜態方法createDefaultDownloader(context)方法看出。
Utils.java:
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
private static class OkHttpLoaderCreator {
static Downloader create(Context context) {
return new OkHttpDownloader(context);
}
}
也就是說Picasso內部會通過反射,看看有沒有整合OkHttp,如果集成了就使用OkHttpDownloader,反之使用UrlConnectionDownloader。Picasso的磁碟快取的實現就是在OkHttpDownloader和UrlConnectionDownloader中實現的。
OkHttpDownloader.java:
public OkHttpDownloader(final Context context) {
this(Utils.createDefaultCacheDir(context));
}
public OkHttpDownloader(final File cacheDir) {
this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
}
public OkHttpDownloader(final File cacheDir, final long maxSize) {
this(defaultOkHttpClient());
try {
client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));
} catch (IOException ignored) {
}
}
在建立OkHttpDownloader物件時,會執行Utils的靜態方法createDefaultCacheDir獲取快取的目錄以及calculateDiskCacheSize方法獲取快取大小,最後通過OkHttpClient的setCache方法設定快取。
再看看UrlConnectionDownloader內的磁碟快取實現方式。
UrlConnectionDownloader.java:
static volatile Object cache;
@Override public Downloader.Response load(Uri uri, int networkPolicy) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installCacheIfNeeded(context);
}
//省略XX行程式碼
....
}
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) {
}
}
}
上面在load方法中會先判斷當前Android的版本,HttpURLConnection在Android 4.0之後增加了一個Response Cache,因此這裡會做版本的判斷是否開啟快取。
記憶體快取策略
private Cache cache;
if (cache == null) {
cache = new LruCache(context);
}
Picasso的記憶體快取策略用的是LruCache,只不過Picasso對其進行改造。
小知識:
LruCache中Lru演算法的實現就是通過LinkedHashMap來實現的。LinkedHashMap繼承於HashMap,它使用了一個雙向連結串列來儲存Map中的Entry順序關係,這種順序有兩種,一種是LRU順序,一種是插入順序,這可以由其建構函式public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,對於get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,還做些調整Entry順序連結串列的工作。LruCache中將LinkedHashMap的順序設定為LRU順序來實現LRU快取,每次呼叫get(也就是從記憶體快取中取圖片),則將該物件移到連結串列的尾端。呼叫put插入新的物件也是儲存在連結串列尾端,這樣當記憶體快取達到設定的最大值時,將連結串列頭部的物件(近期最少用到的)移除。
public LruCache(Context context) {
this(Utils.calculateMemoryCacheSize(context));
}
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("Max size must be positive.");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
}
這裡面通過Utils的類方法calculateMemoryCacheSize(context)獲取可用的最大快取數。
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = getService(context, ACTIVITY_SERVICE);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;//(1)
int memoryClass = am.getMemoryClass();//(2)
if (largeHeap && SDK_INT >= HONEYCOMB) {
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);//(3)
}
// Target ~15% of the available heap.
return 1024 * 1024 * memoryClass / 7;
}
在(1)處會去獲取largeHeap是否開啟,largeHeap開啟是通過在manifest中設定largeHeap=true設定的,設定為true後應用可以使用最大記憶體值, Android官方給的建議是,作為程式設計師的我們應該努力減少記憶體的使用,想回收和複用的方法,而不是想方設法增大記憶體。當記憶體很大的時候,每次gc的時間也會長一些,效能會下降。 如果largeHeap沒有開啟或是當前裝置的版本小於3.1使用的是(2)處的am.getMemoryClass()獲取應用正常情況下的記憶體大小;反之執行(3)處,這裡呼叫Utils的靜態內部類ActivityManagerHoneycomb的靜態方法getLargeMemoryClass(am),在該方法中通過ActivityManger的getLargeMemory方法獲取 開啟largeHeap最大的記憶體大小。最後會取它的百分之15的大小。
@Override public void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}
Bitmap previous;
synchronized (this) {
putCount++;
size += Utils.getBitmapBytes(bitmap);
previous = map.put(key, bitmap);
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
}
trimToSize(maxSize);
}
set方法的作用是進行Bitmap的快取,通過LinkedHashMap儲存,每次儲存完畢後會呼叫trimToSize方法,該方法內部會開啟一個無限迴圈,用於判斷當前快取大小是否大於最大快取數,如果大於就執行近期最少用到的進行清楚,具體實現如下:
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= Utils.getBitmapBytes(value);
evictionCount++;
}
}
}
其中evictionCount是回收次數,我們可以看到,如果當前快取size如果大於最大快取數maxSize時,對LinkedHashMap迭代,執行清除操作。
@Override public Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
return null;
}
get(key)方法用於快取的獲取,其中hitCount是命中次數,根據相應的key從LinkedHashMap中獲取對應的Bitmap,如果Bitmap不為空,說明快取命中,直接返回該Bitmap,反之Bitmap為空時,說明快取失效,missCount自增。
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
evictAll的作用是清除記憶體快取。
@Override public final synchronized void clearKeyUri(String uri) {
boolean sizeChanged = false;
int uriLength = uri.length();
for (Iterator<Map.Entry<String, Bitmap>> i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, Bitmap> entry = i.next();
String key = entry.getKey();
Bitmap value = entry.getValue();
int newlineIndex = key.indexOf(KEY_SEPARATOR);
if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {
i.remove();
size -= Utils.getBitmapBytes(value);
sizeChanged = true;
}
}
if (sizeChanged) {
trimToSize(maxSize);
}
}
clearKeyUri(uri)方法通過for迴圈遍歷LinkedHashMap,查詢是否包含該uri,如果存在該Uri,就執行清除對應的快取操作。
執行緒池
private ExecutorService service;
if (service == null) {
service = new PicassoExecutorService();
}
我們知道在UI執行緒不能進行耗時操作,尤其是網路操作,因此 ,訪問網路的操作需要在子執行緒中進行,但開啟 過多的執行緒會降低系統的效能,提高記憶體的開銷,所以我們需要執行緒池來管理這些執行緒,在Picasso中它自己定義了執行緒池的類PicassoExecutorService,PicassoExecutorService繼承自ThreadPoolExecutor。
PicassoExecutorService.java:
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());
}
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);
}
}
private void setThreadCount(int threadCount) {
setCorePoolSize(threadCount);
setMaximumPoolSize(threadCount);
}
@Override
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
implements Comparable<PicassoFutureTask> {
private final BitmapHunter hunter;
public PicassoFutureTask(BitmapHunter hunter) {
super(hunter, null);
this.hunter = hunter;
}
@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());
}
}
}
在PicassoExecutorService執行緒池類中,提供了一個adjustThreadCount方法,在這個方法中通過當前的網路環境,給當前執行緒池分配合理的執行緒,這樣的話將效能和網路開銷儘量降到最低,我們在使用執行緒池時,也可以參考這種做法。 PicassoExecutorService在繼承自ThreadPoolExecutor時,重寫了submit方法,在這個方法中會將Runnable通過PicassoFutureTask包裝,而PicassoFutureTask繼承自FutureTask並實現了Comparable介面,其中實現了Comparable介面的compareTo方法對請求執行緒(BitmapHunter)進行優先順序排序,當優先順序相同時,按照先進先出來排序,否則按照執行緒(BitmapHunter)優先順序排序。
分發器
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
Dispatcher類用於一系列事件的分發,內部開啟執行緒,通過Handler傳送訊息。
final Handler handler;
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
void dispatchCancel(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}
void dispatchPauseTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}
void dispatchResumeTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
}
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
void dispatchRetry(BitmapHunter hunter) {
handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY);
}
void dispatchFailed(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter));
}
void dispatchNetworkStateChange(NetworkInfo info) {
handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));
}
void dispatchAirplaneModeChange(boolean airplaneMode) {
handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE,
airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0));
}
通過上面方法,可以看出Dispatcher的作用是對一系列事件的分發,如果請求提交操作、請求取消操作、請求暫停操作、請求恢復操作、網路狀態變化(通過註冊廣播監聽)、請求成功等等,在後面講載入流程時會講到這裡。
例項化Picasso
build方法中的引數已經全部瞭解完畢,除了Stats和RequestTransformer,Stats類起著快照的所用,主要儲存一些載入圖片時的引數,比如快取命中數、快取失效數、下載總大小、下載圖片解碼後的總大小、下載次數等,而RequestTransformer用於在執行真正請求前的轉換,主要是對請求資訊的更改。最後就是例項化Picasso了。
Picasso.Builder
build:
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
最後build方法會返回Picasso的例項,Picasso的構造器中,有這麼一段程式碼:
int builtInHandlers = 7;
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(builtInHandlers + extraCount);
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);
上面的requestHandler是一個請求來源的集合,我們在使用Picasso時,可以載入網路圖片、asset中的圖片或是本地圖片等等,都是靠這個請求來源的集合,在這個集合已經集成了7種來源途徑,比如說最熟悉的網路載入,就是通過NetworkRequestHandler類來載入的,在建立NetworkRequestHandler物件時,會將Downloader物件傳入進去,而從上面分析網路載入方式已經說了Downloader只是介面,真正實現類是OkHttpDownloader和UrlConnectionDownloader。
載入途徑講完後,我們再看看Picasso構造器另外一個重要的點:
this.cleanupThread = new Picasso.CleanupThread(referenceQueue, HANDLER);
this.cleanupThread.start();
在建立Picasso例項時,會開啟一個執行緒,這裡的HANDLER是主執行緒的Handler。
private static class CleanupThread extends Thread {
private final ReferenceQueue<Object> referenceQueue;
private final Handler handler;
CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
this.referenceQueue = referenceQueue;
this.handler = handler;
setDaemon(true);
setName(THREAD_PREFIX + "refQueue");
}
@Override public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
Action.RequestWeakReference<?> remove =
(Action.RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);
Message message = handler.obtainMessage();
if (remove != null) {
message.what = REQUEST_GCED;
message.obj = remove.action;
handler.sendMessage(message);
} else {
message.recycle();
}
} catch (InterruptedException e) {
break;
} catch (final Exception e) {
handler.post(new Runnable() {
@Override public void run() {
throw new RuntimeException(e);
}
});
break;
}
}
}
void shutdown() {
interrupt();
}
}
CleanupThread執行緒是一個守護執行緒,在後臺開啟無限迴圈,通過Handler傳送訊息給主執行緒將一些無用的請求任務(Action)回收掉,這裡通過WeakReference弱引用方便回收。這裡看看它是如何回收的。通過Handler傳送一個標識為REQUEST_GCED給主執行緒。
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
...
break;
}
case REQUEST_GCED: {
Action action = (Action) msg.obj;
if (action.getPicasso().loggingEnabled) {
log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
}
action.picasso.cancelExistingRequest(action.getTarget());
break;
}
case REQUEST_BATCH_RESUME:
...
break;
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
};
在switch中找到REQUEST_GCED分支,呼叫了Picasso的cancelExistingRequest方法。
private void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
在cancelExistingRequest方法中,從目標請求任務targetToAction集合中移除,並通過dispatcher分發器通知該任務取消,以便做一些清除操作。
載入流程
載入圖片時會呼叫Picasso的load方法:
這裡我們重點看load(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));
}
將String型別的URI轉換成Uri物件,並呼叫load(Uri)方法:
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
這裡只是建立RequestCreator物件,也就說RequestCreator中的into方法才是真正執行載入圖片的操作 。
public void into(ImageView target) {
into(target, null);
}
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()) {//(1)
picasso.cancelRequest(target);//(2)
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 = 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 =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
into方法比較長,這裡一步步進行分析。
into方法:
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
在判斷語句中data是Request.Builder物件,Request類用於構建請求引數。
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);
}
這裡面的resourceId的值是根據Picasso的load方法來決定,呼叫load(int resourceId)方法時,resourceId才會有值,如果載入的是String型的uri、File以及Uri最後都會被轉換成Uri。在(1)呼叫Request的內部類Builder的hasImage方法,此方法用於判斷load方法傳入的Uri或是resourceId是否為空,如果為空執行取消此次的請求,就像(2)處呼叫Picasso的cancelRequest方法。
public void cancelRequest(ImageView view) {
cancelExistingRequest(view);
}
private void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
cancelExistingRequest方法的作用是取消請求任務,通過Dispatcher分發器傳送取消請求事件以便資源的清除。在上面的into方法中setPlaceholder方法起著一個佔位符的作用,當我們呼叫placeholder方法設定佔位圖片時,會在請求結果前顯示該佔位圖。
into方法:
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);
}
deferred預設值為false,當呼叫fit()方法時值為true,fit方法用於調整影象的大小。
into方法:
Request request = createRequest(started);
String requestKey = createKey(request);
createRequest方法用於構建請求類。
private Request createRequest(long started) {
int id = nextId.getAndIncrement();
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());
}
Request transformed = picasso.transformRequest(request);
if (transformed != request) {
// If the request was changed, copy over the id and timestamp from the original.
transformed.id = id;
transformed.started = started;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
}
}
return transformed;
}
到這裡我們的Request請求類建立完畢,在構建完請求類,又呼叫了Picasso的transformRequest方法,而這個方法又會Picasso中的requestTransformer的transformRequest方法,達到在真正執行請求前,還可以更改請求引數,這個在上面講起Picasso.Builder類的build方法時提到過。
Request請求類構建完畢後,通過createKey方法建立key值,這個key可以用於記憶體快取LruCache中LinkedHashMap儲存Bitmap的依據(key)。
into方法:
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;
}
}
shouldReadFromMemoryCache方法用於判斷是否從記憶體中讀取,如果從記憶體中讀取,執行Picasso的quickMemoryCacheCheck方法。
Bitmap quickMemoryCacheCheck(String key) {
Bitmap cached = cache.get(key);
if (cached != null) {
stats.dispatchCacheHit();
} else {
stats.dispatchCacheMiss();
}
return cached;
}
quickMemoryCacheCheck方法根據請求的key值從LruCache中取Bitmap,如果Bitmap為空,說明快取失效,否則快取命中。
回到上面的into方法中,如果快取中的bitmap不為空,執行Picasso的cancelRequest方法取消當前請求,並載入該bitmap並顯示,如果Callback不為空,回撥主執行緒載入成功,最後返回結束請求。
其中Picasso的cancelRequest方法如下:
public void cancelRequest(ImageView view) {
cancelExistingRequest(view);
}
private void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
cancelExistingRequest方法作用取消當前的請求任務,並通過分發器傳送取消請求事件以便清除資源。
into方法:
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
如果不從快取中讀取或是讀取的bitmap為空,就將這次的請求任務(Action)提交。
Picasso.java:
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);
}
enqueueAndSubmit方法中判斷同一個ImageView當請求任務不一致,從請求任務集合rargetToAction中移除,重新新增到rargetToAction,最後呼叫submit方法。
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
submit方法中通過分發器執行dispatchSubmit方法進行任務的提交。
Dispatcher.java
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
通過Handler傳送REQUEST_SUBMIT標示。
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
//省略XX行程式碼
...
default:
Picasso.HANDLER.post(new Runnable() {
@Override
public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
接著呼叫performSubmit方法。
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 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;
}
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());
}
}
最終會判斷暫停任務集合中是否存在該請求任務,如果存在,存入pausedActions集合中並執行返回,此次任務結束。反之會從hunterMap獲取BitmapHunter例項,這個BitmapHunter繼承自Runnable,圖片的網路載入就是通過它來執行的,如果執行的請求任務(執行緒)已經存在,調整該請求任務的優先順序。 如果以上條件都不滿足,就建立BitmapHunter,並提交給PicassoExecutorService這個執行緒池執行。
這裡重點看看BitmapHunter中的靜態方法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);
}
requestHandlers就是我們之前在例項化Picasso時講到的請求來源集合,也就是說這個方法會從請求來源集合中查詢符合此次請求任務的請求類。
BitmapHunter繼承自Runnable,並提交給PicassoExecutorService執行緒池,檢視BitmapHunter的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 (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);
}
}
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
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;
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;
}
run方法中執行hunt方法,在hunt方法中先判斷是否從快取中讀取,當不從快取讀取或是讀取的bitmap為空時,就會通過請求來源類(請求來源集合中的一種)的load方法獲取Bitmap。這裡以NetworkRequestHandler網路請求類的load方法為例。
@Override public Result load(Request request, int networkPolicy) throws IOException {
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的load方法中呼叫Dowloader(OkHttpDownloader或是UrlConnectionDownloader)的load方法獲取Response。最後將獲取到的結果(Bitmap或是InputStream)包裝成Result物件,並返回。
最後回到BitmapHunter執行緒中的hunt方法中。從Result中取出Bitmap,如果bitmap為空,取出InputStream,並將InputStream轉換成Bitmap。拿到bitmap後將它賦值給result,再通過分發器Dispatcher傳送事件。如果result不為空,說明請求成功,執行Dispatcher的dispatchComplete方法通過Handler傳送訊息,最後會呼叫Dispatcher的performComplete(hunter)。
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");
}
}
以上程式碼會根據條件判斷是否執行快取記憶體操作,接著執行batch(hunter)方法。
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);
}
}
通過Handler傳送訊息,最後會呼叫performBatchComplete方法。
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
到這裡通過主執行緒的Handler將我們請求到的結果傳送到主執行緒中並處理。
@Override
public void handleMessage(final 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;
}
//省略XX行程式碼
...
default:
Picasso.HANDLER.post(new Runnable() {
@Override
public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
if (!shouldDeliver) {
return;
}
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();
if (single != null) {
deliverAction(result, from, single);
}
if (hasMultiple) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, from, join);
}
}
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
if (action.isCancelled()) {
return;
}
if (!action.willReplay()) {
targetToAction.remove(action.getTarget());
}
if (result != null) {
if (from == null) {
throw new AssertionError("LoadedFrom cannot be null.");
}
action.complete(result, from);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error();
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
}
}
}
從上面方法一直追蹤下去會發現呼叫action.complete,而這個action是我們在RequestCreator中執行into方法是定義的。
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
也就是說最終會呼叫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);
if (callback != null) {
callback.onSuccess();
}
}
接著呼叫PicassoDrawable的靜態方法setBitmap。
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
}
PicassDrawable繼承自BitmapDrawable,最終將請求到的bitmap封裝成PicassDrawable,通過ImageView的setImageDrawable方法給當前的ImageView載入圖片。
至此Picasso原始碼解析完畢。