最近在做android的一个功能,使用到系统挂载的sd,u盘等外置存储,当这些外置存储被unmount后,如果我的app还打开着外置存储路径下的文件,系统的vold 服务就会给使用这个资源的app发过来一个Interrupt信号, linux系统对这个信号的默认处理,就是把接受到该信号的进程杀掉,也就是我的app被vold强行给干掉了。
这里的interrupt信号不是 java中的线程 interrupt, 这是linux系统的信号, 针对于进程而言, 可以使用kill 命令发送信号https://blog.csdn.net/u012459903/article/details/95170776
如果是linux环境下开发,自然我们可以用一个 signal()函数 或者 sigaction()函数 安装一个信号处理函数来替代系统的默认行为,就可以避免这类问题发生,这样我们程序可以自行处理这个interrupt信号,而不会被系统给默认杀掉。android也是同样的原理,其底层任然是linux, 也可以有同样的机制来捕捉这些 信号,自行处理。 遗憾的是并没有找到相应的java层api, 网络上多数博文都是讲述 android 底层处理crash打印 栈信息的原理。 这里,手动通过jni的方式,将我们的app安装上信号处理函数。
实现:简单点讲,就是在jni层调用 signal(SIGINT, handlerInterrupt); 实现app收到 SIGINT 信号的时候自己处理,而不会被kill.
注意一个问题点:
本想在 signal()注册的信号处理函数中,直接从native层调用java传递进来的回调对象,这样每次收到信号即可触发java层的回调,结果发现 jni调用java处始终报错, 原因在于: ART虚拟机防止在本机信号处理期间从JNI进行任何Java调用(ART prevents any Java calls from JNI during native signal handling) 据说是为了避免死锁等等问题, 在信号处理调用过程中不允许从nativie调用到java。 所以为了解决这一个问题, 这里在jni层创建了另外一个线程,专门用来回调java, 而信号处理函数只需要设置一个条件变量告诉该线程什么时候去回调java即可。
最终效果:(kill 命令发送信号测试,需要root权限)
贴代码:
先上关键性的代码,jni
//jni_signal_Unitil.c
// Created by wang.xiancan on 2020/9/10.
//
#include "com_example_test_signal_Unitil_jni.h"
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include<string.h>
//#include <execinfo.h>
#include<signal.h>
#include <pthread.h>
#include "jnilog.h"
static int bInited = 0;
//虚拟机, 一个进程一个对应
static JavaVM *gJvm = NULL;
//本地全局引用
static jobject gJobj = NULL;
static jobject gCallback = NULL;
struct signal_info {
int signal;
int flag;
};
//该互斥锁用来确保变量 signal_info 的值稳定
pthread_mutex_t gThread_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
struct signal_info gSignalInfo = {0, 0};
static void setSignal(int signal) {
pthread_mutex_lock(&gThread_mutex);
do {
gSignalInfo.flag = 1;
gSignalInfo.signal = signal;
//产生一个触发条件
pthread_mutex_lock(&lock);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
} while (0);
pthread_mutex_unlock(&gThread_mutex);
}
static int checkSignal() {
int ret = -1;
pthread_mutex_lock(&gThread_mutex);
do {
if (gSignalInfo.flag) {
ret = gSignalInfo.signal;
gSignalInfo.flag = 0;
}
} while (0);
pthread_mutex_unlock(&gThread_mutex);
return ret;
}
void notify2java(int signal) {
JNIEnv *env = NULL;
jclass jcls = NULL;
jmethodID jcallback = NULL;
// 1. 获取线程中 java 运行环境 jniEnv 每一个线程有一个自己的jniEnv
if ((*gJvm)->AttachCurrentThread(gJvm, &env, NULL) != JNI_OK) {
TRACK("%s: AttachCurrentThread() failed", __func__);
return;
}
// 2. 获取 Java class 类
jcls = (*env)->GetObjectClass(env, gCallback); // 获取 callback class 也可以通过包名获取,
// 比如:jclass clazz = (*mEnv)->FindClass(mEnv,"com/example/test_signal/Unitil_jni$SignalCallback");
if (jcls != NULL) {
TRACK("find jcls success");
// 3. 获取 java 的方法 ID methodID
jcallback = (*env)->GetMethodID(env, jcls, "signal", "(I)V");
// 4. 调用 java 方法
if (jcallback != NULL) {
TRACK("get method ok\n");
(*env)->CallVoidMethod(env, gCallback, jcallback, signal); // 调用Java方法
}
} else {
TRACK("find class erro\n");
}
//5. 释放 java 运行环境
if ((*gJvm)->DetachCurrentThread(gJvm) != JNI_OK) {
TRACK("%s: DetachCurrentThread() failed", __func__);
}
}
void handlerInterrupt(int signum) {
TRACK("getsignal ---------thread_self:%lu", pthread_self());
if (SIGINT == signum) {
TRACK("wang get sig SIGINT interrupt:%d [%d%s]\n", signum, __LINE__, __FUNCTION__);
setSignal(signum);
return;
}
}
void thread_run(void *arg) {
int sig = 0;
while (1) {
//条件等待
pthread_mutex_lock(&lock);
pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);
if ((sig = checkSignal()) != -1) {
TRACK("getsignal: %d", sig);
notify2java(sig);
}
}
}
int init() {
TRACK("init ---------thread_self:%lu", pthread_self());
if (bInited) {
return 0;
} else {
bInited = 1;
}
//SIGPIPE SIGUSR1 SIGSEGV
signal(SIGINT, handlerInterrupt);
pthread_t thread_t;
if (pthread_create(&thread_t, NULL, thread_run, NULL) == 0) {
TRACK("thread create ok:%lu", thread_t);
pthread_detach(thread_t);
}
return 0;
};
JNIEXPORT void JNICALL Java_com_example_test_1signal_Unitil_1jni_init
(JNIEnv *env, jclass obj, jobject callback) {
TRACK("pthread: %d", pthread_self());
// 获取虚拟机。并保存到 gJvm 中
(*env)->GetJavaVM(env, &gJvm);
/*要想在新线程中使用对象obj,就必须以全局引用方式保存,否则obj只是局部引用,本方法返回后就会销毁*/
// 创建对象的本地引用
gJobj = (*env)->NewGlobalRef(env, obj);
gCallback = (*env)->NewGlobalRef(env, callback);
init();
}
//逆初始化
void deinit() {
JNIEnv *env = NULL;
(*gJvm)->AttachCurrentThread(gJvm, &env, NULL);
(*env)->DeleteGlobalRef(env, gJobj);
(*env)->DeleteGlobalRef(env, gCallback);
gJobj = NULL;
gCallback = NULL;
(*gJvm)->DetachCurrentThread(env);
}
//jnilog.h //这个用来在native层打印log
#ifndef __JNI_LOG_HEAD_H__
#define __JNI_LOG_HEAD_H__
#include<android/log.h>
#define MY_LOG_TAG "from-jni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,MY_LOG_TAG,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,MY_LOG_TAG,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,MY_LOG_TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,MY_LOG_TAG,__VA_ARGS__) // 定义LOGF类型
//#define TRACK(...) __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,"[%d %s]",__LINE__,__FUNCTION__);__android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__)
#define TRACK(...) __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__)
#endif
//com_example_test_signal_Unitil_jni.h javah 生成的头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_test_signal_Unitil_jni */
#ifndef _Included_com_example_test_signal_Unitil_jni
#define _Included_com_example_test_signal_Unitil_jni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_test_signal_Unitil_jni
* Method: init
* Signature: (Lcom/example/test_signal/Unitil_jni/SignalCallback;)V
*/
JNIEXPORT void JNICALL Java_com_example_test_1signal_Unitil_1jni_init
(JNIEnv *, jclass, jobject);
#ifdef __cplusplus
}
#endif
#endif
java 层,回调
package com.example.test_signal;
import android.util.Log;
public class Unitil_jni {
private final String TAG = "Unitil_jni";
static{
System.loadLibrary("jni_signal");
}
public native void init(SignalCallback callback);
public interface SignalCallback{
void signal(int sig);
}
public void sayhello(){
Log.d(TAG, "sayhello: ");
}
}
activity:
package com.example.test_signal;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private final int MSG_SIG = 1;
Handler mMainHander;
private TextView mtext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mtext = findViewById(R.id.textView);
mMainHander = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MSG_SIG:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
mtext.setText("who want to kill me? 捕获到信号:"+msg.arg1+":"+dateFormat.format(date));
break;
default:
super.handleMessage(msg);
}
}
};
Unitil_jni unitil = new Unitil_jni();
unitil.init(new Unitil_jni.SignalCallback() {
@Override
public void signal(int sig) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
Log.d(TAG, "signal: "+":"+sig+dateFormat.format(date));
//这个子线程没有权限去修改ui
//mtext.setText("who want to kill me???? 捕获到信号:"+sig+":"+dateFormat.format(date));
if(mMainHander!=null) {
Message msg = mMainHander.obtainMessage(MSG_SIG, sig, -1, null);
mMainHander.sendMessage(msg);
}
}
});
}
}