參考文章:
Picasso源碼解析
一豺撑、簡(jiǎn)介
介紹:Picasso,可譯為“畢加索”,是Android中一個(gè)圖片加載開(kāi)源庫(kù)紊婉。
源碼地址:https://github.com/square/picasso
二、功能特點(diǎn)
1辑舷、功能列表
2喻犁、功能介紹
2.1 圖片的異部加載
2.2 圖片轉(zhuǎn)換
使用最少的內(nèi)存完成復(fù)雜的圖片轉(zhuǎn)換,轉(zhuǎn)換圖片以適合所顯示的ImageView何缓,來(lái)減少內(nèi)存消耗
也可以customTransformer方法肢础,進(jìn)行圖片的具體調(diào)整。
2.3?加載過(guò)程 & 錯(cuò)誤處理
Picasso支持加載過(guò)程中和加載錯(cuò)誤時(shí)顯示對(duì)應(yīng)圖片碌廓。
2.4 在Adapter中的回收不在視野的ImageView和取消已經(jīng)回收的ImageView下載進(jìn)程
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 自動(dòng)添加磁盤和內(nèi)存二級(jí)緩存功能
2.7 支持優(yōu)先級(jí)處理
每次任務(wù)調(diào)度前會(huì)選擇優(yōu)先級(jí)高的任務(wù)慨蛙,比如 App 頁(yè)面中 Banner 的優(yōu)先級(jí)高于 Icon 時(shí)就很適用。
2.8 支持飛行模式纪挎、并發(fā)線程數(shù)根據(jù)網(wǎng)絡(luò)類型而變
手機(jī)切換到飛行模式或網(wǎng)絡(luò)類型變換時(shí)會(huì)自動(dòng)調(diào)整線程池最大并發(fā)數(shù)期贫,比如 wifi 最大并發(fā)為 4, 4g 為 3异袄,3g 為 2
2.9 “無(wú)”本地緩存
無(wú)”本地緩存唯灵,不是說(shuō)沒(méi)有本地緩存,而是 Picasso 自己沒(méi)有實(shí)現(xiàn)隙轻,交給了 Square 的另外一個(gè)網(wǎng)絡(luò)庫(kù) okhttp 去實(shí)現(xiàn)埠帕,這樣的好處是可以通過(guò)請(qǐng)求 Response Header 中的 Cache-Control 及 Expired 控制圖片的過(guò)期時(shí)間垢揩。
三、Picasso源碼解析
Picasso.with(this).load(imageUrl).into(imageView)
3.1? with
一個(gè)單例模式敛瓷,為了保證線程安全叁巨,使用的是雙重校驗(yàn)鎖。在Picasso創(chuàng)建的過(guò)程中又使用了Builder模式呐籽,最大的特點(diǎn)就是鏈?zhǔn)秸{(diào)用锋勺,使調(diào)用者的代碼邏輯簡(jiǎn)潔,同時(shí)擴(kuò)展性非常好狡蝶。下面我看一下new Builder()中的方法庶橱。
在Builder的構(gòu)造方法中就只是獲取到當(dāng)前應(yīng)用級(jí)別的上下文,也就說(shuō)明了Picasso是針對(duì)應(yīng)用級(jí)別的使用贪惹,不會(huì)是隨著Activity或是Fragment的生命周期而產(chǎn)生變化苏章,只有當(dāng)當(dāng)前的應(yīng)用退出或是銷毀時(shí)Picasso才會(huì)停止它的行為。
接下來(lái)奏瞬,我們看看build方法中到底做了什么事情枫绅。
在這個(gè)方法中主要初始化了Downloader、LruCache硼端、PicassoExecutorService并淋、RequestTransformer、Stats珍昨、Dispatcher县耽、并且返回一個(gè)Picasso對(duì)象。
3.1.1? downloader 下載器
首先镣典,我們先看downloader下載器酬诀,如果downloader==null的話,就會(huì)執(zhí)行Utils.createDefaultDownloader(context)方法去創(chuàng)建一個(gè)下載器骆撇。
createDefaultDownloader方法中首先使用java反射機(jī)制來(lái)查找項(xiàng)目中是否使用了okhttp網(wǎng)絡(luò)加載框架瞒御,如果使用了則會(huì)使用okhttp作為圖片的加載方式,如果沒(méi)有使用神郊,則會(huì)使用內(nèi)置的封裝加載器UrlConnectionDownloader肴裙。
注:由于okhttp3的包名已更換,所以在這里都是使用內(nèi)置的封裝下載器涌乳,這個(gè)是一個(gè)小bug等待完善蜻懦。當(dāng)修復(fù)之后Picasso+okhttp3則是最理想的加載方式。
當(dāng)然我們自己也可以自定義下載器夕晓,使用okhttp3 作為加載器宛乃。代碼如下:
OkHttp3Downloader的下載地址:
https://github.com/JakeWharton/picasso2-okhttp3-downloader
接下來(lái)我們先分析OkHttpDownloader,然后在分析UrlConnectionDownloader,看看他們?cè)创a中到底實(shí)現(xiàn)了什么東西征炼。
OkHttpDownloader
OkHttpDownloader的構(gòu)造方法:
在構(gòu)造方法中通過(guò)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
通過(guò)Utils.calculateDiskCacheSize(cacheDir)析既,設(shè)置緩存的大小。
其中StatFs用于獲取存儲(chǔ)空間谆奥。
getBlockCount():文件系統(tǒng)中總的存儲(chǔ)區(qū)塊的數(shù)量眼坏;
getBlockSize():文件系統(tǒng)中每個(gè)存儲(chǔ)區(qū)塊的字節(jié)數(shù);
最大緩存大小是50M酸些。
通過(guò)defaultOkHttpClient()方法設(shè)置的OkHttpClient請(qǐng)求客戶端宰译。
UrlConnectionDownloader? 接下來(lái)分析UrlConnectionDownloader? 這個(gè)默認(rèn)的下載器。
UrlConnectionDownloader中使用的是系統(tǒng)自帶的HttpURLConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求的魄懂。
這個(gè)設(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的默認(rèn)文件緩存采用的是LruCache。
LruCache的構(gòu)造方法如下:
通過(guò)Utils.calculateMemoryCacheSize(context)市栗,設(shè)置了緩存大小缀拭。
activityManager.getLargeMemoryClass(),為單個(gè)應(yīng)用的最大內(nèi)存使用。
LruCache的內(nèi)部實(shí)現(xiàn)是采用的LinkedHashMap肃廓,來(lái)保存緩存圖片智厌。
LinkedHashMap:它繼承與HashMap诲泌、底層使用哈希表與雙向鏈表來(lái)保存所有元素盲赊,
LinkedHashMap是Hash表和鏈表的實(shí)現(xiàn),并且依靠著雙向鏈表保證了迭代順序是插入的順序敷扫。雙向循環(huán)鏈表哀蘑。
HashMap:它根據(jù)鍵的HashCode值存儲(chǔ)數(shù)據(jù),根據(jù)鍵可以直接獲取它的值,具有很快的訪問(wèn)速度葵第,遍歷時(shí)绘迁,取得數(shù)據(jù)的順序是完全隨機(jī)的。
區(qū)別在于HashMap并不是按插入次序順序存放的卒密,而LinkedHashMap是順序存放的缀台。
關(guān)于HashMap和LinkedHashMap的源碼分析,我們?nèi)蘸笤斀狻?/b>
我們首先分析LruCache的set方法哮奇。
在set方法中膛腐,最終調(diào)用了map.put()方法,將數(shù)據(jù)放到Hash表里面鼎俘。在這個(gè)方法的最后有一個(gè)trimToSize(maxSize)哲身,他到底實(shí)現(xiàn)了什么尼?贸伐,首先我們看看它的源碼實(shí)現(xiàn)勘天。
從源碼中,我們看出,當(dāng)所插入的元素大小size大于maxSize時(shí)脯丝,LinkHashMap就把最舊的一個(gè)元素刪除掉商膊。get方法相對(duì)簡(jiǎn)單,我們就看一下源碼實(shí)現(xiàn)。
LruCache.get()方法:
3.1.3? PicassoExecutorService線程池
PicassoExecutorService的構(gòu)造方法如下圖所示:
PicassoExecutorService繼承的是ThreadPoolExecutor線程池
談到線程池讨永,我們先了解一下線程池的有點(diǎn):
重用線程池中的線程盖溺, 避免因?yàn)榫€程的創(chuàng)建和銷毀所帶來(lái)的性能開(kāi)銷.
有效控制線程池中的最大并發(fā)數(shù),避免大量線程之間因?yàn)橄嗷屨枷到y(tǒng)資源而導(dǎo)致的阻塞現(xiàn)象.
能夠?qū)€程進(jìn)行簡(jiǎn)單的管理潦匈,可提供定時(shí)執(zhí)行和按照指定時(shí)間間隔循環(huán)執(zhí)行等功能.
ThreadPoolExecutor 的配置參數(shù)
corePoolSize: 線程池的核心線程數(shù),默認(rèn)情況下赚导, 核心線程會(huì)在線程池中一直存活茬缩, 即使處于閑置狀態(tài). 但如果將allowCoreThreadTimeOut設(shè)置為true的話, 那么核心線程也會(huì)有超時(shí)機(jī)制, 在keepAliveTime設(shè)置的時(shí)間過(guò)后吼旧, 核心線程也會(huì)被終止.
maximumPoolSize: 最大的線程數(shù)凰锡, 包括核心線程, 也包括非核心線程圈暗, 在線程數(shù)達(dá)到這個(gè)值后掂为,新來(lái)的任務(wù)將會(huì)被阻塞.
keepAliveTime: 超時(shí)的時(shí)間, 閑置的非核心線程超過(guò)這個(gè)時(shí)長(zhǎng)员串,講會(huì)被銷毀回收勇哗, 當(dāng)allowCoreThreadTimeOut為true時(shí),這個(gè)值也作用于核心線程.
unit:超時(shí)時(shí)間的時(shí)間單位.
workQueue:線程池的任務(wù)隊(duì)列寸齐, 通過(guò)execute方法提交的runnable對(duì)象會(huì)存儲(chǔ)在這個(gè)隊(duì)列中.
threadFactory: 線程工廠, 為線程池提供創(chuàng)建新線程的功能.
handler: 任務(wù)無(wú)法執(zhí)行時(shí)欲诺,回調(diào)handler的rejectedExecution方法來(lái)通知調(diào)用者.
在這個(gè)構(gòu)造方法中,我們重點(diǎn)了解PriorityBlockingQueue和Utils.PicassoThreadFactory()兩個(gè)類或者功能方法渺鹦。
PriorityBlockingQueue:它是無(wú)界阻塞隊(duì)列扰法,容量是無(wú)限的,它使用與類PriorityQueue相同的順序規(guī)則毅厚。它是線程安全的塞颁,是阻塞的,具體詳解會(huì)在簡(jiǎn)書(shū)數(shù)據(jù)結(jié)構(gòu)中了解吸耿。
PicassoThreadFactory()最終使用的是PicassoThread線程工廠祠锣。我們簡(jiǎn)單了解PicassoThread的實(shí)現(xiàn)。
3.1.4 RequestTransformer
RequestTransformer主要是對(duì)RequestCreator創(chuàng)建的Request進(jìn)行轉(zhuǎn)換珍语,默認(rèn)對(duì)Request對(duì)象不做處理锤岸。源碼中也證實(shí)了這一點(diǎn)。
3.1.5 Stats 圖片的狀態(tài)
Stats的構(gòu)造方法如下:stats主要是用來(lái)統(tǒng)計(jì)緩存板乙,下載數(shù)量等數(shù)據(jù)是偷,一言以蔽之拳氢,就是保存圖片的一些狀態(tài)信息。
HandlerThread的詳解請(qǐng)閱讀handlerThread詳解
HandlerThread的主要優(yōu)點(diǎn)在于他是用的是子線程的Looper,所以說(shuō)不占用主線程度 資源蛋铆。
Stats里面自己實(shí)現(xiàn)了一個(gè)Handler馋评,代碼如下:
3.1.6? Dispatcher? 核心類
這個(gè)類在這里起到了一個(gè)調(diào)度器的作用,圖片要不要開(kāi)始下載以及下載后Bitmap的返回都是通過(guò)這個(gè)調(diào)度器來(lái)執(zhí)行的刺啦,后面進(jìn)行進(jìn)行詳細(xì)分析留特。我們先看一下Dispathcher的核心構(gòu)造方法。
控制的中心玛瘸,控制線程的加載和取消蜕青、網(wǎng)絡(luò)監(jiān)聽(tīng)、消息處理等糊渊。
幾個(gè)重要的參數(shù)右核,我們上面已經(jīng)介紹了。主要簡(jiǎn)單介紹DispatcherThread和DispatcherHandler渺绒。
DispatcherThread是一個(gè)HandlerThread,DispatcherHandler是自定義的消息分發(fā)的贺喝。源碼如下:
我們以dispatchSubmit為例。最終會(huì)調(diào)用Dispatcher的dispatchSubmit()方法宗兼。
Dispatcher的dispatchSubmit()方法主要是獲取BitmapHunter實(shí)例躏鱼,由這個(gè)實(shí)例來(lái)執(zhí)行實(shí)際的下載操作。BitmapHunter本身是Runnable的一個(gè)實(shí)現(xiàn)殷绍,而這個(gè)實(shí)例最終是交由Picasso線程池進(jìn)行運(yùn)行的染苛。這個(gè)實(shí)例最終是要放到this.hunterMap=newLinkedHashMap(),循環(huán)雙向隊(duì)列中篡帕。
那么這個(gè)BitmapHunter加載圖片成功或失敗后是怎么通知UI的呢殖侵?我們前面提到Dispatcher在Picasso中起到了一個(gè)調(diào)度器的作用贸呢,當(dāng)圖片加載完畢后自然也是通過(guò)這個(gè)調(diào)度器來(lái)更新UI镰烧,上面我們得到BitmapHunter的run方法會(huì)執(zhí)行響應(yīng)的下載任務(wù),那么我們就去這個(gè)run方法中去看看楞陷。
我們可以看到成功就會(huì)調(diào)用dispatcher.dispatchComplete(this)方法怔鳖,失敗就會(huì)調(diào)用dispatcher.dispatchFailed(this)方法。接下來(lái)我們就去Dispather方法中看看就這個(gè)是如何實(shí)現(xiàn)的固蛾。源碼如下:
經(jīng)過(guò)handler消息處理后结执,就會(huì)執(zhí)行dispatcher.performComplete(hunter)或者dispatcher.performError(hunter, false)。
圖片下載完成之后艾凯,首先放到LruCache中献幔,其實(shí)就是把操作先暫存在一個(gè)list中,等空閑的時(shí)候再拿出來(lái)處理趾诗,這樣做得好處也是盡量減少主線程的執(zhí)行時(shí)間蜡感,一方面防止ANR蹬蚁,另一方面快速返回,響應(yīng)頁(yè)面的其他渲染操作郑兴,防止卡頓用戶界面犀斋。然后下載任務(wù)從hunterMap刪除。然后執(zhí)行batch(hunter)方法情连。
private static final intBATCH_DELAY=200;// ms
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH,BATCH_DELAY);
延時(shí)200毫秒之后叽粹,就來(lái)到了handlerMessage方法中。最終執(zhí)行dispatcher.performBatchComplete()方法却舀。
這個(gè)mainThreadHandler是在Dispatcher實(shí)例化時(shí)由外部傳遞進(jìn)來(lái)的虫几,我們?cè)谇懊娴姆治鲋锌吹剑琍icasso在通過(guò)Builder創(chuàng)建時(shí)會(huì)對(duì)Dispatcher進(jìn)行實(shí)例化挽拔,在那個(gè)地方將主線程的handler傳了進(jìn)來(lái)持钉,我們回到Picasso這個(gè)類,看到其有一個(gè)靜態(tài)成員變量HANDLER篱昔,這樣我們也就清楚了每强。
執(zhí)行到這里,圖片已經(jīng)馬上出來(lái)了州刽,hunter.picasso.complete(hunter)空执,Picasso中一個(gè)Action提供了請(qǐng)求前后的銜接工作,對(duì)于我們現(xiàn)在的情況穗椅,Picasso使用了ImageViewAction來(lái)進(jìn)行處理辨绊,也就是在ImageViewAction中的complete方法完成了最后的圖片渲染工作。
最后調(diào)用了PicassoDrawable.setBitmap(target,context,result,from,noFade,indicatorsEnabled)方法匹表。
最后執(zhí)行PicassoDrawable门坷,從這個(gè)構(gòu)造方法中,我們就明白了placeholder是如何設(shè)置的啦袍镀。
在PicassoDrawable方法中默蚌,實(shí)現(xiàn)了這個(gè)功能。
dispatcher.performError(hunter, false)就不帶大家詳細(xì)分析了苇羡。最后后調(diào)用ImageViewAction的error方法绸吸。
至此Dispather分析完畢,至此我們留下一個(gè)疑問(wèn)Dispather.dispatchSubmit(Action action),從哪里開(kāi)始調(diào)用的设江。
3.2 load()方法
接下來(lái)我們分析锦茁,Picasso中的load方法,圖片是如何進(jìn)行網(wǎng)絡(luò)請(qǐng)求的叉存。
待續(xù)码俩。。歼捏。稿存。够傍。