Android高效內存:讓圖片占用盡可能少的內存
一、讓你的圖片最小化
1.1 大圖小圖內存使用情況對比
大圖:440 * 336 小圖:220 * 168 資源目錄:xhdpi
小圖的高寬都是大圖的1/2-->小圖是原圖的1/4
界面效果:
測試設備:Coolpad 8676-M01 5.1 density=2.0
測試前准備操作:同一款設備,設置圖片前后多次調用gc直到內存短時間內保持穩定不再變化
內存使用情況:下圖依次是 初始內存,大圖內存,小圖內存
大圖占用內存:11.23 MB - 10.66 MB = 0.57 MB
小圖占用內存:10.81 MB - 10.66 MB = 0.15 MB
大圖小圖內存關系:0.15 MB * 4 = 0.60 MB 約等於 0.57 MB (這是統計工具的誤差,理論上就是相等的)
同樣的方式在另外一台設備小米4c上得到的結果如下:
測試設備:Xiaomi Mi-4c V8.2.1.0.LXKCNDL 5.1.1 density=3.0
大圖占用內存:13.22 MB - 11.95 MB = 1.27 MB
小圖占用內存:12.27 MB - 11.95 MB = 0.32 MB
大圖小圖內存關系:0.32 MB * 4 = 1.28 MB 約等於 1.27 MB
結論:由此可見大圖比小圖占用更多的內存,圖片大小(分辨率)與占用內存成正比關系
備注:圖片在硬盤上占用的磁盤空間大小,與在內存中占用的內存大小完全不一樣,不是一個概念,不要混淆
1.2 使用.9圖代替大圖
根據上文中圖片大小與內存的關系,可以更加深刻的理解Android中.9圖片的作用,它不但能減少apk的體積,還能減少圖片占用內存。
1.3 使用繪制背景或者Drawable代替圖片
有些時候我們根本不需要圖片,而是自己繪制背景,可以在自定義View的onDraw中繪制背景,當然最方便的還是使用系統的Drawable,繪制部分交給系統去完成。下面測試圖片與Drawable的內存占用對比
原始圖片大小:482 * 482
界面效果:
測試設備:Xiaomi Mi-4c V8.2.1.0.LXKCNDL 5.1.1
測試前准備操作:同一款設備,設置背景前后多次調用gc直到內存短時間內保持穩定不再變化
內存使用情況:下圖依次是 初始內存,使用圖片占用的內存,使用Drawable占用的內存,使用onDraw繪制占用的內存
使用圖片占用內存:13.97 MB - 11.97 MB = 2.00 MB
使用Drawable占用內存:11.97 MB - 11.97 MB = 0.00 MB (不會是0,有誤差,只是很少)
使用onDraw繪制占用內存:11.98 MB - 11.97 MB = 0.01 MB
結論:繪制背景,或者使用系統提供Drawable作為背景,會大大減少內存占用
Drawable參考資料:
Drawable實戰解析:Android XML shape 標簽使用詳解(apk瘦身,減少內存好幫手)
Android GradientDrawable(shape標簽定義)靜態使用和動態使用(圓角,漸變實現)
二、讓你的圖片省內存
2.1 “讓你的圖片最小化”
“讓你的圖片最小化”一節中描述的方法:使用盡可能小的圖,使用.9,自己繪制背景或者使用Drawable來繪制背景
2.2 在內存中壓縮圖片
加載大圖片時需要對圖片進行壓縮,使用等比例壓縮方法直接在內存中處理圖片
Options options = new BitmapFactory.Options();
options.inSampleSize = 5; // 原圖的五分之一,設置為2則為二分之一
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
這樣做要注意的是,圖片質量會變差,inSampleSize設置的值越大,圖片質量就越差。
2.3 讀取位圖尺寸和類型時不把圖片加載到內存中
有時候我們取得一張圖片,也許只是為了獲得這個圖片的一些信息,比如圖片的width、height等信息,不需要顯示到界面上,這個時候我們可以不把圖片加載到內存中。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
2.4 用完就回收
由於Android外層是使用java,而底層使用的是C語言為圖片對象分配的內存空間。所以我們的外部雖然看起來釋放了,但里層卻並不一定完全釋放了,我們使用完圖片后最好再釋放掉里層的內存空間。
if (!bitmapObject.isRecyled()) { // Bitmap對象沒有被回收
bitmapObject.recycle(); // 釋放
System.gc(); // 提醒系統及時回收
}
2.5 降低要顯示的圖片色彩質量
2.5.1 顏色模型
RGB(ARGB)
RGB色彩模式是工業界的一種顏色標准,是通過對紅(R)、綠(G)、藍(B)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的,RGB即是代表紅、綠、藍三個通道的顏色,這個標准幾乎包括了人類視力所能感知的所有顏色,是目前運用最廣的顏色系統之一。在Android中還有包含透明度Alpha的顏色模型,即ARGB。
YUV
YUV,分為三個分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用於指定像素的顏色。
YUV的原理是把亮度與色度分離,研究證明,人眼對亮度的敏感超過色度。利用這個原理,可以把色度信息減少一點,人眼也無法查覺這一點。
主要用於電視系統以及模擬視頻領域,它將亮度信息(Y)與色彩信息(UV)分離,沒有UV信息一樣可以顯示完整的圖像,只不過是黑白的,這樣的設計很好地解決了彩色電視機與黑白電視的兼容問題
YUV的存儲中與RGB格式最大不同在於,RGB格式每個點的數據是連繼保存在一起的。即R,G,B是前后不間隔的保存在2-4byte空間中。而YUV的數據中為了節約空間,U,V分量空間會減小。每一個點的Y分量獨立保存,但連續幾個點的U,V分量是保存在一起的,(反正人眼一般也看不出區別).這幾個點合起來稱為macro-pixel, 這種存儲格式稱為Packed格式。另外一種存儲格式是把一幅圖像中Y,U,V分別用三個獨立的數組表示。這種模式稱為planar模式。
CMYK
CMYK也稱作印刷色彩模式,顧名思義就是用來印刷的。印刷四分色模式是彩色印刷時采用的一種套色模式,利用色料的三原色混色原理,加上黑色油墨,共計四種顏色混合疊加,形成所謂“全彩印刷”。四種標准顏色是:
- C:Cyan = 青色,又稱為‘天藍色’或是‘湛藍’
- M:Magenta = 品紅色,又稱為‘洋紅色’
- Y:Yellow = 黃色
- K:Key(blacK) = 定位套版色(黑色),有些文獻解釋說這里的K指代Black黑色,且為了避免與RGB的Blue藍色混淆不用B而改稱,雖然這是一種有用的助憶,但事實上這種說法是不正確的。
CMYK和RGB相比有一個很大的不同:RGB模式是一種發光的色彩模式,你在一間黑暗的房間內仍然可以看見屏幕上的內容;CMYK是一種依靠反光的色彩模式,我們是怎樣閱讀報紙的內容呢?是由陽光或燈光照射到報紙上,再反射到我們的眼中,才看到內容。它需要有外界光源,如果你在黑暗房間內是無法閱讀報紙的。只要是在印刷品上看到的圖像,就是CMYK模式表現的。比如期刊、雜志、報紙、宣傳畫等,都是印刷出來的,那么就是CMYK模式的了。
CMYK原色與疊加之后的顏色對比
2.5.2 RGB在計算機中顏色值的數字化編碼
在不考慮透明度的情況下,一個像素點的顏色值在計算機中的表示方法有以下3種:
- 浮點數編碼:比如float: (1.0, 0.5, 0.75),每個顏色分量各占1個float字段,其中1.0表示該分量的值為全紅或全綠或全藍;
- 24位的整數編碼:比如24-bit:(255, 128, 196),每個顏色分量各占8位,取值范圍0-255,其中255表示該分量的值為全紅或全綠或全藍;
- 16位的整數編碼:比如16-bit:(31, 45, 31),第1和第3個顏色分量各占5位,取值范圍0-31,第2個顏色分量占6位,取值范圍0-63;
在Java中,float類型的變量占32位,int類型的變量占32位,short和char類型的變量都在16位,因此可以看出,用浮點數表示法編碼一個像素的顏色,內存占用量是96位即12字節;而用24位整數表示法編碼,只要一個int類型變量,占用4個字節(高8位空着,低24位用於表示顏色);用16位整數表示法編碼,只要一個short類型變量,占2個字節;因此可以看出采用整數表示法編碼顏色值,可以大大節省內存,當然,顏色質量也會相對低一些。在Android中獲取Bitmap的時候一般也采用整型編碼。
2.5.3 Android中RGB編碼格式(整型編碼)
- RGB888(int):R、G、B分量各占8位
- RGB565(short):R、G、B分量分別占5、6、5位
- RGB555(short):RGB分量都用5位表示(剩下的1位不用)
- ARGB8888(int):A、R、G、B分量各占8位
- ARGB4444(short):A、R、G、B分量各占4位
回想一下Android的BitmapConfig類中,有ARGB_8888、ARGB_4444、RGB565等常量,現在可以知道它們分別代表了什么含義。同時也可以計算一張圖片在內存中可能占用的大小,比如采用ARGB_8888編碼載入一張1920*1200的圖片,大概就會占用1920*1200*4/1024/1024=8.79MB的內存。
2.5.4 降低要顯示的圖片色彩質量
采用低內存占用量的編碼方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省內存;
1920*1200的圖片:
ARGB_8888:1920*1200*4/1024/1024=8.79MB
ARGB_4444,RGB565:1920*1200*2/1024/1024=4.39MB
總結
在Android中,對圖片的使用一定要關注,大多數情況下,占用內存多,OOM發生都是因為圖片資源使用不當。不要盲目加一個大圖到Android項目中,能使用.9進來使用,而且.9圖本身盡可能小,另外能使用繪制實現就不要加一個圖片資源。有些時候,在不影響用戶體驗的情況下,可以降低圖片素材質量,比如不需要透明度的就不要了,有些透明度用肉眼看不出來。