Android App 性能優化之圖片優化

接下來說明一下關於其他內存問題。圖片問題,作爲一個優秀的Android開發者,在圖片的類型選擇,圖片顯示前的處理都是要好好考慮的,因爲不同類型圖片在Android中的顯示代價是不同的,使用不同顯示方式代價也是不同的,首先看一下圖片類型png與jpg兩種類型顯示代價有不同,原因在於png佔的內存較多,但解碼叫簡單,若png圖片過多,會容易垃圾回收,甚至內存溢出,而jpg的內存小,但解碼複雜,會花更多時間解碼,所以要根據具體情況來定,如果當前是由於內存問題導致垃圾回收頻繁執行導致卡慢頓,這樣圖片優化就減少png,如果是非內存問題導致的,就可以使用png。

谷歌官方說法如下:

Smaller PNG Files(較少的png文件)

儘量減少PNG圖片的大小是Android裏面很重要的一條規範。相比起JPEGPNG能夠提供更加清晰無損的圖片,但是PNG格式的圖片會更大,佔用更多的磁盤空間。到底是使用PNG還是JPEG,需要設計師仔細衡量,對於那些使用JPEG就可以達到視覺效果的,可以考慮採用JPEG即可。

谷歌官方這樣說應該是由於Android上導致卡慢頓的大多數原因是和內存有關吧。

關於在圖片顯示前的操作:

Pre-scaling Bitmaps(預放縮圖片)

bitmap做縮放,這也是Android裏面最遇到的問題。對bitmap做縮放的意義很明顯,提示顯示性能,避免分配不必要的內存。Android提供了現成的bitmap縮放的API,叫做createScaledBitmap(),使用這個方法可以獲取到一張經過縮放的圖片。

 

上面的方法能夠快速的得到一張經過縮放的圖片,可是這個方法能夠執行的前提是,原圖片需要事先加載到內存中,如果原圖片過大,很可能導致OOM。下面介紹其他幾種縮放圖片的方式。

inSampleSize能夠等比的縮放顯示圖片,同時還避免了需要先把原圖加載進內存的缺點。我們會使用類似像下面一樣的方法來縮放bitmap

 

 

另外,我們還可以使用inScaledinDensityinTargetDensity的屬性來對解碼圖片做處理,源碼如下圖所示:

 

(注:這裏的bitmapoption還可以知道圖片的編碼類型)

還有一個經常使用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖片,可以事先獲取到圖片的大小而不至於佔用什麼內存。如下圖所示:




Re-using Bitmaps(重複使用bitmaps)

我們知道bitmap會佔用大量的內存空間,這節會講解什麼是inBitmap屬性,如何利用這個屬性來提升bitmap的循環效率。前面我們介紹過使用對象池的技術來解決對象頻繁創建再回收的效率問題,使用這種方法,bitmap佔用的內存空間會差不多是恆定的數值,每次新創建出來的bitmap都會需要佔用一塊單獨的內存區域,如下圖所示:

 

爲了解決上圖所示的效率問題,Android在解碼圖片的時候引進了inBitmap屬性,使用這個屬性可以得到下圖所示的效果:

 

使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的內存區域,新解碼的bitmap會嘗試去使用之前那張bitmapheap中所佔據的pixel data內存區域,而不是去問內存重新申請一塊區域來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用屏幕所能夠顯示的圖片數量的內存大小。下面是如何使用inBitmap的代碼示例:

 

使用inBitmap需要注意幾個限制條件:

·在SDK 11 -> 18之間,重用的bitmap大小必須是一致的,例如給inBitmap賦值的圖片大小爲100-100,那麼新申請的bitmap必須也爲100-100才能夠被重用。從SDK 19開始,新申請的bitmap大小必須小於或者等於已經賦值過的bitmap大小。

·新申請的bitmap與舊的bitmap必須有相同的解碼格式,例如大家都是8888的,如果前面的bitmap8888,那麼就不能支持4444565格式的bitmap了,不同的編碼格式佔用的內存是不同的,有時候也可以根據需求指定編碼格式。

我們可以創建一個包含多種典型可重用bitmap的對象池,這樣後續的bitmap創建都能夠找到合適的“模板”去進行重用。如下圖所示:

 

Google介紹了一個開源的加載bitmap的庫:Glide,這裏麪包含了各種對bitmap

 

前面提到編碼方式,其實不同的編碼方式佔用內存是不同的當然顯示效果也是有區別的,可以在不影響用戶體驗的前提下,適當選擇編碼方式。

 Smaller Pixel Formats

常見的png,jpeg,webp等格式的圖片在設置到UI上之前需要經過解碼的過程,而解壓時可以選擇不同的解碼率,不同的解碼率對內存的佔用是有很大差別的。在不影響到畫質的前提下儘量減少內存的佔用,這能夠顯著提升應用程序的性能。

AndroidHeap空間是不會自動做兼容壓縮的,意思就是如果Heap空間中的圖片被收回之後,這塊區域並不會和其他已經回收過的區域做重新排序合併處理,那麼當一個更大的圖片需要放到heap之前,很可能找不到那麼大的連續空閒區域,那麼就會觸發GC,使得heap騰出一塊足以放下這張圖片的空閒區域,如果無法騰出,就會發生OOM。如下圖所示:

 

所以爲了避免加載一張超大的圖片,需要儘量減少這張圖片所佔用的內存大小,Android爲圖片提供了4種解碼格式,Android默認是使用argb8888格式,還有argb4444,alpha8及rgb565:

 對於不同解碼格式佔用內存大小具體如下:

Bitmap.Config ARGB_4444:每個像素佔四位,即A=4R=4G=4B=4,那麼一個像素點佔4+4+4+4=16

 

Bitmap.Config ARGB_8888:每個像素佔四位,即A=8R=8G=8B=8,那麼一個像素點佔8+8+8+8=32

 

Bitmap.Config RGB_565:每個像素佔四位,即R=5G=6B=5,沒有透明度,那麼一個像素點佔5+6+5=16

 

Bitmap.Config ALPHA_8:每個像素佔四位,只有透明度,沒有顏色。

 

一般情況下我們都是使用的ARGB_8888,由此可知它是最佔內存的,因爲一個像素佔32位,8=1字節,所以一個像素佔4字節的內存。假設有一張480x800的圖片,如果格式爲ARGB_8888,那麼將會佔用1500KB的內存。

 


隨着解碼佔用內存大小的降低,清晰度也會有損失。我們需要針對不同的應用場景做不同的處理,大圖和小圖可以採用不同的解碼率。在Android裏面可以通過下面的代碼來設置解碼率:

 

實際上,一張圖片在內存中佔用多大空間主要受圖片本身大小(分辨率),解碼方式,還有就是設備像素密度這三個因素影響,其中像素密度是由設備定的,編程人員可控性不高。所以解決一張圖片在內存中大小問題,就得從圖片分辨率和解碼方式入手。


注意:

Bitmap 對象在不使用時,我們應該先調用recycle()釋放內存,然後才置空,因爲加載bitmap對象的內存空間,一部分是java的,一部分是c的(因爲Bitmap分配的底層是通過jni調用的,BitMap底層是skia圖形庫,skia圖形庫是c實現的,通過jni的方法在java層進行封裝)。這個recycle()函數就是針對c部分的內存釋放。


發佈了27 篇原創文章 · 獲贊 21 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章