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,然后重新加载图片
- 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
- 内存复用
- 在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
- 基于当前的上下文来决定怎么加载图片—-完整、压缩、怎么压缩(采样率inSampleSize)
局部加载
单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等
- 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
- 采样之后内存是小了,可是图的尺寸也小了,可以使用矩阵,内存不变,放大图片
**
//canvas
Matrix matrix = new Matrix();
matrix.preScale(2, 2, 0, 0);
canvas.drawBitmap(bitmap, matrix, paint);
//ImageView
Matrix matrix = new Matrix();
matrix.postScale(2, 2, 0, 0);
imageView.setImageMatrix(matrix);
imageView.setScaleType(ScaleType.MATRIX);
imageView.setImageBitmap(bitmap);
内存优化
- 去除重复图片
- 检查图片尺寸+bitmap数组进行hash对比
- 大图片监控
- 插桩方式在setBitmap方法后拿到bitmap的宽高和view的宽高做比较
- 定时去获取内存快照中view和bitmap中宽高作比较