Android图片加载内存占用分析

作者: C6C | 来源:发表于2017-12-05 19:15 被阅读400次

    作为一名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

    相关文章

      网友评论

        本文标题:Android图片加载内存占用分析

        本文链接:https://www.haomeiwen.com/subject/lhdfixtx.html