1.前期准备
在项目根目录build.gradle
中,添加仓库地址:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
2.案例实践
构建一个新的Library Module,其中build.gradle
中添加依赖:
dependencies {
implementation 'com.github.bytedance:memory-leak-detector:0.2.1'
}
Raphael API使用
Raphael是有两种方式可以监控进程中native内存: 其一是通过java代码来实现,其二是通过adb shell 发送广播命令行来控制。
启动监控:
// 监控指定的so
Raphael.start(
Raphael.MAP64_MODE|Raphael.ALLOC_MODE|0x0F0000|1024,
"/storage/emulated/0/raphael", // 需要申请读写sdcard权限
null
);
参数:
- 第一个参数: 指定模式
- 第二个参数: native内存文件存放的目录,若是sdcard,则需要申请权限
- 第三个参数:指定监控的so库。比如,监控libxxx.so中内存,则传入
".*libxxx\\.so$"
; 若传入null ,则监控进程中全部so库;
等同于adb shell 命令行实现:
## 监控整个进程(RaphaelReceiver 组件所在的进程)
## 0x0CF0400=Raphael.MAP64_MODE|Raphael.ALLOC_MODE|0x0F0000|1024
adb shell am broadcast -a com.bytedance.raphael.ACTION_START -f 0x01000000 --es configs 0xCF0400
打印内存:
// 代码控制
Raphael.print();
等同于:
## 本地广播
adb shell am broadcast -a com.bytedance.raphael.ACTION_PRINT -f 0x01000000
更多API详情,请阅读Raphael API使用
实现思路:
为了灵活使用,建议使用两者配合使用,首先通过java 代码方式 在Application中启动监控整个进程,其次,涉及业务场景后,通过adb shell 来打印内存,缓存到指定目录下。
代码实现
基于raphael api ,简单封装下,代码如下:
public class RaphaelUtils {
/**
* 监听整个进程中全部的so库内存泄漏
*/
public static void monitorAllNativeSo(){
String regexSo=null;
monitorNativeSo(regexSo);
}
/**
* 监控指定的 so 库内存泄漏
* @param regexSo
*/
public static void monitorNativeSo(String regexSo){
final String spaceDir="/storage/emulated/0/raphael";
monitorNativeSo(spaceDir,regexSo);
}
/**
* 用于监听so内存泄漏,存储到指定位置
* 也可以通过adb shell 命令行来执行(灵活使用):
* adb shell am broadcast -a com.bytedance.raphael.ACTION_START -f 0x01000000 --es configs 0xCF0400 --es regex ".*libXXX\\.so$"
*
*
* @param spaceDir 记录泄漏的存放地址,这里必须获取 读写权限。
* 比如:"/storage/emulated/0/raphael"
* @param regexSo 传入null ,监听全部so库。
*/
public static void monitorNativeSo(String spaceDir,String regexSo){
// 监控整个进程
Raphael.start(Raphael.MAP64_MODE|Raphael.ALLOC_MODE|0x0F0000|1024,
spaceDir,
regexSo
);
}
/**
* 打印内存泄漏的信息,存储导致sdcard中
*
* 也可以通过adb shell 命令行来执行(灵活使用):
* adb shell am broadcast -a com.bytedance.raphael.ACTION_PRINT -f 0x01000000
*/
public static void printNativeLeak(){
Raphael.print();
}
/**
* 停止监听
* 也可以通过adb shell 命令行来执行(灵活使用):
* adb shell am broadcast -a com.bytedance.raphael.ACTION_STOP -f 0x01000000
*/
public static void stopMonitor(){
Raphael.stop();
}
}
在Application中onCreate()指定监控so库:
public void onCreate() {
super.onCreate();
RaphaelUtils.monitorNativeSo( ".*libAppPlayx\\.so$");
}
构建apk 进行安装,启动后,赋予读写权限,进行一些列的业务操作,进行adb shell 命令操作,打印内存情况:
adb shell am broadcast -a com.bytedance.raphael.ACTION_PRINT -f 0x01000000
内存文件将缓存在Raphael.start()中第二个传入的目录下,这里是"/storage/emulated/0/raphael"
:
将其拷贝出来,通过adb pull 也来拷贝。
配置Python 环境:
report 文件是无法直接进行获取native 堆栈信息,需要借用raphael.py 进行转换。先配置python的环境,在官网下载python 3.x ,进行默认安装。
在命令中执行python ,检查是否安装成功:
为了方便,将需要的几个文件都拷贝到同一个目录中:
- so对应addr2line工具
- 带有符号表的so库
- report文件
- py脚本(raphael.py/mmap.py)
执行命令行如下,若是report 过大,执行会过长:
执行完后,会在同个目录中生成leak-doublets.txt,里面包含内存信息和堆栈信息。
打开默认生成leak-doublets.txt:
204,364,751 totals // 单指raphael拦截到的未释放的虚拟内存总和
204,364,751 libAppPlayJNI.so //该库未释放的内存
0x0000007865600000, 58741400, 1
0x0000000001171d4c /data/app/~~vZjWn3Y0HAlsqNnxySQWjg==/com.xxx.miniworld-J6zXyou6Nnd_y7pG4EZo7g==/lib/arm64/libAppxxxJNI.so (F:/minichina/miniad/xxxx/OgreSingleton.h:110)
....
信息解读:
内存和调用次数:
0x0000007865600000, 58741400, 1
0x0000007865600000
是report里此堆栈第一次分配出的内存地址,58741400
是report里此堆栈的内存总和, 1
是report里此堆栈的总次数
native的调用栈:
0x0000000001171d4c /data/app/~~vZjWn3Y0HAlsqNnxySQWjg==/com.xxx.miniworld-J6zXyou6Nnd_y7pG4EZo7g==/lib/arm64/libAppxxxJNI.so (F:/minichina/miniad/xxxx/OgreSingleton.h:110)
根据以上信息,就很好分析哪些native 对象占用内存,在某些业务场景下该进行释放。
3.借鉴
raphael其中有一个很好的借鉴方面,通过adb shell 命令行发送广播命令,从而执行一些逻辑操作。