1. dp dip dpi px
- dp = dip:Android 为了适配不同的设备弄出来的单位 1dp = dpi(像素密度) / 160
- dpi(dots per inch):像素密度 = px / 英寸
- px:像素
他们之间的换算:屏幕的总 dp = px / (dpi/160)
2. Bitmap 大小计算 看源码
以下是 java 层的源码
1 | 类:`BitmapFactory`.`decodeResource`(Resources res, int id) |
以下是 native 层的源码
1 | 类 `BitmapFactory.cpp`.nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, |
3. Bitmap 的内存开辟
内存申请因为版本不同而有所区别:
Bitmap 的内存申请不同版本间有些许差异,在 3.0-7.0
的 bitmap 像素内存都是存放在 Java heap
中的,而 8.0
以后则是放在 Native heap
中的
看下面代码
1 | @Override |
上面我们创建了一张 2G 大小的 bitmap 我们在 8.0 以下的版本运行是会 OOM 的,而我们在 8.0 以上的版本运行是完全没问题,但 Native 内存多了 2G 的内存。
1 | E/TAG: AvailMem :1654 |
通过之前的源码分析可知 bitmap 的内存创建都是通过 tryAllocPixels
方法来申请的
8.0 以上像素内存在 Native 内存中
1 | auto wrapper = alloc(size, info, rowBytes, ctable); |
看一下源码(8.0以上):
1 | 类 `BitmapFactory.cpp`.doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, |
4. Bitmap 内存的回收
4.1 recycle 方法
如果是 8.0
我们手动调用 recycle
方法,数据是会立即释放的
,因为像素数据本身就是在 Native 层开辟的。但如果是在 8.0 以下
,就算我们手动调用 recycle 方法,数据也是不会立即释放的
,
而是 DeleteWeakGlobalRef 交由 Java GC 来回收
。建议大家翻译一下 recycle 方法注释。注意:以上的所说的释放数据仅代表释放像素数据
,并未释放 Native 层的 Bitmap 对象。
4.2 不手动调用 recycle 方法时Native 层的 Bitmap 是怎么回收
- Android M(6.0) 版本及以前的版本, Bitmap 的内存回收主要是通过 BitmapFinalizer 来完成的(类似于 GC 时 Object 的finalize()方法)
- Android M(6.0) 以上版本 虽然没有了 BitmapFinalizer 类,但在 new Bitmap 时会注册 native 的 Finalizer 方法
总结:其实无论是 Android M 前还是之后,释放 Native 层的 Bitmap 对象的思想都是去监听 Java 层的 Bitmap 是否被释放,一旦当 Java 层的 Bitmap 对象被释放则立即去释放 Native 层的 Bitmap 。只不过 Android M 前是基于 Java 的 GC 机制,而 Android M 后是注册 native 的 Finalizer 方法。
5.Bitmap 的内存复用
内存复用好处:防止反复的去开辟和释放内存,内存抖动 -> GC -> 卡顿
Bitmap 绝对是我们 Android 开发中最容易引起 OOM 的对象之一,因为其占用的像素数据内存比较大,而加载图片又是很常见的操作。如果不断反复的去开辟和销毁 Bitmap 数据内存,势必可能会引起应用的内存抖动,因此 Google 的开发者也为我们想了一些办法,那就是允许 Bitmap 内存复用,具体如下:
- 被复用的 Bitmap
必须为 Mutable(通过 BitmapFactory.Options 设置),然后 options.inBitmap = bitmap1,设置 inBitmap复用的Bitmap是谁
- 4.4 之前,将要解码的图像(无论是资源还是流)必须是 jpeg 或 png 格式且和被复用的 Bitmap 大小一样,其中BitmapFactory.Options#inSampleSize 字段必须设置为 1,要求比较严苛
- 4.4 以后,将要解码的图像的内存需要小于等于要复用的 Bitmap 的内存
1 | // 不复用的写法,消耗内存 32 M |
6. xh,xxh,xxxh 放哪个文件夹更高效
- 放当前主流的 xxh , native 源码中只会开辟一次内存
7.Bitmap 的内存优化与适配
做效果的时候,最好按比例去计算
8.线上 OOM 的 dump 收集
首先 OOM
是可以 try catch
的,但是一般不这么做。
可以在 崩溃日志收集那儿 之前进行 dump 内存,怎么 dump 内存 可以参考 leakCanny