三大崩溃
众所周知,安卓端有三大崩溃,都会造成应用崩掉,分别是
- RuntimeException
- java端的运行时异常.比如一些空指针之类的,发生时应用会崩溃.
- ANR
- 安卓为了用户体验设的保护机制,在应用在主线程做耗时操作的时候,长时间无响应会产生,一个问用户是否要继续等待的选择框,若用户选择关闭,或者长时间不选择,都会造成应用关闭.
- Native信号异常
- 当我们的代码导入第三方的so包的时候,由于c/c++代码的一些问题,产生native信号,就会造成应用直接崩掉,然后报一大堆的汇编的堆栈信息.
下面就分别讲讲如何捕获这三种异常
捕获RuntimException
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49/** * <p> * <h1>捕获java运行时异常(Runtime-Exception)</h1> * <p> Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常, 然后便会调用uncaughtException函数。如果该handler没有被显式设置,则会调用对应线程组的默认handler。 如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置: <p> MyCrashHandler myCrashHandler = new MyCrashHandler(); Thread.setDefaultUncaughtExceptionHandler(myCrashHandler); <p> 实现自定义的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() { // // 重启应用 } /** * <h1>获取Exception崩溃堆栈</h1> * <p> 捕获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是无法捕获的,但是你可以在事后收到该消息,做你需要做的操作
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/** * 主要通过收听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
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package 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
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176/** 预处理指令是以#号开头的代码行。 #号必须是该行除了任何空白字符外的第一个字符。 #后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。 整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。 */ // #include包含一个源代码文件 #include <jni.h> // 使用jni进行java和c语言互相调用,必须导入的头文件 #include <stdlib.h> // <stdlib.h> 头文件里包含了C语言的中最常用的系统函数 #include <signal.h> // 在signal.h头文件中,提供了一些函数用以处理执行过程中所产生的信号。 //#include "NativeActivity.hpp" #include <android/log.h> // 谷歌提供的用于安卓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对象 New<Type>Array: 创建类型为Type的数组对象 Get<Type>Field: 获取类型为Type的字段 Set<Type>Field: 设置类型为Type的字段的值 GetStatic<Type>Field: 获取类型为Type的static的字段 SetStatic<Type>Field: 设置类型为Type的static的字段的值 Call<Type>Method: 调用返回类型为Type的方法 CallStatic<Type>Method: 调用返回值类型为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库<string.h>下的函数,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 的内存,因为该内存是操作系统保留的 } }
作者:wangwox
链接:https://www.jianshu.com/p/bb658019f97b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最后
以上就是清爽鸵鸟最近收集整理的关于安卓捕获RuntimeException,ANR,Native信号异常三大崩溃下面就分别讲讲如何捕获这三种异常的全部内容,更多相关安卓捕获RuntimeException内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复