Android圖片加載問題分析

下圖是一個(gè)客戶端圖片加載模塊常見的處理流程。

imagepipeline.png

本文以UniversalImageLoader為例分析了這一流程,然后分析了Fresco的優(yōu)勢(shì)和問題臼节,最終推薦大家使用Glide伍派。

從UniversalImageLoader分析圖片加載中需要處理的問題

網(wǎng)絡(luò)

主要用于下載網(wǎng)絡(luò)圖片,在UIL中是將圖片地址變?yōu)镮nputStream推溃。UIL支持多種類型來源的圖片顯示,包括:

  • 網(wǎng)絡(luò)
  • 文件
  • Uri資源(如果是視頻届腐,會(huì)找到縮略圖顯示)
  • assets
  • drawable

緩存

  • UIL使用兩級(jí)緩存:磁盤緩存圖片文件铁坎、內(nèi)存緩存Bitmap
  • UIL已經(jīng)實(shí)現(xiàn)了多種緩存策略,但一般都使用LRU緩存
  • UIL可以指定圖片緩存的路徑犁苏,和緩存文件名的生成規(guī)則
  • 需要自己確定緩存的大小硬萍,確定內(nèi)存緩存的大小尤其重要
    • 通過ActivityManager#getMemoryClassRuntime.getRuntime().maxMemory()獲得單個(gè)應(yīng)用的最大內(nèi)存(前者單位為MB,后者單位為B)围详,一般最多劃分1/4的最大內(nèi)存朴乖,否則容易導(dǎo)致OOM。
    • 圖片較多助赞,大圖較多的應(yīng)用买羞,需要使用較大的緩存,提高緩存的命中率
    • 內(nèi)存也不宜太小雹食,最少應(yīng)該能緩存2~3個(gè)屏幕大小的Bitmap

解碼

  • 要按需解碼畜普,否則會(huì)造成內(nèi)存的浪費(fèi),主要通過options.inJustDecodeBounds進(jìn)行預(yù)解析
  • ImageAware可以幫助控制解碼圖片的大小群叶,ImageViewAware就是對(duì)ImageView的一個(gè)封裝
  • 注意照片方向Exif吃挑,避免圖片錯(cuò)誤旋轉(zhuǎn)

顯示

實(shí)現(xiàn)接口BitmapDisplayer可以自定義顯示效果,已實(shí)現(xiàn)的包括:帶描邊的圓形盖呼、漸入儒鹿、圓角矩形(不能四個(gè)角分別指定)等。

某些設(shè)計(jì)可能會(huì)出現(xiàn)兩個(gè)角圓角几晤、另外兩個(gè)直角的特殊裁剪模式约炎。自己實(shí)現(xiàn)這類Displayer時(shí),不要生成一個(gè)新的Bitmap,定義一個(gè)Drawable會(huì)更高效圾浅。因?yàn)樯尚碌腂itmap會(huì)引起內(nèi)存分配和回收掠手,從而使GC更加頻繁,而Drawable只是在繪制時(shí)會(huì)使用很少的計(jì)算資源狸捕∨绺耄可以參考源碼中RoundedBitmapDisplayer。

多線程

圖片的下載和解碼都需要再后臺(tái)線程中處理灸拍,而且為了提高效率做祝,一般都使用多個(gè)線程分別進(jìn)行解碼和網(wǎng)絡(luò)請(qǐng)求。

共有三個(gè)Executor鸡岗,分別用于

  • 分發(fā)任務(wù)
  • 處理已緩存圖片
  • 處理未緩存圖片

已緩存的圖片主要占用計(jì)算資源混槐,未緩存的圖片則主要占用網(wǎng)絡(luò)資源,所以不應(yīng)該在一個(gè)Executor中競(jìng)爭(zhēng)轩性∩牵可以分別指定Executor的線程數(shù)量,UIL默認(rèn)為3個(gè)揣苏。

監(jiān)聽

UIL向外提供了兩類監(jiān)聽:ImageLoadingListener和ImageLoadingProgressListener

PauseOnScrollListener主要用于悯嗓,在列表滾動(dòng)時(shí)暫停圖片加載,但在現(xiàn)在的RecyclerView中無法使用卸察。需要自己使用ImageLoader#pauseImageLoader#resume

了解了UIL是如何處理圖片加載的問題之后脯厨,其他的第三方庫也都是大同小異,下面再介紹下Fresco和Glide蛾派。

Fresco的優(yōu)勢(shì)和問題

