java 捕获sigkill,安卓捕获RuntimeException,ANR,Native信号异常

三大崩溃

众所周知,安卓端有三大崩溃,都会造成应用崩掉,分别是

RuntimeException

java端的运行时异常.比如一些空指针之类的,发生时应用会崩溃.

ANR

安卓为了用户体验设的保护机制,在应用在主线程做耗时操作的时候,长时间无响应会产生,一个问用户是否要继续等待的选择框,若用户选择关闭,或者长时间不选择,都会造成应用关闭.

Native信号异常

当我们的代码导入第三方的so包的时候,由于c/c++代码的一些问题,产生native信号,就会造成应用直接崩掉,然后报一大堆的汇编的堆栈信息.

下面就分别讲讲如何捕获这三种异常

捕获RuntimException

/**

*

*

捕获java运行时异常(Runtime-Exception)

*

Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常,

然后便会调用uncaughtException函数。如果该handler没有被显式设置,则会调用对应线程组的默认handler。

如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置:

MyCrashHandler myCrashHandler = new MyCrashHandler();

Thread.setDefaultUncaughtExceptionHandler(myCrashHandler);

实现自定义的handler,只需要继承JavaCrashHandler,并实现myUncaughtExceptionToDo方法即可。

*/

public class JavaCrashHandler implements UncaughtExceptionHandler{

@Override

public void uncaughtException(Thread thread, final Throwable throwable) {

// 捕获异常

String stackTraceInfo = getStackTraceInfo(throwable);

myUncaughtExceptionToDo();

}

/**

* 自定义的对异常的处理

*/

public void myUncaughtExceptionToDo() {

// // 重启应用

}

/**

*

获取Exception崩溃堆栈

*

捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。

异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,

我们可以找到异常的源头,并跟踪到异常一路触发的过程。

*/

public static String getStackTraceInfo(final Throwable throwable) {

String trace = "";

try {

Writer writer = new StringWriter();

PrintWriter pw = new PrintWriter(writer);

throwable.printStackTrace(pw);

trace = writer.toString();

pw.close();

} catch (Exception e) {

return "";

}

return trace;

}

}

捕获ANR

ANR是无法捕获的,但是你可以在事后收到该消息,做你需要做的操作

/**

* 主要通过收听ANR的广播,来检测是否发生ANR的现象,但是无法阻止ANR

*

*

在onReceive()里面做判断

if (intent.getAction().equals(ACTION_ANR)) {

// do you want to do

}

*

* @author aaa

*

*/

public class ANRCacheHelper {

private static MyReceiver myReceiver;

public static void registerANRReceiver(Context context){

myReceiver = new MyReceiver();

context.registerReceiver(myReceiver, new IntentFilter(ACTION_ANR));

}

public static void unregisterANRReceiver(Context context){

if (myReceiver == null) {

return;

}

context.unregisterReceiver(myReceiver);

}

private static final String ACTION_ANR = "android.intent.action.ANR";

private static class MyReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals(ACTION_ANR)) {

// to do

}

}

}

}

捕获Native信号异常

NativeCacheHandler.java

package com.wtest.wlib.android.catchs;

import android.util.Log;

/**

* 捕获本地的native信号异常

* @author aaa

*

*/

public class NativeCacheHandler {

static {

// 写Android.mk文件中定义好的类库名

// 也就是把libs/armeabi/libwlibcatchs.so这个文件,掐头去尾

System.loadLibrary("wlibcatchs");

}

/**

* 注册捕获本地Native信号异常

*/

public void registerNativeCacheHandler(){

nativeRegisterHandler();

}

/**

* 发生本地native信号异常的时候,会回调到这里来

*/

public void onNativeCrashed() {

Log.d("wtest", "捕获到本地异常,执行到这里");

// new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace();

// startActivity(new Intent(this, MainActivity.class));

}

/**

* 警告!!!

* 用于测试,故意制造一个本地信号异常,非测试不要用该函数

*

*/

@Deprecated

public void makeError() {

nativeMakeError();

}

private native int nativeRegisterHandler();

private native boolean nativeMakeError();

}

NativeCacheHandler.cpp

/**

预处理指令是以#号开头的代码行。

#号必须是该行除了任何空白字符外的第一个字符。

#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。

整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

*/

// #include包含一个源代码文件

#include // 使用jni进行java和c语言互相调用,必须导入的头文件

#include // 头文件里包含了C语言的中最常用的系统函数

#include // 在signal.h头文件中,提供了一些函数用以处理执行过程中所产生的信号。

//#include "NativeActivity.hpp"

#include // 谷歌提供的用于安卓JNI输出log日志的头文件

// 条件编译:即可以设置不同的条件,在编译时编译不同的代码,预编译指令中的表达式与C语言本身的表达式基本一至如逻辑运算、算术运算、位运算等均可以在预编译指令中使用。

#ifdef MIKMOD // #ifdef如果宏已经定义,则编译下面代码

// #include "mikmod_build.h"

#endif // 预处理指令#endif用来限定#ifdef命令的范围

#define DO_TRY // #define定义宏

#define DO_CATCH(loc)

#define CATCH_SIGNALS

extern "C" // 在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

