购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3.2 超大Bitmap优化

当我们掌握了字节码插桩技术后,就可以对程序中的超大Bitmap进行优化了。该优化方案分为两个步骤:一是拦截Bitmap的创建并发现内存占用较大的Bitmap;二是在拦截逻辑中对异常Bitmap进行优化。笔者继续通过Lancet来实现该优化方案。

1.拦截Bitmap创建

想要检测出异常的Bitmap,最好的时机便是Bitmap创建的时候,因此我们需要在Bitmap创建前就进行拦截。为了寻找拦截的入口,我们需要先分析一下Bitmap的源码,了解它的创建流程。

Bitmap是通过Bitmap.createBitmap静态函数来创建的,而createBitmap静态函数又会调用Native层的Bitmap.cpp对象来创建真正的Bitmap,最终的Bitmap实际上是通过调用calloc函数创建的一块用来存放图片元数据的内存区域。创建流程如图2-32所示。

图2-32 Bitmap创建流程

结合上面的流程图可以发现,拦截的入口有两个:一是在Java层创建Bitmap的时候进行拦截,二是通过NativeHook技术来拦截Native层的Bitmap创建函数。但是拦截Native层的Bitmap创建流程比较复杂,稳定性也差一些,并且即使我们在Native层进行了Bitmap的拦截且发现了异常,还是要通过JNI调用获取Java层的堆栈后才能有效地定位异常申请Bitmap的位置。因此这里推荐在Java层进行Bitmap的创建拦截,Java层创建Bitmap的静态方法主要有下面几个。

所以我们只需要拦截这几个方法,就能检测到程序中的Bitmap是否已经创建了,这里笔者以其中一个创建方法为例,通过Lancet进行拦截。通过Lancet,我们不需要写Gradle脚本,也不需要进行任何字节码操作,直接通过Java代码和注解的方式就能实现字节码操作。在拦截的代码逻辑中,会在Bitmap创建之前检测所创建的Bitmap的内存占用大小并进行日志输出。Bitmap的格式不一样,内存消耗会不一样,常见的ARGB_8888格式的大小是4B,所以用这个格式来展示图片,所占用的内存大小就是图片的宽×高×4B,其他格式(如ARGB_4444和RGB_565)则是2B。

运行后,通过日志可以看到,代码成功检测到了Bitmap的创建并输出了所创建的大小,如图2-33所示。

图2-33 拦截成功日志

2.完善拦截逻辑

实际上,仅打印Bitmap的大小对优化超大的Bitmap是没有多大帮助的,所以需要继续完善插桩逻辑。我们可以设置一个Bitmap大小的阈值,阈值可以根据实际的场景来确定,这里的策略是根据机型和屏幕分辨率来设置,比如一台分辨率为1920×1080的低端机型,它的Bitmap的最大阈值为15MB,这是一张刚好铺满整个手机屏幕且格式为ARGB_8888的图片所占用的内存大小。对于超过这个阈值的,需要打印出堆栈信息,并上报异常。有了这些信息我们就可以定位图片的具体位置,并进一步排查该图片是否异常。

除了检测异常图片并收集日志数据外,我们还可以进行兜底优化,比如低端机型的可用内存并不多,那么就可以在拦截逻辑中将大小超过阈值的图片按比例缩放,缩放规则可以是将超过屏幕分辨率的图片的宽高按照比例缩小至屏幕分辨率的宽高,还可以将ARGB_8888格式降为RGB_565格式,这样图片的内存占用直接减少了一半。

结合上面提到的策略,进一步优化后的Bitmap拦截函数如下。在代码中,我们只需要修改入参中的width、height或者Config等属性,就能完成对图片的重设置以实现兜底优化。为了避免在兜底优化时出现一些“误伤”,我们还可以增加白名单机制,只有在非白名单的Activity场景中才进行兜底优化。 uOpXJaObJZiLDT2ZADAeAaHgqCtVTxEUPvU+wtkA14WCxNc4CUX1vCu276HZ/T+7

点击中间区域
呼出菜单
上一章
目录
下一章
×