前面寫的5篇博客讓我們對Picasso的用法有了很詳細的瞭解,Android的框架層出不窮,每年都會開源出大量的優秀的框架,如果我們只是站在一個使用者的角度去不斷的接觸框架,而不去學習框架底層的實現原理,那樣頂多也只能一直當新手,而不是老司機了,下面我們就從最基本的入手,從源碼的角度去剖析Picasso的工作原理(該分析都是基於Picasso 2.5.2版本).Let’s Go.
官方給出的一個最簡單的用法,一個鏈式調用就能實現一張圖片的請求及展示:
Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);
只需這一行代碼,Picasso就能從我們提供的URL地址中去請求圖片,然後將圖片展示到targetImageView
中,我們就從最簡單的開始,一層層的還原Picasso最底層的本質.
Picasso.with(context)
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
這段代碼就是爲了初始化Picasso,獲取一個Picasso的實例,該類代碼具有很大的共性,很多框架在獲取實例時基本都是採用了 單例模式+建造者模式
- 單例模式特點:
- 採用後加載機制,保證實例只有在被使用的時候才被創建
- 採用雙重檢驗鎖模式,保證了在多個線程同時請求的過程中,只創建唯一的單例對象
- 同步對象爲類本身,保證了該對象的同步範圍在整個java虛擬機中,也就是全局的
- 利用建造者模式創建一個默認的Picasso對象,一般來說使用建造者模式,是因爲構建該對象本身時需要創建其他的類對象,也是保證了多個類的初始化,看代碼
new Builder(context).build()
/** 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);
picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
在該方法中主要對初始化了以下參數:
- Downloader
- 下載工具類,該
Utils.createDefaultDownloader(context);
如果OKHttp可用的話,會返回一個OkHttpDownloader
,否則的話會返回一個UrlConnectionDownloader
(內部使用HttpURLConnection實現)
- 下載工具類,該
Cache
該類的默認實現爲
LruCache
,也就是利用最近最少使用算法進行緩存,內部是一個LinkedHashMap
,這裏要注意的是,一般我們在初始化LinkedHashMap
時,會限制initialCapacity
容量值,但此處是在初始化LruCache
時通過調用Utils.calculateMemoryCacheSize(context)
來計算出最大的堆內存值然後劃分出7分之一,也就是15%作爲容量大小,所以在每次調用set()
方法進行緩存時,都會調用trimToSize()
不停的計算當前可使用的空間大小,超出範圍就刪除之前保存的數據;Utils.calculateMemoryCacheSize(context)
的代碼塊
static int calculateMemoryCacheSize(Context context) { ActivityManager am = getService(context, ACTIVITY_SERVICE); //計算出最大的堆內存值,也就是可用大小值 boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0; int memoryClass = am.getMemoryClass(); if (largeHeap && SDK_INT >= HONEYCOMB) { memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am); } //分配各15%的大小 // Target ~15% of the available heap. return 1024 * 1024 * memoryClass / 7; }
ExecutorService
- 默認實現類
PicassoExecutorService
,也就是ThreadPoolExecutor
子類而已,默認線程數是3,但是PicassoExecutorService
會在不同的網絡狀態下調整線程數,wifi
下爲4個線程,4G
狀態爲3個線程,3G
狀態下爲2個線程,2G
狀態下爲1個線程.
- 默認實現類
- RequestTransformer
- 一個接口,是爲了在圖片展示之前進行預處理,我們之前在Picasso的用法中講到過,詳見Picasso最全使用教程 四
- Stats
- 主要記錄一些信息,比如下載的總大小值,緩存大小值,在之前的博客中有用到過,詳見Picasso最全使用教程 四,用到了
StatsSnapshot
- 主要記錄一些信息,比如下載的總大小值,緩存大小值,在之前的博客中有用到過,詳見Picasso最全使用教程 四,用到了
Dispatcher
負責分發和處理 Action,包括提交、暫停、繼續、取消、網絡狀態變化、重試等等。這個類是我們着重要分析的類,我們先來看一下默認的操作都有哪些
在構造方法中,將之前的
ExecutorService
,Downloader
,Cache
,Stats
進行了賦值在構造方法第一行就創建了
DispatcherThread
類,啓動了一個線程在構造方法中創建一個
DispatcherHandler
,並將DispatcherThread.getLooper()
作爲參數傳入該handler的構造函數中,意味着該Handler處理的數據都是從DispatcherThread
發出的- 初始化並添加了一個對網絡監聽的廣播
上面就是關於.with(context)
所做的分析,我們繼續向下分析.
.load(String url)方法源碼解析
我們現在看.load(String url)
方法都做了什麼操作,其實.load(url)
最後調用的依然是.load(Uri uri)
public RequestCreator load(String path) {
//對path進行判空,省略部分代碼
....
return load(Uri.parse(path));
}
//其實調用的是這個方法
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
所以加載圖片的本質就是我們通過Picasso
調用了load(url)
返回了一個new出來的RequestCreator
示例,同時將Picasso
,uri
地址, 還有resourceId
也就是0,作爲構造參數傳入,下面我們就看看該構造函數都做了哪些操作;
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);
}
- 先判斷Picasso是否已經啓動,如果沒有啓動,就直接拋出異常
- 對picasso進行賦值
這裏又用到了建造者模式使用
Request.Builder()
將創建的Builder
賦值給data
,我們來看該部分的代碼代碼如下:
this.uri = uri; this.resourceId = resourceId; this.config = bitmapConfig; }
我們看到,這裏只是做了賦值操作,並沒有做其他事,
Builder
還有其他幾個構造方法,也都只進行了賦值操作,並未做其他處理,等我們分析完之後,就會明白此處代碼的用意. 貼出來兩個,另一個Builder (Request request)
就不在貼出,部分代碼如下public Builder(Uri uri) { setUri(uri); }
public Builder(int resourceId) { setResourceId(resourceId); }
最重要的方法.into(target)
方法解析
.into(target)
方法是ReqeustCreator
中的方法,所以我們去一窺究竟,其實.into(target)
還是在內部調用的.into(target,callback)
,看代碼
public void into(ImageView target) {
into(target, null);
}
下面我們就着重看一下.into(target,callback)
都做了哪些操作,代碼較多,我們逐一進行分析
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//1 檢測是否在主線程中執行,如果不是就拋異常
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//2 該判斷是只有在uri爲null,並且resourceId爲0的情況下才會執行相應邏輯,取消請求顯示默認圖片,然後結束
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
//3 是否延時加載,默認爲false,只有當調用了 .fit() 纔會執行該邏輯
if (deferred) {
//調用 .fit() 之前,不能調用 .resize(w,h) ,否則拋出異常
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
//target是否已經完全繪製出來,如果沒有,則執行相關操作
if (width == 0 || height == 0) {
//是否設置了默認圖片,在進行延時請求前先展示默認圖片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 加入延時隊列,同時創建一個DeferredRequestCreator去測量target的寬高,結束任務
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
//target已經繪製完成,調用resize方法,並繼續向下執行
data.resize(width, height);
}
//4 創建一個request,並根據request生產一個requestKey
Request request = createRequest(started);
String requestKey = createKey(request);
//5 判斷可否從內存中讀取資源,默認爲可以
if (shouldReadFromMemoryCache(memoryPolicy)) {
//從內存中獲取,如果能獲取到就取消請求操作,並執行callback.onSuccess()的回調,結束任務
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;
}
}
//6 判斷是否設置了默認圖片,在進行請求前先展示默認圖片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//7 這時是的確需要聯網請求了,生成對應的 ImageViewAction,並將 action 添加到 Picasso的請求隊列中
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
部分功能說明已在代碼中標出,但還是用一張示意圖來進行解析比較簡單易懂
圖畫的比較醜,但是看着還算比較清晰,我們在按照代碼,圖示,再用文字描述一遍,讓更有深的認識;
- 檢測是否在主線程中執行,如果不是就拋異常,可能是要更新UI顯示,所以要在主線程中執行代碼邏輯
- 該判定方法
data.hasImage()
容易讓人誤解爲是否已經有圖片獲取好了,但其實是爲了判斷在Builder創建的時候,是否設置了uri
或者resourceId
,如果沒有設置uri
並且resourceId
爲0,說明該請求是一個錯誤請求,就可以加載默認圖片並取消請求操作; - 是否延時加載,默認爲false,只有當調用了
.fit()
纔會執行該邏輯,如果有同學不太理解.fit()
的作用,可翻看我之前寫的博客,其實就是爲了重新計算target的寬高值,而且不能在調用了.resize()
之後再次調用.fit()
- 創建一個request,並根據request生產一個requestKey
- 判斷可否從內存中讀取資源,默認爲
true
,如果有同學不知道怎麼設置跳過內存讀取這一步驟,可翻看我之前寫的博客 - 判斷是否設置了默認圖片,在進行請求前先展示默認圖片
- 這時是的確需要聯網請求了,生成對應的 ImageViewAction,並將 action 添加到 Picasso的請求隊列中
OK,到現在我們已經大致瞭解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);
都做了哪些操作,至於第7步之後又做了哪些操作,我們將繼續分析,還原她的本質,願大家都有美好的一天…