{

#ifdef CATCH_SIGNALS

static struct sigaction old_sa[NSIG];

/**

JNIEnv类中有很多函数可以用:

NewObject: 创建Java类中的对象

NewString: 创建Java类中的String对象

NewArray: 创建类型为Type的数组对象

GetField: 获取类型为Type的字段

SetField: 设置类型为Type的字段的值

GetStaticField: 获取类型为Type的static的字段

SetStaticField: 设置类型为Type的static的字段的值

CallMethod: 调用返回类型为Type的方法

CallStaticMethod: 调用返回值类型为Type的static方法

等许多的函数,具体的可以查看jni.h文件中的函数名称。

*/

static JNIEnv *g_sigEnv; // 定义一个静态的JNIEnv类型的指针变量 // JNIEnv类型实际上代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。

// 例如,创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等等。JNIEnv的指针会被JNI传入到本地方法的实现函数中来对Java端的代码进行操作。

static jobject g_sigObj; // 如果native方法不是static的话,这个obj就代表这个native方法的类实例

// 如果native方法是static的话,这个obj就代表这个native方法的类的class对象实例(static方法不需要类实例的,所以就代表这个类的class对象)

static jmethodID g_sigNativeCrashed;

// 信号处理函数,捕获到底层信号异常会执行到这里

void android_sigaction(int signal, siginfo_t *info, void *reserved)

{

// 回调之前定义的要回调的java里面的函数

g_sigEnv->CallVoidMethod(g_sigObj, g_sigNativeCrashed);

old_sa[signal].sa_handler(signal);

}

#endif

static jshortArray g_audioSamples;

// 当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。

// 当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)

// 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)

{

JNIEnv *env = NULL;

/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);

* GetEnv()函数返回的 Jni 环境对每个线程来说是不同的,

* 由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,

* 所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取

*

*/

//得到JNI Env

if (jvm->GetEnv((void **)&env, JNI_VERSION_1_2))

return JNI_ERR;

jclass cls = env->FindClass("com/wtest/wlib/android/catchs/NativeCacheHandler");

#ifdef CATCH_SIGNALS

g_sigEnv = env;

g_sigNativeCrashed = env->GetMethodID(cls, "onNativeCrashed", "()V");

#endif

return JNI_VERSION_1_2;

}

JNIEXPORT jint JNICALL

Java_com_wtest_wlib_android_catchs_NativeCacheHandler_nativeRegisterHandler(JNIEnv *env, jobject this_)

{

#ifdef CATCH_SIGNALS

// Try to catch crashes...

g_sigObj = env->NewGlobalRef(this_); // 全局引用在一个本机方法的多次不同调用之间使用。他们只能通过使用NewGlobalRef函数来创建。

struct sigaction handler; // struct定义结构体(类似于java中的javabean)

memset( // c库下的函数,void *memset(void *buffer, int c, int count); 把buffer所指内存区域的前count个字节设置成字符c

&handler, // 参1:指向要填充的内存块。

0, // 参2:要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。

sizeof( // 用于获取任何东西的内存大小

struct sigaction)); // 参3:要被设置为该值的字节数。

// // 结构体sigaction包含了对特定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

// struct sigaction {

// void (*sa_handler)(int); // 指定对signum信号的处理函数,可以是SIG_DFL默认行为,SIG_IGN忽略接送到的信号,或者一个信号处理函数指针。这个函数只有信号编码一个参数。

// void (*sa_sigaction)(int, siginfo_t *, void *); // 当sa_flags中存在SA_SIGINFO标志时,sa_sigaction将作为signum信号的处理函数。

// sigset_t sa_mask; // 指定信号处理函数执行的过程中应被阻塞的信号。

// int sa_flags; // 指定一系列用于修改信号处理过程行为的标志,由0个或多个标志通过or运算组合而成,比如SA_RESETHAND,SA_ONSTACK | SA_SIGINFO。

// void (*sa_restorer)(void); // 已经废弃,不再使用。

// }

// 设置信号处理函数

handler.sa_sigaction = android_sigaction;

// 信号处理之后重新设置为默认的处理方式。

// SA_RESTART:使被信号打断的syscall重新发起。

// SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

// SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵 尸进程。

// SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

// SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

// SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。

handler.sa_flags = SA_RESETHAND;

// 注册信号处理函数

// 参1 代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。

// 参2 指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。

// 参3 和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。

#define CATCHSIG(X) sigaction(X, &handler, &old_sa[X])

CATCHSIG(SIGILL); // 信号4 非法指令

CATCHSIG(SIGABRT); // 信号6 来自abort函数的终止信号

CATCHSIG(SIGBUS); // 信号7 总线错误

CATCHSIG(SIGFPE); // 信号8 浮点异常

CATCHSIG(SIGSEGV); // 信号11 无效的存储器引用(段故障)

CATCHSIG(SIGSTKFLT);// 信号16 协处理器上的栈故障

CATCHSIG(SIGPIPE); // 信号13 向一个没有读用户的管道做写操作

#endif

}

JNIEXPORT jboolean JNICALL

Java_com_wtest_wlib_android_catchs_NativeCacheHandler_nativeMakeError()

{

// 故意制造一个信号11异常

char *ptr = NULL; // 赋值为NULL,空指针,值为0

*ptr = '!'; // ERROR HERE! // 因为在大多数操作系统中,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值