Android app 捕获系统信号,避免被信号kill

最近在做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);
                }
            }
        });
    }
}

 

Android应用中,可以通过全局捕获异常来避免应用闪退。可以通过以下步骤实现: 1. 创建一个自定义的Application类,并在其中重写`Thread.UncaughtExceptionHandler`接口的`uncaughtException`方法。 2. 在`uncaughtException`方法中处理全局异常,例如记录异常信息、上传日志或者进行其他处理操作。 3. 在Application的onCreate方法中,将自定义的UncaughtExceptionHandler设置为默认的异常处理器。 下面是一个示例代码: ```java public class MyApplication extends Application implements Thread.UncaughtExceptionHandler { @Override public void onCreate() { super.onCreate(); // 设置全局异常处理器 Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, Throwable ex) { // 处理全局异常,例如记录异常信息、上传日志等操作 Log.e("MyApplication", "Uncaught Exception: " + ex.getMessage()); // 重启应用或者执行其他操作 restartApp(); } private void restartApp() { // 重启应用,可以根据实际需求来实现 Intent intent = new Intent(getApplicationContext(), MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent); // 退出应用 System.exit(0); } } ``` 记得在AndroidManifest.xml文件中将自定义的Application类配置为应用的默认Application类: ```xml <application android:name=".MyApplication" ...> ... </application> ``` 通过以上步骤,当应用发生未捕获的异常时,会调用自定义的异常处理方法,你可以在其中进行相应的处理操作,例如记录异常信息、上传日志等。最后,你可以选择重启应用或者执行其他操作。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值