圖片庫做爲開發中一個重要的基礎組件,其重要性不言而喻。目前來講,比較流行的圖片庫分別是 Android-Universal-Image-Loader、Picasso、Glide、Fresco。它們的通常特色是:android
這篇文章要分析的是 square/picasso。沒錯,又是 square,誰中你那麼優秀呢?不去管它的舊版本如何,這裏直接分析其最新版本 2.71828 吧。git
picasso,偉大的畫家畢加索,這裏想必也是在表達致敬之意。square 在 github 項目首頁中只用了一句話解釋這個庫。github
一個適用於Android的強大的圖像下載和緩存庫編程
除了 github 項目首頁的介紹外,picasso 還另外提供了一個 github page。在這裏,它進一步介紹了其具有的幾個功能:設計模式
同時該頁面也給出了一些使用例子,如在 Adapter 中使用,圖像變換以及自定義變換、設置 placeholders 等。感興趣的同窗能夠自行前往看一看,不過要注意 Picasso 的實例再也不從 Picasso.with(context) 開始了,也再也不是該頁面所展現的 Picasso.get(),而封裝了一個 PicassoProvider,而後經過它的靜態方法 get() 來獲取。緩存
gradle 中集成,so easy,每一個整 android 的小夥伴都懂的。bash
implementation 'com.squareup.picasso:picasso:2.71828'網絡
前面簡要介紹了下 picasso,下面便開始進入正題源碼分析。不變的原則,從最簡單的主路徑開始分析。分析以前先來看一看其框架圖,是否是似曾相識?併發
picasso 工程裏有提供 sample,做爲典型的例子,這裏選擇了從 SampleListDetailAdapter 拿出一段用於 ListView 的 Adapter 中加載列表圖片的代碼來做爲用於主路徑分析的 demo。app
PicassoProvider.get()
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
.centerInside()
.tag(context)
.into(holder.image);
複製代碼
鏈式調用的一大特色就在於無論多複雜的邏輯,看起來好像都是順序執行的同樣。嗯呵,你可能想到了 RxJava。下面就來逐個逐個分析上面的每個方法的調用。
public final class PicassoProvider {
@SuppressLint("StaticFieldLeak")
private static volatile Picasso instance;
@Initializer
public static Picasso get() {
// 單例實現
if (instance == null) {
synchronized (PicassoProvider.class) {
if (instance == null) {
// 獲取 context
Context autoContext = PicassoContentProvider.context;
if (autoContext == null) {
throw new NullPointerException("context == null");
}
// 經過 Picasso.Builder 來構建 Picasso 實例
instance = new Picasso.Builder(autoContext).build();
}
}
}
return instance;
}
private PicassoProvider() {
throw new AssertionError("No instances.");
}
}
複製代碼
PicassoProvider 經過單例設計模式的方式,在 get() 方法中爲咱們提供了 Picasso 的實例。也就是說,在應用中,一個進程內只有一個 Picasso 實例。
構建 Picasso 實例是須要 Context 的,這裏是從 PicassoContentProvider 處獲取。所以,進一步看看 PicassoContentProvider 又是如何提供 Context。
public final class PicassoContentProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak")
@Nullable static Context context;
@Override public boolean onCreate() {
context = getContext();
return true;
}
......
}
複製代碼
PicassoContentProvider 繼承了 ContentProvider,這是一個很是微妙的實現。其利用了 ContentProvider 會在 Application 初始化的時候同時被初始化的原理,從而初始化 context。不得不服!!!這就避免了開發者還須要對其進行全局初始化的步驟,典型是在應用的 Application#onCreate() 進行初始化。
先來看 Picasso 的構造方法,其主要目的是獲取 ApplicationCotnext,從而避免引發內存泄漏。
public Builder(@NonNull Context context) {
checkNotNull(context, "context == null");
this.context = context.getApplicationContext();
}
複製代碼
再來看 build() 方法。
public Picasso build() {
Context context = this.context;
// 初始化磁盤 Cache,也即初始化 OkHttp 的 Cache
okhttp3.Cache unsharedCache = null;
if (callFactory == null) {
File cacheDir = createDefaultCacheDir(context);
long maxSize = calculateDiskCacheSize(cacheDir);
unsharedCache = new okhttp3.Cache(cacheDir, maxSize);
callFactory = new OkHttpClient.Builder()
.cache(unsharedCache)
.build();
}
// 初始化內存 Cache
if (cache == null) {
cache = new PlatformLruCache(Utils.calculateMemoryCacheSize(context));
}
// 初始化線程池
if (service == null) {
service = new PicassoExecutorService(new PicassoThreadFactory());
}
// 初始化內存 Cache 統計器
Stats stats = new Stats(cache);
// 初始化分發器
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, cache, stats);
// new 出 Picasso 實例
return new Picasso(context, dispatcher, callFactory, unsharedCache, cache, listener,
requestTransformers, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled,
loggingEnabled);
}
複製代碼
磁盤 Cache: 對了,磁盤在手機上通常指的就是閃存 Flash 或者外置的 SD Card 了。Picasso 沒有單獨實現一個磁盤緩存,而是利用了 OkHttp 的緩存管理,使得緩存的有效期與 Http 協議同步。這樣作的好處是保證了圖片的更新的及時性,而不像其餘磁盤緩存經過硬設一個緩存過時時間好比 7 天來判斷其是否過時。固然,它可能也會增長多一些網絡請求。另外,默認建立的磁盤 Cache 的路徑爲 App 的內部 data 目錄下 cache/picasso-cache,而大小通常爲磁盤空間總大小的 2%,可是也不能超過 50 M。
內存 Cache: PlatformLruCache,其內部經過 LruCache 來實現的內存 Cahce。而其大小被限制在應用可得到的最大 heap 內存的 15%。
/** A memory cache which uses a least-recently used eviction policy. */
final class PlatformLruCache {
final LruCache<String, BitmapAndSize> cache;
......
}
複製代碼
線程池: PicassoExecutorService,其繼承自ThreadPoolExecutor,在其構造函數中設置了其核心線程數以及最大線程數爲 3 個,這就限制了其最多隻能有 3 個線程能同時工做。
class PicassoExecutorService extends ThreadPoolExecutor {
private static final int DEFAULT_THREAD_COUNT = 3;
PicassoExecutorService(ThreadFactory threadFactory) {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), threadFactory);
}
......
}
複製代碼
Stats: 內存 Cache 統計器,統計 Cache 的各個指標,好比命中,未命中等。
分發器Dispatcher: 似曾相識的概念,在 OkHttp 中有見到過。這裏也是同樣,主要管理任務的分發與調度。包括提交,取消,重試以及網絡變化的監聽策略。
Picasso及其構造函數: Picasso 的構造會使得其持有 Dispatcher,Cache,PlatformLruCache,RequestTransformer 隊列,RequestHandler隊列等,這些引用都會保存在 Picasso 內部。除此以外,其自身也會初始化一些 RequestHandlers 用於處理已知的各類資源類型。當咱們傳遞不一樣的資源類型的請求時,其便會選擇相應的 RequestHandler 來進行處理。
另外,這裏還注意到,Picasso 實現了 LifecycleObserver 接口。這意味着咱們能夠將其註冊到 LifecycleOwner 的 LifecycleRegistry 中,從而實現生命週期的聯動。
public class Picasso implements LifecycleObserver {
......
Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory,
@Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, @Nullable Listener listener,
List<RequestTransformer> requestTransformers, List<RequestHandler> extraRequestHandlers,
Stats stats, @Nullable Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled,
boolean loggingEnabled) {
this.context = context;
this.dispatcher = dispatcher;
this.callFactory = callFactory;
this.closeableCache = closeableCache;
this.cache = cache;
this.listener = listener;
this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers));
this.defaultBitmapConfig = defaultBitmapConfig;
// Adjust this and Builder(Picasso) as internal handlers are added or removed.
int builtInHandlers = 8;
int extraCount = extraRequestHandlers.size();
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
......
allRequestHandlers.add(ResourceDrawableRequestHandler.create(context));
allRequestHandlers.add(new ResourceRequestHandler(context));
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(callFactory, stats));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.stats = stats;
this.targetToAction = new LinkedHashMap<>();
this.targetToDeferredRequestCreator = new LinkedHashMap<>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
}
......
}
複製代碼
到這裏,Picasso 的運行環境已經基本初始化好了,接下來就是等待用戶的請求發送了。
前面經過 PicassoProvider.get() 構建了 Picasso 實例,同時也初始化了 Picasso 的運行環境,接下來就是一系列參數的設置。經過 Picasso 的 load() 方法即可獲得 Request 的建立器 RequestCreator,咱們將須要的參數都送進 RequestCreator 以便能最終構建出咱們所須要的那個 Request。
load() 重載了 4 個不一樣的方法,分別對應了加載不一樣的數據源,4 個方法以下。
public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}
public RequestCreator load(@Nullable String path) {
.......
return load(Uri.parse(path));
}
public RequestCreator load(@Nullable File file) {
......
return load(Uri.fromFile(file));
}
public RequestCreator load(@DrawableRes int resourceId) {
......
return new RequestCreator(this, null, resourceId);
}
複製代碼
能夠看出前 3 個其實都是同一個,形參都將轉成 Uri。而從上面的代碼也能夠看出,最終都是 new 出一個 RequestCreator 實例。而且這裏 Uri 與 resourceId 是互斥的,必定只是其中的一個。再來看看 RequestCraetor 的構造方法。
RequestCreator(Picasso picasso, @Nullable Uri uri, int resourceId) {
......
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
複製代碼
這裏最主要就是構造了 Request.Builder 實例,並存儲在 data 屬性中。其主要是用於在設置好參數後,進一步構造出 Request 實例。
public RequestCreator placeholder(@DrawableRes int placeholderResId) {
......
this.placeholderResId = placeholderResId;
return this;
}
public RequestCreator error(@DrawableRes int errorResId) {
......
this.errorResId = errorResId;
return this;
}
複製代碼
就是簡單的記錄下 placeholderResId 以及 errorResId。
resizeDeimen() 其實主是將參數 dimen 所表達的數值轉成實際大小,再調用 resize() 方法,因此這裏直接看 resize() 方法便可。
public RequestCreator resize(int targetWidth, int targetHeight) {
data.resize(targetWidth, targetHeight);
return this;
}
public RequestCreator centerCrop() {
data.centerCrop(Gravity.CENTER);
return this;
}
複製代碼
這裏的 data 是 Request.Builder 實例,因此這裏是將參數送進了 RequestBuilder 中去了。和上面的 2 個方法比較一下,爲何參數會被存儲在不一樣的地方?
public RequestCreator tag(@NonNull Object tag) {
data.tag(tag);
return this;
}
複製代碼
tag 用來標記一個 Request ,能夠是任意一個對象,如 String,Context,Activity,Fragment等。咱們能夠經過 tag 來 pause,resume 以及 cancel 具有相同 tag 的 Request。但要注意的是 tag 與 Request 的生命週期是同樣長的,必定程度上可能會存在內存泄漏的風險。
關於 RequestCreator 的參數還有不少能夠設置的,這裏就不一一詳細進行講解了。僅經過類圖將其列舉出來以下。
其實 into() 也是有不少重載的,好比into(BitmapTarget),into(RemoteViews, int viewId, int notificationId,Notification notification) 等。也就是說它還能夠將圖片顯示到 Notification 中以及 BitmapTarget 接口。關於 BitmapTarget 這個接口是什麼意思呢?這裏看一看其在註釋中舉的一個例子就能夠明白了。
public class ProfileView extends FrameLayout implements BitmapTarget {
Override
public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
setBackgroundDrawable(new BitmapDrawable(bitmap));
}
Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
setBackgroundDrawable(errorDrawable);
}
Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
setBackgroundDrawable(placeHolderDrawable);
}
}
複製代碼
也就是說咱們只實現這個接口,而後在 onBitmapLoaded() 的回調中本身選擇應該如何顯示該圖片。
而與 into() 相同級別的還有另外一個方法 fetch(),這個方法主要是隻下載圖片而不展現到 target 上去。這也是一個極爲有用的方法,典型的場景就是預加載。
關於 into() 以外的東西就瞭解這麼多,下面將重要放到 into(ImageView) 上來。into(ImageView) 其只是進一步調用了 into(ImageView,Callback),所以直接來看後面這個重載方法。
方法有點長,但其實很簡單,不要畏難,耐着性子看完,增長了比較詳細的註釋了。
public void into(@NonNull ImageView target, @Nullable Callback callback) {
long started = System.nanoTime();
// 檢查是否爲主線程,Picasso 要求請求必須從主線程發起。
checkMain();
// target 合法性檢查
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
// 若是沒有設置 url 或者 resId,則取消當前 ImageView 的 Request,並直接返回
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
// 若是調用了 fit() ,也就是使得圖片大小爲適應 ImageView 的大小
if (deferred) {
if (data.hasSize()) {
// fit() 與 resize() 不能同時調用,不然會拋出異常
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0) {
// ImageView 沒有具體的高度或者還沒被渲染出來的狀況下
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
// 構建 Request
Request request = createRequest(started);
if (shouldReadFromMemoryCache(request.memoryPolicy)) {
// 容許從內存緩存讀取則優先從內存緩存讀取
Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key);
if (bitmap != null) {
// 緩存中存在有須要的圖片,則取消並返回結果
picasso.cancelRequest(target);
RequestHandler.Result result = new RequestHandler.Result(bitmap, MEMORY);
setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
// 設置 PlasceHolder
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 建立 ImageViewAction 並提交
Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
callback);
picasso.enqueueAndSubmit(action);
}
複製代碼
方法裏的代碼都加了詳細的註釋,而且總結流程圖以下。
流程圖上看也確實步驟比較多,但其實關鍵步驟就 2 個。其一,先判斷是否內存緩存是否已經存儲圖片了,若是有就用內存緩存的圖片。其二,若是沒有就構造一個 ImageViewAction 來提交到 Picasso 的請求隊列。說到緩存,這裏有一個重要的知識點,關於緩存的 key,它是在 Request 被 build 出來後一塊兒被建立的,其在 Request 的 createKey() 方法中。
private String createKey(StringBuilder builder) {
Request data = this;
// key 的主要部分
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
// key 的分隔線
builder.append(KEY_SEPARATOR);
// 參數
if (data.rotationDegrees != 0) {
builder.append("rotation:").append(data.rotationDegrees);
if (data.hasRotationPivot) {
builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
}
builder.append(KEY_SEPARATOR);
}
if (data.hasSize()) {
builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
builder.append(KEY_SEPARATOR);
}
if (data.centerCrop) {
builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}
//noinspection ForLoopReplaceableByForEach
for (int i = 0, count = data.transformations.size(); i < count; i++) {
builder.append(data.transformations.get(i).key());
builder.append(KEY_SEPARATOR);
}
return builder.toString();
}
複製代碼
代碼看起來也有點長,但簡單理解下,cache key 主要由主體部分 + 參數部分構成。主體部分通常是指 url 或者 resId,而參數部分主要就是其是否有旋轉,resize 等。那這樣一來就獲得一個結論,就是同一個圖片在內存中可能由於要顯示的參數不同會有不一樣的緩存。這樣作的好處天然是提升了顯示速度,而壞處天然就是佔用的內存會較多。
在 into(ImageView) 中,若是沒有命中 Cache,最後一步就是封裝 ImageViewAction,並提交給 picasso,由其進一步處理。
Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
callback);
picasso.enqueueAndSubmit(action);
複製代碼
先來看一看 enqueueAndSubmit
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (targetToAction.get(target) != action) {
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
}
submit(action);
}
複製代碼
Picasso 中用 targetToAction 來保存了 target 與 action 之間的關係。這裏首先從裏面找 target 是否已經關聯了其餘的 action,若是已經關聯了則先要將其取消。這裏的場景就是特別適合 Adapter 了。在 Adapter 中因爲 ImageView 是會被重用的,那麼就必定會存在一個 target 對應不一樣的 action 的狀況。
這裏先來看一下任務的取消 cancelExistingRequest() 的實現,而後再去看 submit()。
void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
// 從 dispatcher 中取消
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
// 若是有延遲的 request,也要將其取消,這裏假設沒有吧。
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}
複製代碼
因此主要是將 Dispatcher 中的 action 取消。Dispatcher 中主要用了一個 HandlerThread 來管理,而且全部關於請求的提交,取消等都是經過消息來執行的。以下所示。
static final int REQUEST_SUBMIT = 1;
static final int REQUEST_CANCEL = 2;
static final int HUNTER_COMPLETE = 4;
static final int HUNTER_RETRY = 5;
static final int HUNTER_DECODE_FAILED = 6;
static final int NETWORK_STATE_CHANGE = 9;
static final int AIRPLANE_MODE_CHANGE = 10;
static final int TAG_PAUSE = 11;
static final int TAG_RESUME = 12;
static final int REQUEST_BATCH_RESUME = 13;
複製代碼
消息如何執行,這裏就不詳細說明了。Dispatcher 中的 action 取消明顯就是經過 REQUEST_CANCEL 來執行,而 REQUEST_CANCEL 對應的就是 performCance()。
void performCancel(Action action) {
// 取消對應的 hunter
String key = action.request.key;
BitmapHunter hunter = hunterMap.get(key);
if (hunter != null) {
hunter.detach(action);
if (hunter.cancel()) {
hunterMap.remove(key);
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId());
}
}
}
// 若是在 pause 隊列中,則移除
if (pausedTags.contains(action.getTag())) {
pausedActions.remove(action.getTarget());
if (action.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId(),
"because paused request got canceled");
}
}
// 失敗隊列中包含了,也移除
Action remove = failedActions.remove(action.getTarget());
if (remove != null && remove.picasso.loggingEnabled) {
log(OWNER_DISPATCHER, VERB_CANCELED, remove.request.logId(), "from replaying");
}
}
複製代碼
hunter 是什麼呢?hunter 的中文意思是獵人或者搜尋者的意思。這裏就是負責執行實際請求的類 BitmapHunter,它實現了 Runnable 接口,是 Dispatcher 中 ExecuteService 線程池的實際調度單位。這裏先看一下它的 cancel(),後面還會講它的 submit()。
boolean cancel() {
return action == null
&& (actions == null || actions.isEmpty())
&& future != null
&& future.cancel(false);
}
複製代碼
future 是 Future<?> 類型,它是 ExecuteService.submit() 的返回值,這裏咱們能夠經過其 cancel() 方法來取消 BitmapHunter 的執行。
回到 Picasso 的 enqueueAndSubmit() 中來,下一步就是 submit(action)。submit() 將 action 經過 Dispatcher.dispatchSubmit()提交到 Dispatcher ,Dispatcher 將其轉化成消息 REQUEST_SUBMIT 發送到消息隊列中,而 REQUEST_SUBMIT 所對應的處理方法是 performSubmit(),所以從 performSubmit() 的代碼開始分析 Request 的提交。
void performSubmit(Action action, boolean dismissFailed) {
......
// 已經提交了
BitmapHunter hunter = hunterMap.get(action.request.key);
if (hunter != null) {
hunter.attach(action);
return;
}
// 線程池已經關閉
if (service.isShutdown()) {
......
return;
}
// 建立一個新的 BitmapHunter
hunter = forRequest(action.picasso, this, cache, stats, action);
// 將 hunter 提交給線程池
hunter.future = service.submit(hunter);
// 已經提交的 hunter 保存在 hunterMap 中
hunterMap.put(action.request.key, hunter);
......
}
複製代碼
代碼中關鍵的邏輯是建立 hunter 並提交給線程 ExecuteService。這裏須要關注一下 forRequest 的實現,其裏面有一個關鍵步驟就是爲 Request 選擇一下合適的 Handler。
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher,
PlatformLruCache cache, Stats stats, Action action) {
Request request = action.request;
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
// 選擇合適的 Handler 並建立 BitmapHunter
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);
}
複製代碼
假設這裏給的是 url 地址,且是以 http / https 開頭的,那被選中的就是 NetworkRequestHandler。
當一個 Runnable 被提交到線程池 ExecuteService 中去以後,接下來就是等待其被度到,也就是執行其方法 run()。
@Override public void run() {
try {
......
result = hunt();
......
} catch (NetworkRequestHandler.ResponseException e) {
......
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}
複製代碼
精簡一下代碼,關鍵就是調用 hunt()。
Result hunt() throws IOException {
// 1.先判斷緩存中是否存在,若是存在則直接返回
if (shouldReadFromMemoryCache(data.memoryPolicy)) {
Bitmap bitmap = cache.get(key);
if (bitmap != null) {
......
return new Result(bitmap, MEMORY);
}
}
......
final AtomicReference<Result> resultReference = new AtomicReference<>();
final AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
try {
// 2. 經過 NetworkRequestHandler 加載圖片,並等待其返回。
requestHandler.load(picasso, data, new RequestHandler.Callback() {
@Override public void onSuccess(@Nullable Result result) {
resultReference.set(result);
latch.countDown();
}
@Override public void onError(@NonNull Throwable t) {
exceptionReference.set(t);
latch.countDown();
}
});
latch.await();
} catch (InterruptedException ie) {
......
}
......
Bitmap bitmap = result.getBitmap();
if (bitmap != null) {
......
// 3.執行全部的 Transformation,獲取最終的 result
List<Transformation> transformations = new ArrayList<>(data.transformations.size() + 1);
if (data.needsMatrixTransform() || result.getExifRotation() != 0) {
transformations.add(new MatrixTransformation(data));
}
transformations.addAll(data.transformations);
result = applyTransformations(picasso, data, transformations, result);
.....
}
return result;
}
複製代碼
關鍵也就是註釋中 3 個步驟: (1) 先判斷緩存中是否存在,若是存在則直接返回。這個在 into() 中也有判斷。 (2) 經過 NetworkRequestHandler 加載圖片,並等待其返回。關於 NetworkRequestHandler 的 load() 方法的實現,其實就是 OkHttp 的請求過程,這裏就沒有必要展開了。同時這裏使用了 CountDownLatch 類來使得當前線程能夠等待異步線程執行完成。這裏簡要了解一下其原理,即當 CountDownLatch.await() 使得當線程進入 awaiting 狀態後,只有經過 countDown() 使得其計數變成 0 後纔會喚醒當前的線程。具體原理圖以下。
另外還使用了 AtomicReference,相似的還有 AtomicInteger 等,主要做用即是在併發編程中保證數據操做的原子性。這些都是併發編程的內容,這裏只做瞭解便可。
(3) 執行全部的 Transformation,獲取最終的 result。這裏的 Transformation 就是經過 RequestCreator 的 transform() 方法所添加的。Transformation 是一個接口,通常咱們可在其方法 transform() 中進行自定義處理,處理完後再將新的圖片返回。
到這裏,請求的提交到執行就分完成了。至此,整個從 Picasso 的初始化到構建 Request 設置參數,再到提交 request 以及 執行 request 都分析完畢了。
Picasso 整體來講,其源碼難度較小,分析起來也比較輕鬆。其中最重要的兩個細節點在於:
下面再以 Picasso 的框架圖來完成這篇文章的所有總結。
最後,感謝你能讀到並讀完此文章。受限於做者水平有限,若是存在錯誤或者疑問都歡迎留言討論。若是個人分享可以幫助到你,也請記得幫忙點個贊吧,鼓勵我繼續寫下去,謝謝。