Fresco在解決圖片加載問題上的思路和其他框架有很大的不同俄认。它最大的問題有兩個(gè):

  • 不能直接使用ImageView:這個(gè)問題對(duì)于老項(xiàng)目的重構(gòu)幾乎是致命的个少。
  • 需要指定寬高:指定寬高對(duì)于圖片加載其實(shí)是一件好事洪乍,但在加載網(wǎng)絡(luò)圖片的時(shí)候需要服務(wù)端告知原圖的尺寸,才能幫助客戶端實(shí)現(xiàn)更好的體驗(yàn)夜焦。

相比UIL壳澳,它的優(yōu)點(diǎn)主要包括:

  • 5.0以下的系統(tǒng)上,使用ASHMEM茫经,不會(huì)占用Java堆內(nèi)容
  • 多一級(jí)未解碼圖片的內(nèi)存緩存巷波,減少文件IO(很多Android機(jī)器使用1年之后變慢,很大的原因就是IO變慢了卸伞,所以這一優(yōu)化的效果還是很顯著的)
  • 支持多圖請(qǐng)求抹镊,可以在大圖顯示之前展現(xiàn)縮略圖
  • 支持Gif動(dòng)畫和漸進(jìn)式JPEG(圖片還未下載完成的時(shí)候就可以先顯示一部分)

更多關(guān)于Fresco的特點(diǎn)可以參考Android圖片加載開源庫深度推薦,安利Fresco

緩存和網(wǎng)絡(luò):Image Pipeline

在5.0系統(tǒng)以下荤傲,Image Pipeline 使用 pinned purgeables 將Bitmap數(shù)據(jù)避開Java堆內(nèi)存垮耳,存在ASHMEM中。這要求圖片不使用時(shí),要顯式地釋放內(nèi)存终佛。SimpleDraweeView自動(dòng)處理了這個(gè)釋放過程俊嗽,所以沒有特殊情況,盡量使用SimpleDraweeView铃彰。

顯示

修改圖片的顯示效果需要使用DraweeHolder绍豁,它不僅能控制圖片的顯示,還可以處理View的Touch事件牙捉。默認(rèn)支持圓角和圓形竹揍。

另一個(gè)控制圖片顯示的方法是在Postprocessor中修改Bitmap,但效率很低邪铲。

監(jiān)聽

  • ControllerListener:監(jiān)聽圖片顯示的過程
  • RequestListener:監(jiān)聽圖片獲取的過程

Glide是個(gè)不錯(cuò)的選擇

優(yōu)點(diǎn)

  • 支持本地Video
  • 分別控制每次請(qǐng)求的優(yōu)先級(jí)
  • 支持縮略圖
  • 可直接更新AppWidget和Notification中的圖片
  • 自定義圖片轉(zhuǎn)換效果鬼佣,還可使用GPU轉(zhuǎn)換(使用GPU處理圖片變換,并保持到緩存文件中霜浴,在Demo中可以看到GPU變換的10種特效)
  • 定義加載動(dòng)畫
  • 很方便的使用圖片裁剪服務(wù)
  • 使用Bitmap回收池晶衷,減少系統(tǒng)gc

可參考Glide相關(guān)文章了解怎么通過自定義的GlideModule優(yōu)化加載的圖片。本文也提供給了參考代碼阴孟,在代碼中引入七牛裁圖服務(wù)晌纫,同時(shí)也可體驗(yàn)10種GPU變化的特效。

關(guān)于Transformation和BitmapImageViewTarget的使用

  • Transformation#transform:在緩存前對(duì)圖片進(jìn)行處理永丝,處理之后的圖片才會(huì)進(jìn)行緩存锹漱。處理圖片的過程中會(huì)產(chǎn)生額外的內(nèi)存消耗,處理后的圖片會(huì)占據(jù)獨(dú)立的緩存空間慕嚷。但第二次使用的時(shí)候哥牍,不再需要處理,直接從緩存中讀取喝检。
  • BitmapImageViewTarget#setResource:控制圖片的顯示邏輯嗅辣,每次顯示的時(shí)候都會(huì)處理。因此在setResource中應(yīng)該定義特殊的Drawable來控制顯示效果挠说,而不應(yīng)該對(duì)Bitmap進(jìn)行處理澡谭。(Bitmap的頻繁生成和回收會(huì)導(dǎo)致gc;Drawable是在繪制的時(shí)候损俭,通過Paint設(shè)置特殊的繪制效果蛙奖,不會(huì)產(chǎn)生新的Bitmap)

