Android Picasso源碼解析(一)

參考文章:

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方法中去看看。

BitmapHunter.java

我們可以看到成功就會調(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ù)。。。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容

  • Picasso,看的版本是v.2.5.2 使用方法,大概這么幾種加載資源的形式 還可以對圖片進行一些操作:設(shè)置大小...
    Jinjins1129閱讀 357評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,638評論 25 708
  • 一. 概述 Picasso是Square出品的一個非常精簡的圖片加載及緩存庫,其主要特點包括: 易寫易讀的流式編程...
    SparkInLee閱讀 1,099評論 2 11
  • 昨夜無事望月宮, 宮里嫦娥空自嘆! 遙問空嘆為哪般? 緣來天下第一人。 曉覺起身隔窗問, 均是一人誰二人? 鞘中殘...
    云先生_2017閱讀 188評論 1 2
  • 目前為止我最寶貴財富就是我的家庭,我覺得很幸福。我家人對我很好,也教我對別人也好。 我希望我將來能擁有的就是自由的...
    可愛的三姑閱讀 120評論 2 0