JNI的异常,会在Linux系统层发生崩溃的时候发送一些特定的信号,通过捕捉这些特定的信号来避免Jni 异常崩溃的发生,并回调到JAVA层去处理这个异常
项目中Native层的结构体有点庞大和复杂,在和java对象进行互相传递的过程中,如果对内存的使用不当(开辟新地址以及释放地址等)容易出现内存地址使用异常的情况,从而导致很匪夷所思的地方出现崩溃,如一下异常日志:
2021-07-20 10:41:09.868 3868-3968/? A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x7f93e00000 in tid 3968 (Thread-13)
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: Build fingerprint: 'Android/msm8953_64/msm8953_64:7.1.2/N2G47H/root05281558:userdebug/JL-fly-keys-V1.0.2'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: Revision: '0'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: ABI: 'arm64'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: pid: 3868, tid: 3968, name: Thread-13 >>> com.medlander.b40tablet <<<
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7f93e00000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x0 0000007f93dfff10 x1 0000007f940e7270 x2 0000007f94000000 x3 0000000000000000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x4 00000000000000e7 x5 0000000071a62432 x6 0000000012ff7864 x7 0000007f93929e94
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x8 0000007f91afebb4 x9 0000000000001088 x10 0000000000001108 x11 0000000000001158
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x12 0000000000000007 x13 0000000000000008 x14 0000000000000000 x15 00000000fff10000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x16 0000007fb63875a0 x17 0000007fb632ebb4 x18 0000000000000000 x19 0000007f93c29b00
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x20 0000000000000000 x21 0000000000000003 x22 0000000012fe0640 x23 0000000012fdaeb0
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x24 0000000000000000 x25 0000000012fe05e0 x26 0000007f9392b4cc x27 0000007f9392b4d4
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: x28 0000007f9392b4d0 x29 0000007f9392b380 x30 0000007fb1513550
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: sp 0000007f9392b260 pc 0000007fb15135fc pstate 0000000020000000
2021-07-20 10:41:09.978 4270-4270/? A/DEBUG: backtrace:
2021-07-20 10:41:09.979 4270-4270/? A/DEBUG: #00 pc 000000000001a5fc /data/app/com.medlander.b40tablet-1/lib/arm64/libmedlander-lib_V1.1.so (Java_android_1medlander_1lib_1api_course_CommonCourseDetailsAddress_setCourseItemNew+564)
2021-07-20 10:41:09.979 4270-4270/? A/DEBUG: #01 pc 000000000113a254 /data/app/com.medlander.b40tablet-1/oat/arm64/base.odex (offset 0x110a000)
根本很难定位问题具体出现在哪里 ,这时候我们可以捕获这个异常信号signal 11 (SIGSEGV)来处理,这里使用sigaction以及sigsetjmp结合的方式来检测和捕获。
随便自定义一个Exception
package com.medlander.candjavaobject;
/**
* FileName: JniException
* Author: Jokey
* Date: 2021/7/23 15:38
* Description: 描述
* Version 版本号
*/
public class JniException extends Exception {
public JniException() {
super();
}
public JniException(String message) {
super(message);
}
}
Mainactivity中的关键代码
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
add();
} catch (JniException e) {
e.printStackTrace();
}
}
});
Native实现add方法
jint add() {
char *a = NULL;
int v1 = a[1] - '0';
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "v1 %d", v1);
char *b = NULL;
int v2 = b[1] - '0';
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "v2 %d", v2);
return v1 + v2;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_medlander_candjavaobject_MainActivity_add(JNIEnv *env, jobject thiz) {
// 代码跳转锚点
int flag = sigsetjmp(JUMP_ANCHOR, 1);
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "flag = %d", flag);
if (flag != 0) {
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "sigsetjmp != 0");
env->ExceptionClear();
jclass cls = env->FindClass("com/medlander/candjavaobject/JniException");
/* 如果这个异常类没有找到,VM会抛出一个NowClassDefFoundError异常 */
if (cls != NULL) {
env->ThrowNew(cls, "异常了"); // 抛出指定名字的异常
}
/* 释放局部引用 */
env->DeleteLocalRef(cls);
return -1;
}
// 注册要捕捉的系统信号量
struct sigaction sigact;
struct sigaction old_action;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigset_t block_mask;
sigemptyset(&block_mask);
sigaddset(&block_mask, SIGABRT); // handler处理捕捉到的信号量时,需要阻塞的信号
sigaddset(&block_mask, SIGSEGV); // handler处理捕捉到的信号量时,需要阻塞的信号
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigact.sa_mask = block_mask;
sigact.sa_handler = exception_handler;
sigaction(SIGINT, &sigact, NULL); // 注册要捕捉的信号
sigaction(SIGABRT, &sigact, NULL); // 注册要捕捉的信号
sigaction(SIGSEGV, &sigact, NULL); // 注册要捕捉的信号
}
int sum = add();
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "sum %d", sum);
return sum;
}
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
/*
jni捕获异常的方法之二:捕捉系统崩溃信号,适用于代码量大的情况。
*/
// 定义代码跳转锚点
sigjmp_buf JUMP_ANCHOR;
volatile sig_atomic_t error_cnt = 0;
void exception_handler(int errorCode) {
error_cnt += 1;
__android_log_print(ANDROID_LOG_INFO, "jni_jokey", "JNI_ERROR, error code %d, cnt %d",
errorCode, error_cnt);
// DO SOME CLEAN STAFF HERE...
// jump to main function to do exception process
siglongjmp(JUMP_ANCHOR, 1);
}
很明显add()会报异常崩溃,我们在native开头注册了捕捉的信号,当异常发生时会跳转到exception_handler,再通过siglongjmp跳转到sigsetjmp段,进行处理,记得env->ExceptionClear();来清除异常阻止崩溃,然后env->ThrowNew(cls, "异常了");来抛给Java处理
注意: sigsetjmp(JUMP_ANCHOR, 1);的返回值就是siglongjmp(JUMP_ANCHOR, 1);的第二个参数