關(guān)于Bitmap的回收機(jī)制

系統(tǒng)的Bitmap內(nèi)存管理機(jī)制隨著Android系統(tǒng)的演進(jìn)可以分為三個(gè)階段,關(guān)于這部分可以參考官網(wǎng)文章Managing Bitmap Memory杆兵。

  • 2.3.3及以下:Bitmap的像素?cái)?shù)據(jù)存儲(chǔ)在native內(nèi)存中雁仲,但依舊會(huì)計(jì)算在一個(gè)進(jìn)程的內(nèi)存上限之中。
  • 3.0~4.4:Bitmap的像素?cái)?shù)據(jù)存儲(chǔ)在Java堆內(nèi)存中琐脏,解碼Bitmap時(shí)可以通過Options#inBitmap復(fù)用不再使用的Bitmap攒砖,從而減少系統(tǒng)gc。但要求被復(fù)用的Bitmap和新Bitmap的像素?cái)?shù)據(jù)一樣大。
  • 5.0及以上:對(duì)于復(fù)用Bitmap的限制不再嚴(yán)格要求一樣大祭衩,只要?jiǎng)e復(fù)用的Bitmap的像素?cái)?shù)據(jù)不小于新Bitmap即可灶体。

如有興趣,可參考筆者的另一篇文章了解《Glide如何通過引用計(jì)數(shù)復(fù)用Bitmap》

Android系統(tǒng)使用的內(nèi)存除了native heapJava heap以外掐暮,還有ASHMEM蝎抽。在5.0之前可以通過Options#inPurgeable將Bitmap的數(shù)據(jù)存儲(chǔ)在ASHMEM中,從而不占據(jù)一個(gè)進(jìn)程的內(nèi)存上限路克。

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

關(guān)于Android系統(tǒng)中三種內(nèi)存的和Bitmap的關(guān)系可以參考Introducing Fresco: A new image library for Android

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末樟结,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子精算,更是在濱河造成了極大的恐慌瓢宦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灰羽,死亡現(xiàn)場(chǎng)離奇詭異驮履,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)廉嚼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門玫镐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怠噪,你說我怎么就攤上這事恐似。” “怎么了傍念?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵矫夷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我憋槐,道長(zhǎng)双藕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任秦陋,我火速辦了婚禮蔓彩,結(jié)果婚禮上治笨,老公的妹妹穿的比我還像新娘驳概。我一直安慰自己,他們只是感情好旷赖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布顺又。 她就那樣靜靜地躺著,像睡著了一般等孵。 火紅的嫁衣襯著肌膚如雪稚照。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音果录,去河邊找鬼上枕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弱恒,可吹牛的內(nèi)容都是我干的辨萍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼返弹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锈玉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起义起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤拉背,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后默终,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椅棺,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年齐蔽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了土陪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肴熏,死狀恐怖鬼雀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛙吏,我是刑警寧澤源哩,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站鸦做,受9級(jí)特大地震影響励烦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泼诱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一坛掠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧治筒,春花似錦屉栓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堤框,卻和暖如春域滥,著一層夾襖步出監(jiān)牢的瞬間纵柿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工启绰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昂儒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓委可,卻偏偏與公主長(zhǎng)得像荆忍,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撤缴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,520評(píng)論 25 707
  • 關(guān)于網(wǎng)絡(luò)加載已經(jīng)寫完了刹枉,今天來給大家分享一下關(guān)于圖像加載的知識(shí),在開發(fā)中除了請(qǐng)求數(shù)據(jù)怎么顯示之外屈呕,剩下的 最...
    deyson閱讀 893評(píng)論 0 3
  • 一微宝、簡(jiǎn)介 在泰國舉行的谷歌開發(fā)者論壇上,谷歌為我們介紹了一個(gè)名叫Glide的圖片加載庫虎眨,作者是bumptech蟋软。這...
    天天大保建閱讀 7,454評(píng)論 2 28
  • 參考文章 Python學(xué)習(xí)筆記[2] 一步一步教你認(rèn)識(shí)Python閉包 高階函數(shù) 特殊的函數(shù),特殊在能將函數(shù)作為參...
    泛福軒閱讀 244評(píng)論 0 0
  • 隨著互聯(lián)網(wǎng)時(shí)代的到來嗽桩,我們的生活正呈現(xiàn)出日新月異的變化岳守,伴隨著各種智能終端的出現(xiàn),媒體信息的共享與無縫傳遞成為可能...
    viviflash閱讀 212評(píng)論 0 0