转载请注明出处:https://lizhaoxuan.github.io
前言
早些时候对Android下GC调用时机比较好奇,所以写了一些case测试各种情况下Android GC调用时机与现象,感兴趣的话可以跳过去瞅瞅 : 《Android GC机制实践调研》
在这个过程中发现一个让人非常震惊的问题:从资源文件中加载一张110kb的图片创建Bitmap对象,占用的内存高达40MB!
为什么为什么为什么??
于是这篇博客便产生了,我希望可以通过一系列测试case,来了解Bitmap在各种场景下的各种使用姿势将会在内存占用和加载速度两方面都有哪些表现,从而从中探索可能的优化点和最佳实践。
各种场景下创建Bitmap内存占用
从资源文件创建Bitmap
1.不同分辨率的drawable文件夹下加载相同素材,Bitmap的内存占用大小
这里我们准备了一张117.16kb 1200*900的jpg图片放到了res/各种分辨率的drawabe目录下。对他们进行分别加载然后输出各种值进行对比,需要说明一下这里加载的意思可以是:执行bitmapFactory.decodeResource
。 与给ImageView设置Resource 、给布局设置背景等创建创建Bitmap或进行图片显示的操作相同。
看下实验数据
【努比亚Z9 Nubia NX508J】 分辨率1080 * 1920 像素密度:424ppi
文件夹 | getByteCount | getRowBytes | getHeight | getWidth |
---|---|---|---|---|
drawable | 38880000b ≈ 37mb | 14400b | 2700 | 3600 |
mdip | 38880000b ≈ 37mb | 14400b | 2700 | 3600 |
xhdip | 9720000b ≈ 9mb | 7200b | 1350 | 1800 |
xxhdip | 4320000b ≈ 4mb | 4800b | 900 | 1200 |
38880000b是什么概念?37MB!!
想一下,你的应用还啥都没干呢,就仅是加载了一张图片将近40MB的内存就被占用了,再加上其他一些操作,内存妥妥的就跳到临界值了,如果再有一些不当的溢出,OOM指日可待!
似乎,图片放在分辨率越高的文件夹下,内存占用越小
2.不同格式的图片创建Bitmap内存占用大小
上面测试用的是jpg,而通常我们开发中使用的都是png,看到这么大的内存占用,我有想过是否是因为图片格式的问题,于是把这张图片丢到美图秀秀里(美图秀秀真好用),然后分别导出了长宽一样的jpg和png两张图片,放到资源文件夹中进行加载。
【努比亚Z9 Nubia NX508J】
drawable_jpg_1.jpg 1200*900 135.76kb
drawable_png_1.png 1200*900 1.64mb
jpg getByteCount : 38880000 getRowBytes:14400 getHeight:2700 getWidth:3600
png getByteCount : 38880000 getRowBytes:14400 getHeight:2700 getWidth:3600
内存占用和之前一样,并且虽然png的图片本身高达1.64mb,但内存占用依然只是37mb。
从资源文件中加载图片的内存占用与图片格式、图片占硬盘大小无关!(但和apk包体积有关)
3.不同的分辨率的设备加载同一张素材,Bitmap内存占用大小
Android存在着很多分辨率适配问题,不同drawable文件夹也是为了适配而存在的,所以我们还要挑几个分辨率不一样的手机看一下:
【荣耀畅玩4X】 分辨率:1280 * 720 像素密度:267ppi
文件夹 | getByteCount | getRowBytes | getHeight | getWidth |
---|---|---|---|---|
drawable | 17280000b ≈ 16mb | 9600b | 1800 | 2400 |
mdip | 17280000b ≈ 16mb | 9600b | 1800 | 2400 |
xhdip | 4320000b ≈ 4mb | 4800b | 900 | 1200 |
xxhdip | 1920000b ≈ 2mb | 3200b | 600 | 800 |
诶?很明显啊,选一个分辨率低一点的手机,果然相同条件的图片加载内存占用是不一样的。我这正好还有一个和努比亚分辨率一样的手机,用这个也测一下:
【乐视 le x620】 分辨率:1080 * 1920 像素密度:401ppi
文件夹 | getByteCount | getRowBytes | getHeight | getWidth |
---|---|---|---|---|
drawable | 29773800b ≈ 28mb | 12600b | 2363 | 3150 |
mdip | 29773800b ≈ 28mb | 12600b | 2363 | 3150 |
xhdip | 7440300b ≈ 7mb | 6300b | 1181 | 1575 |
xxhdip | 3309600b ≈ 3mb | 4200b | 788 | 1050 |
问题来了,虽然分辨率是一样的,但是内存占用却不同,关键因素不在分辨率,那在什么呢?
我们都知道我们的应用程序在不同的设备上,Android系统会从不同的资源文件夹下获取图片资源,而其选择的本质不是屏幕的长宽比,是像素密度。
所以这里的关键在于像素密度!从资源文件中加载图片的内存占用与像素密度有关!
OK,上面的结论都是通过数据推理出来的一些表象现状。这里先进行一个小总结:
- 从资源文件中创建Bitmap,图片所在分辨率越高的drawable文件夹,Bitmap占用内存越小。(单从内存的角度可以这样考量,但从实际应用过程中,所有素材都放到分辨率最高的文件夹并不是合适的做法)
- 从资源文件中创建Bitmap,Bitmap占用内存大小与图片宽高极为有关,与图片本身格式以及占硬盘大小无关。
- 从资源文件中创建Bitmap,Bitmap占用内存大小与手机像素密度极为有关。
从网络或本地存储创建Bitmap
通过资源文件创建Bitmap,Android系统会为了适配不同屏幕,而对图片进行一些调整,导致不同情况下内存占用区别很大。那么如果是从网络或本地存储中创建的Bitmap也会因为设备的像素密度而有很大差异吗?
我们来实验一下,我从网络下载一张图片,然后观察内存情况。
我选了一张216932b ≈ 212kb 1600 *1280 的jpg图片下载,并创建一个Bitmap
【努比亚Z9 Nubia NX508J 分辨率1080 * 1920 像素密度:424ppi 】
网络下载:
byte[] size : 216932 ≈ 212kb
bitmap size : 8192000 ≈ 7.8125mb
同一张图片放到资源文件中加载:
drawable getByteCount : 73728000 ≈ 70mb getRowBytes:19200 getHeight:3840 getWidth:4800
mdip getByteCount : 73728000 ≈ 70mb getRowBytes:19200 getHeight:3840 getWidth:4800
xhdip getByteCount : 18432000 ≈ 17.5mb getRowBytes:9600 getHeight:1920 getWidth: