作為一名Android開發人員,你見得最多的大概就是res/drawable-[density]/ 文件夾了,現在又大概多了 res/mipmap-[density]/ 文件夾,這些文件夾通常用來存放圖片資源文件,大家可能再熟悉不過了,現在我問你,一張大小為376.16K的480x800且位數為8的圖片放在res/drawable-xxhdpi/ 文件夾下,在分辨率為1920*1080的手機上這張圖片占用的內存是多少?
1 概念厘清
如果對此比較有了解的小傻逼們,后面其實不需要看了,純粹來掃一下盲,在正式分析之前,先來厘清一下相關的概念。
- 1.1屏幕尺寸:按屏幕對角測量的實際物理尺寸,例如5.5英寸。Android 將所有實際屏幕尺寸分組為四種通用尺寸:小、 正常、大和超大;
- 1.2分辨率:屏幕上物理像素的總數,添加對多種屏幕的支持時, 應用不會直接使用分辨率,而只應關注通用尺寸和密度組指定的屏幕尺寸及密度;
- 1.3屏幕密度:屏幕物理區域中的像素量,通常稱為 dpi(每英寸點數)。屏幕密度越低在給定物理區域的像素就會較少。Android 將所有屏幕密度分為六組通用密度:ldpi( 低)、mdpi(中)、hdpi(高)、xhdpi(超高)、xxhdpi(超超高)和xxxhdpi(超超超高);
- 1.4密度無關像素 (dp):在定義 UI 布局時應使用的虛擬像素單位。密度無關像素等于 160 dpi 屏幕上的一個物理像素,這是系統為mdpi(中)密度屏幕假設的基線密度。在運行時,系統根據使用中屏幕的實際密度按需要以透明方式處理dp單位的任何縮放 。dp單位轉換為屏幕像素很簡單: px = dp * (dpi / 160)。 例如,在 240 dpi屏幕上,1 dp等于1.5 物理像素。
對于我們的分析比較重要的就是屏幕密度。
2 屏幕密度(dpi)對應關系
通用密度 | ldpi | mdpi(基線密度) | hdpi | xhdpi | xxhdpi | xxxhdpi |
---|---|---|---|---|---|---|
描述 | 低 | 中 | 高 | 超高 | 超超高 | 超超超高 |
大小(單位dpi) | 120 | 160 | 240 | 320 | 480 | 640 |
縮放系數 | 0.75 | 1 | 1.5 | 2 | 3 | 4 |
六種通用密度之間遵循 3:4:6:8:12:16 的縮放比率,要注意的一點是xxxhdpi僅限啟動器圖標。
3 具體分析實現代碼
代碼很簡單,就是用一個ImageView包含一張背景圖片,然后通過轉換為Bitmap查看占用內存大小。
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.xishuang.imagesizetest.MainActivity">
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bg2" />
</FrameLayout>
布局文件,就是一個ImageView控件,包含一張背景圖。
MainAcivity.java
private void printBitmapSize(ImageView imageView) {
Drawable drawable = imageView.getDrawable();
if (drawable != null) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
//API 19
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
size = bitmap.getAllocationByteCount();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){
//API 12
size = bitmap.getByteCount();
} else {
//earlier version
size = bitmap.getRowBytes() * bitmap.getHeight();
}
Log.d(TAG, " size = " + size);
} else {
Log.d(TAG, "Drawable is null !");
}
}
getAllocationByteCount()方法可以獲取圖片的實際占用內存大小,在此之前得介紹一種特殊的res/drawable-[density]/文件夾,就是res/drawable-nodpi/,不管當前屏幕的密度如何,系統都不會縮放以此限定符標記的資源。意思就是在這個文件夾中的圖片按原樣進行展示,不會像其它的res/drawable-[density]/那樣改變文件的大小,我們就以此為基準進行分析。
4 圖片的實際內存占用
以實際例子作為分析,把一張大小為376.16K的480x800且位數為8的圖片為例
圖片的像素總數 480x800 = 384000
先使用壓縮前的圖片為例,分析圖片占用的內存大小并打印出來
對圖片進行壓縮后進行同樣得操作
很明顯,壓縮前后內存的占用大小同樣為1536000(Byte),說明圖片的磁盤占用大小與圖片的內存或顯存占用沒有必然關系。從而說明壓縮圖片可以減少我們得apk大小,但是內存的占用是不會變小的,那么圖片的內存占用與什么有關系呢?繼續。。。
圖片的內存占用大小為1536000(Byte),而圖片的原始圖片像素總數為384000,一眼看過去好像沒啥關系,但是真相是384000 * 4 = 1536000(Byte),原始圖片尺寸大小與最終的內存占用大小呈倍數的關系,所以在這里與內存占用大小有直接關系的就是原始圖片尺寸大小(例如:480x800),道理我都懂,但是倍數關系是從哪里來的呢,這就要談論到Bitmap的像素格式了。
Android系統支持4種格式的像素格式,源碼在Bitmap.Config中
/**
* 可用的bitmap配置, 一個bitmap配置描述的是每個像素的存儲格式,這將會影響到圖片的質量 (顏色深
* 度) 以及顯示透明/半透明顏色的能力
*/
public enum Config {
// 這些枚舉中的值必須要與Skia圖像引擎的SkBitmap.h中對應值一一對應
/**
* 只有一個alpha通道
* 每個像素占1個字節
*/
ALPHA_8 (1),
/**
*每個像素占用2個字節,只有RGB 3個通道,沒有alpha 通道
* 紅色的精度是5 bits, 綠色精度是6 bits,藍色精度是5
*/
RGB_565 (3),
/**
* 每個像素占用2個字節.
* (雖然占用內存只有 ARGB8888 的一半,不過已經被官方嫌棄)
*/
@Deprecated
ARGB_4444 (4),
/**
* 每個像素占用4個字節. 每個通道 (RGB的3個通道和alpha
* 的1個透明度通道) 的進度是8bit (256個可能值)
* 這種配置是最靈活的, 質量最好,盡量使用這種格式.
*/
ARGB_8888 (5);
}
由于官方默認使用ARGB_8888格式,導致圖片的每個像素會占用4個Byte大小,所以最終的圖片占用內存大小就是像素總數*像素格式,放到例子里頭就是384000 * 4 = 1536000(Byte),成功接上去了,哈哈哈。。。
小結論:圖片的直接內存占用和圖片的像素總數和系統的像素格式相關,與磁盤存儲的圖片大小無關,其實與磁盤存儲的圖片位數也無關。
5 Android對在res/drawable-[density]/ 文件夾中圖片進行的騷操作
前面提到的圖片實際占用內存大小,是很合理的,但是圖片是放置在
前面也已經提到過res/drawable-nodpi/文件夾,在這個文件夾中的圖片按原樣進行展示,不會像其它的res/drawable-[density]/那樣改變文件的大小,類似于從SD卡或者網絡直接加載一張圖片。
但是如果把圖片放在其它的res/drawable-[density]/ 文件夾中的話,事情就會變得有些不一樣了,系統會根據手機的屏幕密度來縮放對應文件夾中的圖片。
下面就是測試結果,測試手機為360 vizza,手機分辨率為1920*1080,屏幕密度為480dpi,測試圖片為480x800的圖片。
先把圖片放置drawable-ldpi中看占用內存大小,然后依次類比,得出最終的對比數據。
文件夾 | 文件夾dpi | size(Byte) |
---|---|---|
drawable-ldpi | 120 | 24576000 |
drawable-mdpi | 160 | 13824000 |
drawable-hdpi | 240 | 6144000 |
drawable-xhdpi | 320 | 3456000 |
drawable-xxhdpi | 480 | 1536000 |
drawable-xxxhdpi | 640 | 864000 |
看到這個結果先不要慌,穩住,我們能贏...
經過前面的分析,我們知道在res/drawable-nodpi/下圖片的占用內存為1536000(Byte),發現沒有,我加粗的那一行數據中,也就在當圖片放置在res/drawable-xxhdpi/文件夾下面時,圖片所占用的內存也是1536000(Byte),而我們得測試機的屏幕密度就是480dpi,說明在對應屏幕密度的文件下獲取圖片時內存占用不會有變化。
而在把圖片放置其他對應dpi文件夾下時,會出現圖片內存占用出現不同程度的縮放,我們稱與手機屏幕密度一致的文件夾稱之為目標文件夾,當圖片放置的文件夾對應密度比目標文件夾越小時,圖片占用內存越大,當圖片放置的文件夾對應密度比目標文件夾越大時,圖片占用內存越小。
還記得這個表嗎
通用密度 | ldpi | mdpi(基線密度) | hdpi | xhdpi | xxhdpi | xxxhdpi |
---|---|---|---|---|---|---|
描述 | 低 | 中 | 高 | 超高 | 超超高 | 超超超高 |
大小(單位dpi) | 120 | 160 | 240 | 320 | 480 | 640 |
縮放系數 | 0.75 | 1 | 1.5 | 2 | 3 | 4 |
六種通用密度之間遵循 3:4:6:8:12:16 的縮放比率,內存占用縮放的秘密其實就是在這個縮放比率當中,最終的圖片占用內存大小為:
圖片最終內存=圖片原始內存 * (手機屏幕密度/資源圖片文件密度) ^ 2
其實就是圖片寬和高都按縮放比率進行對應的縮放。
舉個栗子:
當圖片放置在res/drawable-ldpi/文件夾下時,圖片內存為1536000(480/120)^2=153600016=24576000(Byte);
當圖片放置在res/drawable-xxhdpi/文件夾下時,圖片內存為1536000(480/480)^2=15360001=1536000(Byte);
當圖片放置在res/drawable-xxxhdpi/文件夾下時,圖片內存為1536000(480/640)^2=15360000.5625=864000(Byte);
注:res/drawable-xxxhdpi/文件夾官方建議只能放啟動圖標,這里只是為了測試才放置測試圖片。
對比一下上表對比數據,都一一對應,說明是ok的。
然后最終結論就是
1、圖片的直接內存占用和圖片的像素總數和系統的像素格式相關,與磁盤存儲的圖片大小無關,其實與磁盤存儲的圖片位數也無關,圖片的直接內存占用大小為:像素總數 * 像素的格式(像素的格式其實就是確定了每個像素占用的字節數)
2、圖片放置在res/drawable-[density]/ 文件夾中時,圖片占用內存大小為:圖片最終內存 = 圖片原始內存 * (手機屏幕密度/資源圖片文件密度) ^ 2