Android圖片加載內存占用分析

作為一名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]/ 文件夾中圖片進行的騷操作

前面提到的圖片實際占用內存大小,是很合理的,但是圖片是放置在


nodpi.png

前面也已經提到過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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容