參考文章:
Picasso源碼解析
一、簡介
介紹:Picasso,可譯為“畢加索”,是Android中一個圖片加載開源庫。
源碼地址:https://github.com/square/picasso
二、功能特點
1、功能列表
2、功能介紹
2.1 圖片的異部加載
2.2 圖片轉(zhuǎn)換
使用最少的內(nèi)存完成復(fù)雜的圖片轉(zhuǎn)換,轉(zhuǎn)換圖片以適合所顯示的ImageView,來減少內(nèi)存消耗
也可以customTransformer方法,進行圖片的具體調(diào)整。
2.3?加載過程 & 錯誤處理
Picasso支持加載過程中和加載錯誤時顯示對應(yīng)圖片。
2.4 在Adapter中的回收不在視野的ImageView和取消已經(jīng)回收的ImageView下載進程
2.5 從不同資源源加載
支持多種數(shù)據(jù)源 網(wǎng)絡(luò)、本地、資源、Assets 等
//加載資源文件
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
//加載本地文件
Picasso.with(context).load(new File("/images/oprah_bees.gif")).into(imageView2);
2.6 自動添加磁盤和內(nèi)存二級緩存功能
2.7 支持優(yōu)先級處理
每次任務(wù)調(diào)度前會選擇優(yōu)先級高的任務(wù),比如 App 頁面中 Banner 的優(yōu)先級高于 Icon 時就很適用。
2.8 支持飛行模式、并發(fā)線程數(shù)根據(jù)網(wǎng)絡(luò)類型而變
手機切換到飛行模式或網(wǎng)絡(luò)類型變換時會自動調(diào)整線程池最大并發(fā)數(shù),比如 wifi 最大并發(fā)為 4, 4g 為 3,3g 為 2
2.9 “無”本地緩存
無”本地緩存,不是說沒有本地緩存,而是 Picasso 自己沒有實現(xiàn),交給了 Square 的另外一個網(wǎng)絡(luò)庫 okhttp 去實現(xiàn),這樣的好處是可以通過請求 Response Header 中的 Cache-Control 及 Expired 控制圖片的過期時間。
三、Picasso源碼解析
Picasso.with(this).load(imageUrl).into(imageView)
3.1? with
一個單例模式,為了保證線程安全,使用的是雙重校驗鎖。在Picasso創(chuàng)建的過程中又使用了Builder模式,最大的特點就是鏈式調(diào)用,使調(diào)用者的代碼邏輯簡潔,同時擴展性非常好。下面我看一下new Builder()中的方法。
在Builder的構(gòu)造方法中就只是獲取到當(dāng)前應(yīng)用級別的上下文,也就說明了Picasso是針對應(yīng)用級別的使用,不會是隨著Activity或是Fragment的生命周期而產(chǎn)生變化,只有當(dāng)當(dāng)前的應(yīng)用退出或是銷毀時Picasso才會停止它的行為。
接下來,我們看看build方法中到底做了什么事情。
在這個方法中主要初始化了Downloader、LruCache、PicassoExecutorService、RequestTransformer、Stats、Dispatcher、并且返回一個Picasso對象。
3.1.1? downloader 下載器
首先,我們先看downloader下載器,如果downloader==null的話,就會執(zhí)行Utils.createDefaultDownloader(context)方法去創(chuàng)建一個下載器。
createDefaultDownloader方法中首先使用java反射機制來查找項目中是否使用了okhttp網(wǎng)絡(luò)加載框架,如果使用了則會使用okhttp作為圖片的加載方式,如果沒有使用,則會使用內(nèi)置的封裝加載器UrlConnectionDownloader。
注:由于okhttp3的包名已更換,所以在這里都是使用內(nèi)置的封裝下載器,這個是一個小bug等待完善。當(dāng)修復(fù)之后Picasso+okhttp3則是最理想的加載方式。
當(dāng)然我們自己也可以自定義下載器,使用okhttp3 作為加載器。代碼如下:
OkHttp3Downloader的下載地址:
https://github.com/JakeWharton/picasso2-okhttp3-downloader
接下來我們先分析OkHttpDownloader,然后在分析UrlConnectionDownloader,看看他們源碼中到底實現(xiàn)了什么東西。
OkHttpDownloader
OkHttpDownloader的構(gòu)造方法:
在構(gòu)造方法中通過Utils.createDefaultCacheDir(context)設(shè)置了文件緩存
private static final intMIN_DISK_CACHE_SIZE=5*1024*1024;// 5MB
private static final intMAX_DISK_CACHE_SIZE=50*1024*1024;// 50MB
通過Utils.calculateDiskCacheSize(cacheDir),設(shè)置緩存的大小。
其中StatFs用于獲取存儲空間。
getBlockCount():文件系統(tǒng)中總的存儲區(qū)塊的數(shù)量;
getBlockSize():文件系統(tǒng)中每個存儲區(qū)塊的字節(jié)數(shù);
最大緩存大小是50M。
通過defaultOkHttpClient()方法設(shè)置的OkHttpClient請求客戶端。
UrlConnectionDownloader? 接下來分析UrlConnectionDownloader? 這個默認的下載器。
UrlConnectionDownloader中使用的是系統(tǒng)自帶的HttpURLConnection進行網(wǎng)絡(luò)請求的。
這個設(shè)置的緩存大小是和OkHttpDownloader大小是一致的。
static final intDEFAULT_WRITE_TIMEOUT_MILLIS=20*1000;// 20s
static final intDEFAULT_CONNECT_TIMEOUT_MILLIS=15*1000;// 15s
3.1.2 LruCache
Retrofit的默認文件緩存采用的是LruCache。
LruCache的構(gòu)造方法如下:
通過Utils.calculateMemoryCacheSize(context),設(shè)置了緩存大小。
activityManager.getLargeMemoryClass(),為單個應(yīng)用的最大內(nèi)存使用。
LruCache的內(nèi)部實現(xiàn)是采用的LinkedHashMap,來保存緩存圖片。
LinkedHashMap:它繼承與HashMap、底層使用哈希表與雙向鏈表來保存所有元素,
LinkedHashMap是Hash表和鏈表的實現(xiàn),并且依靠著雙向鏈表保證了迭代順序是插入的順序。雙向循環(huán)鏈表。
HashMap:它根據(jù)鍵的HashCode值存儲數(shù)據(jù),根據(jù)鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得數(shù)據(jù)的順序是完全隨機的。
區(qū)別在于HashMap并不是按插入次序順序存放的,而LinkedHashMap是順序存放的。
關(guān)于HashMap和LinkedHashMap的源碼分析,我們?nèi)蘸笤斀狻?/b>
我們首先分析LruCache的set方法。
在set方法中,最終調(diào)用了map.put()方法,將數(shù)據(jù)放到Hash表里面。在這個方法的最后有一個trimToSize(maxSize),他到底實現(xiàn)了什么尼?,首先我們看看它的源碼實現(xiàn)。
從源碼中,我們看出,當(dāng)所插入的元素大小size大于maxSize時,LinkHashMap就把最舊的一個元素刪除掉。get方法相對簡單,我們就看一下源碼實現(xiàn)。
LruCache.get()方法:
3.1.3? PicassoExecutorService線程池
PicassoExecutorService的構(gòu)造方法如下圖所示:
PicassoExecutorService繼承的是ThreadPoolExecutor線程池
談到線程池,我們先了解一下線程池的有點:
重用線程池中的線程, 避免因為線程的創(chuàng)建和銷毀所帶來的性能開銷.
有效控制線程池中的最大并發(fā)數(shù),避免大量線程之間因為相互搶占系統(tǒng)資源而導(dǎo)致的阻塞現(xiàn)象.
能夠?qū)€程進行簡單的管理,可提供定時執(zhí)行和按照指定時間間隔循環(huán)執(zhí)行等功能.
ThreadPoolExecutor 的配置參數(shù)
corePoolSize: 線程池的核心線程數(shù),默認情況下, 核心線程會在線程池中一直存活, 即使處于閑置狀態(tài). 但如果將allowCoreThreadTimeOut設(shè)置為true的話, 那么核心線程也會有超時機制, 在keepAliveTime設(shè)置的時間過后, 核心線程也會被終止.
maximumPoolSize: 最大的線程數(shù), 包括核心線程, 也包括非核心線程, 在線程數(shù)達到這個值后,新來的任務(wù)將會被阻塞.
keepAliveTime: 超時的時間, 閑置的非核心線程超過這個時長,講會被銷毀回收, 當(dāng)allowCoreThreadTimeOut為true時,這個值也作用于核心線程.
unit:超時時間的時間單位.
workQueue:線程池的任務(wù)隊列, 通過execute方法提交的runnable對象會存儲在這個隊列中.
threadFactory: 線程工廠, 為線程池提供創(chuàng)建新線程的功能.
handler: 任務(wù)無法執(zhí)行時,回調(diào)handler的rejectedExecution方法來通知調(diào)用者.
在這個構(gòu)造方法中,我們重點了解PriorityBlockingQueue和Utils.PicassoThreadFactory()兩個類或者功能方法。
PriorityBlockingQueue:它是無界阻塞隊列,容量是無限的,它使用與類PriorityQueue相同的順序規(guī)則。它是線程安全的,是阻塞的,具體詳解會在簡書數(shù)據(jù)結(jié)構(gòu)中了解。
PicassoThreadFactory()最終使用的是PicassoThread線程工廠。我們簡單了解PicassoThread的實現(xiàn)。
3.1.4 RequestTransformer
RequestTransformer主要是對RequestCreator創(chuàng)建的Request進行轉(zhuǎn)換,默認對Request對象不做處理。源碼中也證實了這一點。
3.1.5 Stats 圖片的狀態(tài)
Stats的構(gòu)造方法如下:stats主要是用來統(tǒng)計緩存,下載數(shù)量等數(shù)據(jù),一言以蔽之,就是保存圖片的一些狀態(tài)信息。
HandlerThread的詳解請閱讀handlerThread詳解
HandlerThread的主要優(yōu)點在于他是用的是子線程的Looper,所以說不占用主線程度 資源。
Stats里面自己實現(xiàn)了一個Handler,代碼如下:
3.1.6? Dispatcher? 核心類
這個類在這里起到了一個調(diào)度器的作用,圖片要不要開始下載以及下載后Bitmap的返回都是通過這個調(diào)度器來執(zhí)行的,后面進行進行詳細分析。我們先看一下Dispathcher的核心構(gòu)造方法。
控制的中心,控制線程的加載和取消、網(wǎng)絡(luò)監(jiān)聽、消息處理等。
幾個重要的參數(shù),我們上面已經(jīng)介紹了。主要簡單介紹DispatcherThread和DispatcherHandler。
DispatcherThread是一個HandlerThread,DispatcherHandler是自定義的消息分發(fā)的。源碼如下:
我們以dispatchSubmit為例。最終會調(diào)用Dispatcher的dispatchSubmit()方法。
Dispatcher的dispatchSubmit()方法主要是獲取BitmapHunter實例,由這個實例來執(zhí)行實際的下載操作。BitmapHunter本身是Runnable的一個實現(xiàn),而這個實例最終是交由Picasso線程池進行運行的。這個實例最終是要放到this.hunterMap=newLinkedHashMap(),循環(huán)雙向隊列中。
那么這個BitmapHunter加載圖片成功或失敗后是怎么通知UI的呢?我們前面提到Dispatcher在Picasso中起到了一個調(diào)度器的作用,當(dāng)圖片加載完畢后自然也是通過這個調(diào)度器來更新UI,上面我們得到BitmapHunter的run方法會執(zhí)行響應(yīng)的下載任務(wù),那么我們就去這個run方法中去看看。
我們可以看到成功就會調(diào)用dispatcher.dispatchComplete(this)方法,失敗就會調(diào)用dispatcher.dispatchFailed(this)方法。接下來我們就去Dispather方法中看看就這個是如何實現(xiàn)的。源碼如下:
經(jīng)過handler消息處理后,就會執(zhí)行dispatcher.performComplete(hunter)或者dispatcher.performError(hunter, false)。
圖片下載完成之后,首先放到LruCache中,其實就是把操作先暫存在一個list中,等空閑的時候再拿出來處理,這樣做得好處也是盡量減少主線程的執(zhí)行時間,一方面防止ANR,另一方面快速返回,響應(yīng)頁面的其他渲染操作,防止卡頓用戶界面。然后下載任務(wù)從hunterMap刪除。然后執(zhí)行batch(hunter)方法。
private static final intBATCH_DELAY=200;// ms
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH,BATCH_DELAY);
延時200毫秒之后,就來到了handlerMessage方法中。最終執(zhí)行dispatcher.performBatchComplete()方法。
這個mainThreadHandler是在Dispatcher實例化時由外部傳遞進來的,我們在前面的分析中看到,Picasso在通過Builder創(chuàng)建時會對Dispatcher進行實例化,在那個地方將主線程的handler傳了進來,我們回到Picasso這個類,看到其有一個靜態(tài)成員變量HANDLER,這樣我們也就清楚了。
執(zhí)行到這里,圖片已經(jīng)馬上出來了,hunter.picasso.complete(hunter),Picasso中一個Action提供了請求前后的銜接工作,對于我們現(xiàn)在的情況,Picasso使用了ImageViewAction來進行處理,也就是在ImageViewAction中的complete方法完成了最后的圖片渲染工作。
最后調(diào)用了PicassoDrawable.setBitmap(target,context,result,from,noFade,indicatorsEnabled)方法。
最后執(zhí)行PicassoDrawable,從這個構(gòu)造方法中,我們就明白了placeholder是如何設(shè)置的啦。
在PicassoDrawable方法中,實現(xiàn)了這個功能。
dispatcher.performError(hunter, false)就不帶大家詳細分析了。最后后調(diào)用ImageViewAction的error方法。
至此Dispather分析完畢,至此我們留下一個疑問Dispather.dispatchSubmit(Action action),從哪里開始調(diào)用的。
3.2 load()方法
接下來我們分析,Picasso中的load方法,圖片是如何進行網(wǎng)絡(luò)請求的。
待續(xù)。。。。。