前言:
总共三步走:1. native 代码;2. 编译打包so;3. java 层 attach。
具体监听可以得到什么数据,数据怎么用不在该篇的叙述范围之内。
写 native 代码
随便找个地方创建一个 jni 文件夹
关键的代码如下:
# Application.mk
APP_ABI := arm64-v8a # armeabi-v7a x86 # 你需要生成什么版本的 so
# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := monitor_agent # 唯一的模块名,默认生成为 lib<name>.so
LOCAL_LDLIBS := -llog # 依赖 native 的日志 lib
LOCAL_SRC_FILES := agentlib.cpp # 自己的资源文件
include $(BUILD_SHARED_LIBRARY)
/*** jvmti.h ***/
直接去 JDK 里面 CV 一个 jvmti.h 出来。MAC 中的位置见下图。
/*** agentlib.cpp ***/
void JNICALL
objectAlloc(jvmtiEnv
*jvmti_env,
JNIEnv *jni_env, jthread
thread,
jobject object, jclass
object_klass,
jlong size
) {
// 对象创建,这里可以记录一下
}
void JNICALL
objectFree(jvmtiEnv *jvmti_env,jlong tag) {
// 对象释放
}
extern "C"
JNIEXPORT void JNICALL
Java_com_tencent_qjvmti_Monitor_agent_1init(JNIEnv *env, jclass clazz
) {
LOGE("Java_com_tencent_qjvmti_Monitor_agent_1init");
//开启jvm事件监听
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMObjectAlloc = &objectAlloc;
callbacks.ObjectFree = &objectFree;
//设置回调函数
mJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));
//开启监听
mJvmtiEnv-> SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC,nullptr);
mJvmtiEnv-> SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_OBJECT_FREE, nullptr);
}
运行编译
找 ndk-build 在哪,直接运行(运行目录错的话,会提示你错误,比如目录错了)
/Users/lichen/Library/Android/sdk/ndk/22.1.7171670/ndk-build NDK_PROJECT_PATH=.
运行成功之后就会在 jni 的同级目录下面生成你的 so 了。
加载代理
移动 so
agent 的 so 与普通 so 不同,需要我们复制一份出来用。
直接打包进 apk lib 目录里面的 so 读不出来。所以我们直接放到 assets 中方便运行时读取。(或者指定 so 目录,push 进去,运行时读取一下)
读取 so,加载代理
// native 层的 init 方法
private native static void agent_init();
public static void init(Context application) {
//最低支持Android 8.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
//获取so的地址后加载
String agentPath = getAgentLibPath(application);
// 先加载 so
System.load(agentPath);
//加载jvmti
attachAgent(agentPath, application.getClassLoader());
//开启jvmti事件监听
agent_init();
}
// 读取复制之后的 agent.so 的 path
private static String getAgentLibPath(Context context) {
try {
return loadAssetsToCache(context, "libmonitor_agent.so");
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* 将asset里面的文件复制到
* /data/user/0/报名/files 中
*/
private static String loadAssetsToCache(Context context, String assertName) {
String filePath = context.getFilesDir().getAbsolutePath();
AssetManager assetManager = context.getAssets();
try {
InputStream inputStream = assetManager.open(assertName);
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
//保存到本地的文件夹下的文件
String name = filePath + "/" + assertName;
FileOutputStream fileOutputStream = new FileOutputStream(name);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, count);
}
fileOutputStream.flush();
fileOutputStream.close();
inputStream.close();
return name;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
一些小坑
- cmake 和 ndk,网上关于 so 的话题基本上都是 cmake 的,其实到最后认清楚 so 是可以独立出来,不是一定要在 gradle 里面配置的话,这里就可以跳出来了。二者都只不过是 so 的编译工具。
- 如何动态加载 so,直接打包进 apk 的 lib 里面是个大坑,读取不到,最后放到 assets 中再加载到 app 目录中即可。