系列文章
2、Android性能优化-实践篇:Frida监测Binder调用
3、Android性能优化-实践篇-Frida监测Bitmap
一、前言
对于现代应用来说,图片内容是必不可少的,但是由于图片的显示性质,在Android上面需要占用大量的Native内存,并且这部分很难感知,往往是出现OOM之后,才回过头审视这部分。
在Android上面,图片基本都是以Bitmap的形式加载,不管是本地图片,还是网络图片,都会通过某种形式转换成Bitmap来进行展示,以图片A为例,宽高为1080x1920,带透明度,Bitmap占用内存可以达到7.5M(1080x1920x4),如果size更大则占用更大。
所以对于Bitmap的监测是很重要的一件事情,避免内存水位过高,甚至发生OOM。
二、使用Frida监控Bitmap
2.1 分析
实际上目前已经有一些监控Bitmap的手段,但是往往需要集成到App中,且使用过于复杂。
我们依然可以使用Frida来进行Bitmap的监控。
我们知道Frida可以用来Hook Java层方法,那么能不能找一个Bitmap在Java层的统一入口?回忆一下,Bitmap是如何从Resource里面加载的,往往是通过BitmapFactory来进行decode。实际上BitmapFactory提供了多个decode方法,decodeFile/decodeStream/decodeResource,即便是Glide、Fressco这种三方框架,最终也是通过这些方法来实现。
所以我们考虑Hook BitmapFactory的decode这一系列方法。
- 1、添加方法hook
- 2、拿到Bitmap对象
- 3、进行大小判定,如果大于某个阈值(1024x1024),就将此对象打印出来
- 4、打印对应堆栈
到这里我们发现,打印堆栈并不能解决问题,有一些是从网络加载的图片,堆栈并不清楚,那么有没有更好的办法?
实际上在Frida里面,这里有了Java的运行环境,我们可以模拟实现Bitmap的文件写入,也就是:将异常的图片写入到系统中,然后通过adb pull出来,这样就拿到了异常图片本身,拿到之后我们可以容易得判断此图片是否合理,以及云端是否配置了异常的图片。
2.2 实现
代码如下:
Java.perform(function () {
const Bitmap = Java.use('android.graphics.Bitmap');
var Throwable = Java.use("java.lang.Throwable");
var Environment = Java.use('android.os.Environment');
var File = Java.use('java.io.File');
var FileOutputStream = Java.use('java.io.FileOutputStream');
var CompressFormat = Java.use('android.graphics.Bitmap$CompressFormat');
var BitmapFactory = Java.use('android.graphics.BitmapFactory');
// Hook主要解码方法
var decodeMethods = [
'decodeFile',
'decodeStream',
'decodeByteArray',
'decodeResource'
];
var totalSize = 0;
var mb = 1024 * 1024;
var lastIndex = 0;
decodeMethods.forEach(function (method) {
BitmapFactory[method].overloads.forEach(function (overload) {
overload.implementation = function () {
var bitmap = this[method].apply(this, arguments);
if (bitmap !== null) {
var size = bitmap.getByteCount();
if (size > 200 * 1024) {
console.log('[Bitmap Constructor]');
console.log(' ➤ Width : ' + bitmap.getWidth());
console.log(' ➤ Height : ' + bitmap.getHeight());
console.log(' ➤ Size: ' + size);
console.log(' ➤ Thread : ' + Java.use('java.lang.Thread').currentThread().getName());
var stack = Java.use("android.util.Log").getStackTraceString(
Throwable.$new()
);
console.log(stack);
save(bitmap);
}
totalSize += size;
if (totalSize % mb != lastIndex) {
console.log(`Bitmap total size : ${(totalSize / mb).toFixed(2)} Mb`);
}
}
return bitmap;
};
});
});
function save(bitmap) {
try {
// 准备保存路径(SD卡下载目录)
var downloadsDir = Environment.getExternalStoragePublicDirectory(
"Download"
);
var filename = 'frida_bitmap_' + Date.now() + '.png';
var outputFile = File.$new(downloadsDir, filename);
// 保存Bitmap为PNG
var fos = FileOutputStream.$new(outputFile);
bitmap.compress(CompressFormat.PNG.value, 100, fos);
fos.close();
console.log("[+] Bitmap saved to: " + outputFile.getAbsolutePath());
} catch (e) {
console.log("[-] Error saving bitmap: " + e);
}
}
});
效果如下:
将其pull出来之后,可以直接在本地电脑上查看:
三、总结
至此,我们可以对自己的应用进行Bitmap监控,如果发现有异常,定位解决即可,此方案也是仅用于本地监测,如果要监测线上异常,需要考虑别的实现。