Android让图片占用尽可能少的内存

减少图片内存,防止OOM crash

Posted by PanMin on February 2, 2018

使图片最小化

  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

    结论:由此可见大图比小图占用更多的内存,图片大小(分辨率)与占用内存成正比关系

    备注:图片在硬盘上占用的磁盘空间大小,与在内存中占用的内存大小完全不一样,不是一个概念,不要混淆

  2. 使用.9图代替大图

    根据上文中图片大小与内存的关系,可以更加深刻的理解Android中.9图片的作用,它不但能减少apk的体积,还能减少图片占用内存。

  3. 使用绘制背景或者Drawable代替图片

    有些时候我们根本不需要图片,而是自己绘制背景,可以在自定义View的onDraw中绘制背景,当然最方便的还是使用系统的Drawable,绘制部分交给系统去完成。下面测试图片与Drawable的内存占用对比

    测试设备: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标签定义)静态使用和动态使用(圆角,渐变实现)

在内存中压缩图片

加载大图片时需要对图片进行压缩,使用等比例压缩方法直接在内存中处理图片

Options options = new BitmapFactory.Options();
options.inSampleSize = 5;           // 原图的五分之一,设置为2则为二分之一
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

这样做要注意的是,图片质量会变差,inSampleSize设置的值越大,图片质量就越差。

读取位图尺寸和类型时不把图片加载到内存中

有时候我们取得一张图片,也许只是为了获得这个图片的一些信息,比如图片的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;

用完就回收

由于Android外层是使用java,而底层使用的是C语言为图片对象分配的内存空间。所以我们的外部虽然看起来释放了,但里层却并不一定完全释放了,我们使用完图片后最好再释放掉里层的内存空间。

if (!bitmapObject.isRecyled()) {    // Bitmap对象没有被回收
    bitmapObject.recycle();         // 释放
    System.gc();                    // 提醒系统及时回收
}

降低要显示的图片色彩质量

  1. 颜色模型

    RGB(ARGB) RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。在Android中还有包含透明度Alpha的颜色模型,即ARGB。

  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的时候一般也采用整型编码。

  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的内存。

  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图本身尽可能小,另外能使用绘制实现就不要加一个图片资源。有些时候,在不影响用户体验的情况下,可以降低图片素材质量,比如不需要透明度的就不要了,有些透明度用肉眼看不出来。