Android圖片加載問題分析

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

imagepipeline.png

本文以UniversalImageLoader為例分析了這一流程,然后分析了Fresco的優勢和問題,最終推薦大家使用Glide。

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

網絡

主要用于下載網絡圖片,在UIL中是將圖片地址變為InputStream。UIL支持多種類型來源的圖片顯示,包括:

  • 網絡
  • 文件
  • Uri資源(如果是視頻,會找到縮略圖顯示)
  • assets
  • drawable

緩存

  • UIL使用兩級緩存:磁盤緩存圖片文件、內存緩存Bitmap
  • UIL已經實現了多種緩存策略,但一般都使用LRU緩存
  • UIL可以指定圖片緩存的路徑,和緩存文件名的生成規則
  • 需要自己確定緩存的大小,確定內存緩存的大小尤其重要
    • 通過ActivityManager#getMemoryClassRuntime.getRuntime().maxMemory()獲得單個應用的最大內存(前者單位為MB,后者單位為B),一般最多劃分1/4的最大內存,否則容易導致OOM。
    • 圖片較多,大圖較多的應用,需要使用較大的緩存,提高緩存的命中率
    • 內存也不宜太小,最少應該能緩存2~3個屏幕大小的Bitmap

解碼

  • 要按需解碼,否則會造成內存的浪費,主要通過options.inJustDecodeBounds進行預解析
  • ImageAware可以幫助控制解碼圖片的大小,ImageViewAware就是對ImageView的一個封裝
  • 注意照片方向Exif,避免圖片錯誤旋轉

顯示

實現接口BitmapDisplayer可以自定義顯示效果,已實現的包括:帶描邊的圓形、漸入、圓角矩形(不能四個角分別指定)等。

某些設計可能會出現兩個角圓角、另外兩個直角的特殊裁剪模式。自己實現這類Displayer時,不要生成一個新的Bitmap,定義一個Drawable會更高效。因為生成新的Bitmap會引起內存分配和回收,從而使GC更加頻繁,而Drawable只是在繪制時會使用很少的計算資源??梢詤⒖荚创a中RoundedBitmapDisplayer。

多線程

圖片的下載和解碼都需要再后臺線程中處理,而且為了提高效率,一般都使用多個線程分別進行解碼和網絡請求。

共有三個Executor,分別用于

  • 分發任務
  • 處理已緩存圖片
  • 處理未緩存圖片

已緩存的圖片主要占用計算資源,未緩存的圖片則主要占用網絡資源,所以不應該在一個Executor中競爭??梢苑謩e指定Executor的線程數量,UIL默認為3個。

監聽

UIL向外提供了兩類監聽:ImageLoadingListener和ImageLoadingProgressListener

PauseOnScrollListener主要用于,在列表滾動時暫停圖片加載,但在現在的RecyclerView中無法使用。需要自己使用ImageLoader#pauseImageLoader#resume

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

Fresco的優勢和問題

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

  • 不能直接使用ImageView:這個問題對于老項目的重構幾乎是致命的。
  • 需要指定寬高:指定寬高對于圖片加載其實是一件好事,但在加載網絡圖片的時候需要服務端告知原圖的尺寸,才能幫助客戶端實現更好的體驗。

相比UIL,它的優點主要包括:

  • 5.0以下的系統上,使用ASHMEM,不會占用Java堆內容
  • 多一級未解碼圖片的內存緩存,減少文件IO(很多Android機器使用1年之后變慢,很大的原因就是IO變慢了,所以這一優化的效果還是很顯著的)
  • 支持多圖請求,可以在大圖顯示之前展現縮略圖
  • 支持Gif動畫和漸進式JPEG(圖片還未下載完成的時候就可以先顯示一部分)

更多關于Fresco的特點可以參考Android圖片加載開源庫深度推薦,安利Fresco

緩存和網絡:Image Pipeline

在5.0系統以下,Image Pipeline 使用 pinned purgeables 將Bitmap數據避開Java堆內存,存在ASHMEM中。這要求圖片不使用時,要顯式地釋放內存。SimpleDraweeView自動處理了這個釋放過程,所以沒有特殊情況,盡量使用SimpleDraweeView。

顯示

修改圖片的顯示效果需要使用DraweeHolder,它不僅能控制圖片的顯示,還可以處理View的Touch事件。默認支持圓角和圓形。

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

監聽

  • ControllerListener:監聽圖片顯示的過程
  • RequestListener:監聽圖片獲取的過程

Glide是個不錯的選擇

優點

  • 支持本地Video
  • 分別控制每次請求的優先級
  • 支持縮略圖
  • 可直接更新AppWidget和Notification中的圖片
  • 自定義圖片轉換效果,還可使用GPU轉換(使用GPU處理圖片變換,并保持到緩存文件中,在Demo中可以看到GPU變換的10種特效)
  • 定義加載動畫
  • 很方便的使用圖片裁剪服務
  • 使用Bitmap回收池,減少系統gc

可參考Glide相關文章了解怎么通過自定義的GlideModule優化加載的圖片。本文也提供給了參考代碼,在代碼中引入七牛裁圖服務,同時也可體驗10種GPU變化的特效。

關于Transformation和BitmapImageViewTarget的使用

  • Transformation#transform:在緩存前對圖片進行處理,處理之后的圖片才會進行緩存。處理圖片的過程中會產生額外的內存消耗,處理后的圖片會占據獨立的緩存空間。但第二次使用的時候,不再需要處理,直接從緩存中讀取。
  • BitmapImageViewTarget#setResource:控制圖片的顯示邏輯,每次顯示的時候都會處理。因此在setResource中應該定義特殊的Drawable來控制顯示效果,而不應該對Bitmap進行處理。(Bitmap的頻繁生成和回收會導致gc;Drawable是在繪制的時候,通過Paint設置特殊的繪制效果,不會產生新的Bitmap)

關于Bitmap的回收機制

系統的Bitmap內存管理機制隨著Android系統的演進可以分為三個階段,關于這部分可以參考官網文章Managing Bitmap Memory

  • 2.3.3及以下:Bitmap的像素數據存儲在native內存中,但依舊會計算在一個進程的內存上限之中。
  • 3.0~4.4:Bitmap的像素數據存儲在Java堆內存中,解碼Bitmap時可以通過Options#inBitmap復用不再使用的Bitmap,從而減少系統gc。但要求被復用的Bitmap和新Bitmap的像素數據一樣大。
  • 5.0及以上:對于復用Bitmap的限制不再嚴格要求一樣大,只要別復用的Bitmap的像素數據不小于新Bitmap即可。

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

Android系統使用的內存除了native heapJava heap以外,還有ASHMEM。在5.0之前可以通過Options#inPurgeable將Bitmap的數據存儲在ASHMEM中,從而不占據一個進程的內存上限。

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

關于Android系統中三種內存的和Bitmap的關系可以參考Introducing Fresco: A new image library for Android

參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容