Bitmap

Bitmap在android中指的是一张图片,可以是png格式也可以是jpe等其他常用格式

Bitmap像素格式

  • ALPHA_8:颜色信息只由透明度组成,占8位
  • ARGB_4444:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位
  • ARGB_8888:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置
  • RGB_565:颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位

Bitmap内存大小计算

图片长度 x 图片宽度 x 一个像素点占用的字节数

加载Bitmap

  • decodeFile
    • 调用了decodeStream
  • decodeResource
    • 调用了decodeStream
  • decodeStream
  • decodeByteArray

高效加载Bitmap

  • 通过采样率高效加载
    • 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
      • BitmapFactory只会解析图片的原始宽/高信息,并不会去真正地加载图片,所以这个操作是轻量级的
    • 从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数
    • 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
    • 将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片
  • 内存复用
    • 在Bitmap中引入了一个新的字段BitmapFactory.Options.inBitmap,设置此字段为true后,解码方法会尝试复用一张存在的Bitmap
  • 使用缓存
    • LruCache-LruCache作为Bitmap在内存中的存放容器
    • DiskLruCache-在sd卡则使用DiskLruCache来统一管理磁盘上的图片缓存
  • 内存复用 + 缓存
    • 缓存中即将回收的使用弱引用(内存不足时会GC), 设置inBitmap来复用内存

压缩图片

先尺寸压缩,后质量压缩,因为尺寸压缩可以设置options.inJustDecodeBounds = true仅获取 Bitmap 基本信息,几乎不占用应用程序的运行内存

  • 质量压缩
    • 不改变图片的尺寸
    • Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
      • 通过不断较少 quality 来限制文件大小
      • 假如图片特别大,当执行ByteArrayOutputStream.toByteArray() 这行时很可能 OOM
      • 所以质量压缩不能先执行
  • 尺寸压缩
    • 一般用于生成缩略图
    • 通过缩放图片像素来减少图片占用内存大小
    • 获取图片的一些属性,例如图片宽高,图片类型等等(inJustDecodeBounds参数设为true)
      • 基于当前的上下文来决定怎么加载图片—-完整、压缩、怎么压缩(采样率inSampleSize)
        • 完整图片加载到内存中所使用内存 vs 可分配内存
        • 显示图片的控件的大小
        • 当前设备的屏幕大小和密度
      • 计算采样率inSampleSize
        • Android 源码计算方式
        • 常规算法
        • luban

局部加载

单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等

  • BitmapRegionDecoder
  • 根据屏幕大小将这个图切成N多块,要加载时根据屏幕滑动加载,类似于目前常用的谷歌地图方式
    • 还可以实现bitmap复用

getByteCount vs getAllocationByteCount

一般情况下getByteCount()和getAllocationByteCount()是相等的。但是Bitmap内存如果复用之后,两者就不一样了

  • getByteCount()代表存储Bitmap的色素需要的最少内存
  • getAllocationByteCount()代表在内存中为Bitmap分配的内存大小

    通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大

  • getByteCount()表示新解码图片占用内存的大小

  • getAllocationByteCount()表示被复用Bitmap真实占用的内存大小

Android Bitmap内存分配变化

  • Android3.0之前, Bitmap对象放在Java堆,像素数据放在Native内存
    • 手动调用recycle释放
  • Android3.0—Android7.0 对象和像素数据统一,放到Java堆中
    • 就算不调用recycle,Bitmap内存也会随对象一起被回收
    • Bitmap消耗内存大,所以放在Java堆比较容易导致OOM、大量GC
  • Android 8.0 重新将 Bitmap 内存放回到 Native 中
  • 优化3.0—7.0
    • 通过直接调用 libandroid_runtime.so 中 Bitmap 的构造函数,可以得到一张空的 Bitmap 对象,而它的内存是放到 Native 堆中
    • 通过系统的方法创建一张普通的 Java Bitmap
    • 将 Java Bitmap 的内容绘制到之前申请的空的 Native Bitmap 中
    • 将申请的 Java Bitmap 释放,实现图片内存的“偷龙转凤”。
    • 这个“黑科技”有两个主要问题,一个是兼容性问题,另外一个是频繁申请释放 Java Bitmap 容易导致内存抖动

减少内存占用

  • 使用采样 + RGB_565
    • 采样之后内存是小了,可是图的尺寸也小了,可以使用矩阵,内存不变,放大图片

**

  1. //canvas
  2. Matrix matrix = new Matrix();
  3. matrix.preScale(2, 2, 0, 0);
  4. canvas.drawBitmap(bitmap, matrix, paint);
  5. //ImageView
  6. Matrix matrix = new Matrix();
  7. matrix.postScale(2, 2, 0, 0);
  8. imageView.setImageMatrix(matrix);
  9. imageView.setScaleType(ScaleType.MATRIX);
  10. imageView.setImageBitmap(bitmap);

内存优化

  • 去除重复图片
    • 检查图片尺寸+bitmap数组进行hash对比
  • 大图片监控
    • 插桩方式在setBitmap方法后拿到bitmap的宽高和view的宽高做比较
    • 定时去获取内存快照中view和bitmap中宽高作比较