三大崩潰
眾所周知,安卓端有三大崩潰,都會造成應(yīng)用崩掉,分別是
- RuntimeException
- java端的運(yùn)行時(shí)異常.比如一些空指針之類的,發(fā)生時(shí)應(yīng)用會崩潰.
- ANR
- 安卓為了用戶體驗(yàn)設(shè)的保護(hù)機(jī)制,在應(yīng)用在主線程做耗時(shí)操作的時(shí)候,長時(shí)間無響應(yīng)會產(chǎn)生,一個(gè)問用戶是否要繼續(xù)等待的選擇框,若用戶選擇關(guān)閉,或者長時(shí)間不選擇,都會造成應(yīng)用關(guān)閉.
- Native信號異常
- 當(dāng)我們的代碼導(dǎo)入第三方的so包的時(shí)候,由于c/c++代碼的一些問題,產(chǎn)生native信號,就會造成應(yīng)用直接崩掉,然后報(bào)一大堆的匯編的堆棧信息.
下面就分別講講如何捕獲這三種異常
捕獲RuntimException
/**
* <p>
* <h1>捕獲java運(yùn)行時(shí)異常(Runtime-Exception)</h1>
* <p>
Uncaught異常發(fā)生時(shí)會終止線程,此時(shí),系統(tǒng)便會通知UncaughtExceptionHandler,告訴它被終止的線程以及對應(yīng)的異常,
然后便會調(diào)用uncaughtException函數(shù)。如果該handler沒有被顯式設(shè)置,則會調(diào)用對應(yīng)線程組的默認(rèn)handler。
如果我們要捕獲該異常,必須實(shí)現(xiàn)我們自己的handler,并通過以下函數(shù)進(jìn)行設(shè)置:
<p>
MyCrashHandler myCrashHandler = new MyCrashHandler();
Thread.setDefaultUncaughtExceptionHandler(myCrashHandler);
<p>
實(shí)現(xiàn)自定義的handler,只需要繼承JavaCrashHandler,并實(shí)現(xiàn)myUncaughtExceptionToDo方法即可。
*/
public class JavaCrashHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread thread, final Throwable throwable) {
// 捕獲異常
String stackTraceInfo = getStackTraceInfo(throwable);
myUncaughtExceptionToDo();
}
/**
* 自定義的對異常的處理
*/
public void myUncaughtExceptionToDo() {
// // 重啟應(yīng)用
}
/**
* <h1>獲取Exception崩潰堆棧</h1>
* <p>
捕獲Exception之后,我們還需要知道崩潰堆棧的信息,這樣有助于我們分析崩潰的原因,查找代碼的Bug。
異常對象的printStackTrace方法用于打印異常的堆棧信息,根據(jù)printStackTrace方法的輸出結(jié)果,
我們可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過程。
*/
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的廣播,來檢測是否發(fā)生ANR的現(xiàn)象,但是無法阻止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這個(gè)文件,掐頭去尾
System.loadLibrary("wlibcatchs");
}
/**
* 注冊捕獲本地Native信號異常
*/
public void registerNativeCacheHandler(){
nativeRegisterHandler();
}
/**
* 發(fā)生本地native信號異常的時(shí)候,會回調(diào)到這里來
*/
public void onNativeCrashed() {
Log.d("wtest", "捕獲到本地異常,執(zhí)行到這里");
// new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace();
// startActivity(new Intent(this, MainActivity.class));
}
/**
* 警告!!!
* 用于測試,故意制造一個(gè)本地信號異常,非測試不要用該函數(shù)
*
*/
@Deprecated
public void makeError() {
nativeMakeError();
}
private native int nativeRegisterHandler();
private native boolean nativeMakeError();
}
NativeCacheHandler.cpp
/**
預(yù)處理指令是以#號開頭的代碼行。
#號必須是該行除了任何空白字符外的第一個(gè)字符。
#后是指令關(guān)鍵字,在關(guān)鍵字和#號之間允許存在任意個(gè)數(shù)的空白字符。
整行語句構(gòu)成了一條預(yù)處理指令,該指令將在編譯器進(jìn)行編譯之前對源代碼做某些轉(zhuǎn)換。
*/
// #include包含一個(gè)源代碼文件
#include <jni.h> // 使用jni進(jìn)行java和c語言互相調(diào)用,必須導(dǎo)入的頭文件
#include <stdlib.h> // <stdlib.h> 頭文件里包含了C語言的中最常用的系統(tǒng)函數(shù)
#include <signal.h> // 在signal.h頭文件中,提供了一些函數(shù)用以處理執(zhí)行過程中所產(chǎn)生的信號。
//#include "NativeActivity.hpp"
#include <android/log.h> // 谷歌提供的用于安卓JNI輸出log日志的頭文件
// 條件編譯:即可以設(shè)置不同的條件,在編譯時(shí)編譯不同的代碼,預(yù)編譯指令中的表達(dá)式與C語言本身的表達(dá)式基本一至如邏輯運(yùn)算、算術(shù)運(yùn)算、位運(yùn)算等均可以在預(yù)編譯指令中使用。
#ifdef MIKMOD // #ifdef如果宏已經(jīng)定義,則編譯下面代碼
// #include "mikmod_build.h"
#endif // 預(yù)處理指令#endif用來限定#ifdef命令的范圍
#define DO_TRY // #define定義宏
#define DO_CATCH(loc)
#define CATCH_SIGNALS
extern "C" // 在C++中調(diào)用C庫函數(shù),就需要在C++程序中用extern “C”聲明要引用的函數(shù)。這是給鏈接器用的,告訴鏈接器在鏈接的時(shí)候用C函數(shù)規(guī)范來鏈接。主要原因是C++和C程序編譯完成后在目標(biāo)代碼中命名規(guī)則不同。
{
#ifdef CATCH_SIGNALS
static struct sigaction old_sa[NSIG];
/**
JNIEnv類中有很多函數(shù)可以用:
NewObject: 創(chuàng)建Java類中的對象
NewString: 創(chuàng)建Java類中的String對象
New<Type>Array: 創(chuàng)建類型為Type的數(shù)組對象
Get<Type>Field: 獲取類型為Type的字段
Set<Type>Field: 設(shè)置類型為Type的字段的值
GetStatic<Type>Field: 獲取類型為Type的static的字段
SetStatic<Type>Field: 設(shè)置類型為Type的static的字段的值
Call<Type>Method: 調(diào)用返回類型為Type的方法
CallStatic<Type>Method: 調(diào)用返回值類型為Type的static方法
等許多的函數(shù),具體的可以查看jni.h文件中的函數(shù)名稱。
*/
static JNIEnv *g_sigEnv; // 定義一個(gè)靜態(tài)的JNIEnv類型的指針變量 // JNIEnv類型實(shí)際上代表了Java環(huán)境,通過這個(gè)JNIEnv* 指針,就可以對Java端的代碼進(jìn)行操作。
// 例如,創(chuàng)建Java類中的對象,調(diào)用Java對象的方法,獲取Java對象中的屬性等等。JNIEnv的指針會被JNI傳入到本地方法的實(shí)現(xiàn)函數(shù)中來對Java端的代碼進(jìn)行操作。
static jobject g_sigObj; // 如果native方法不是static的話,這個(gè)obj就代表這個(gè)native方法的類實(shí)例
// 如果native方法是static的話,這個(gè)obj就代表這個(gè)native方法的類的class對象實(shí)例(static方法不需要類實(shí)例的,所以就代表這個(gè)類的class對象)
static jmethodID g_sigNativeCrashed;
// 信號處理函數(shù),捕獲到底層信號異常會執(zhí)行到這里
void android_sigaction(int signal, siginfo_t *info, void *reserved)
{
// 回調(diào)之前定義的要回調(diào)的java里面的函數(shù)
g_sigEnv->CallVoidMethod(g_sigObj, g_sigNativeCrashed);
old_sa[signal].sa_handler(signal);
}
#endif
static jshortArray g_audioSamples;
// 當(dāng)Android的VM(Virtual Machine)執(zhí)行到System.loadLibrary()函數(shù)時(shí),首先會去執(zhí)行C組件里的JNI_OnLoad()函數(shù)。
// 當(dāng)沒有JNI_OnLoad()函數(shù)時(shí),Android調(diào)試信息會做出如下提示(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()函數(shù)返回的 Jni 環(huán)境對每個(gè)線程來說是不同的,
* 由于Dalvik虛擬機(jī)通常是Multi-threading的。每一個(gè)線程調(diào)用JNI_OnLoad()時(shí),
* 所用的JNI Env是不同的,因此我們必須在每次進(jìn)入函數(shù)時(shí)都要通過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_); // 全局引用在一個(gè)本機(jī)方法的多次不同調(diào)用之間使用。他們只能通過使用NewGlobalRef函數(shù)來創(chuàng)建。
struct sigaction handler; // struct定義結(jié)構(gòu)體(類似于java中的javabean)
memset( // c庫<string.h>下的函數(shù),void *memset(void *buffer, int c, int count); 把buffer所指內(nèi)存區(qū)域的前count個(gè)字節(jié)設(shè)置成字符c
&handler, // 參1:指向要填充的內(nèi)存塊。
0, // 參2:要被設(shè)置的值。該值以 int 形式傳遞,但是函數(shù)在填充內(nèi)存塊時(shí)是使用該值的無符號字符形式。
sizeof( // 用于獲取任何東西的內(nèi)存大小
struct sigaction)); // 參3:要被設(shè)置為該值的字節(jié)數(shù)。
// // 結(jié)構(gòu)體sigaction包含了對特定信號的處理、信號所傳遞的信息、信號處理函數(shù)執(zhí)行過程中應(yīng)屏蔽掉哪些函數(shù)等等。
// struct sigaction {
// void (*sa_handler)(int); // 指定對signum信號的處理函數(shù),可以是SIG_DFL默認(rèn)行為,SIG_IGN忽略接送到的信號,或者一個(gè)信號處理函數(shù)指針。這個(gè)函數(shù)只有信號編碼一個(gè)參數(shù)。
// void (*sa_sigaction)(int, siginfo_t *, void *); // 當(dāng)sa_flags中存在SA_SIGINFO標(biāo)志時(shí),sa_sigaction將作為signum信號的處理函數(shù)。
// sigset_t sa_mask; // 指定信號處理函數(shù)執(zhí)行的過程中應(yīng)被阻塞的信號。
// int sa_flags; // 指定一系列用于修改信號處理過程行為的標(biāo)志,由0個(gè)或多個(gè)標(biāo)志通過or運(yùn)算組合而成,比如SA_RESETHAND,SA_ONSTACK | SA_SIGINFO。
// void (*sa_restorer)(void); // 已經(jīng)廢棄,不再使用。
// }
// 設(shè)置信號處理函數(shù)
handler.sa_sigaction = android_sigaction;
// 信號處理之后重新設(shè)置為默認(rèn)的處理方式。
// SA_RESTART:使被信號打斷的syscall重新發(fā)起。
// SA_NOCLDSTOP:使父進(jìn)程在它的子進(jìn)程暫停或繼續(xù)運(yùn)行時(shí)不會收到 SIGCHLD 信號。
// SA_NOCLDWAIT:使父進(jìn)程在它的子進(jìn)程退出時(shí)不會收到SIGCHLD信號,這時(shí)子進(jìn)程如果退出也不會成為僵 尸進(jìn)程。
// SA_NODEFER:使對信號的屏蔽無效,即在信號處理函數(shù)執(zhí)行期間仍能發(fā)出這個(gè)信號。
// SA_RESETHAND:信號處理之后重新設(shè)置為默認(rèn)的處理方式。
// SA_SIGINFO:使用sa_sigaction成員而不是sa_handler作為信號處理函數(shù)。
handler.sa_flags = SA_RESETHAND;
// 注冊信號處理函數(shù)
// 參1 代表信號編碼,可以是除SIGKILL及SIGSTOP外的任何一個(gè)特定有效的信號,如果為這兩個(gè)信號定義自己的處理函數(shù),將導(dǎo)致信號安裝錯(cuò)誤。
// 參2 指向結(jié)構(gòu)體sigaction的一個(gè)實(shí)例的指針,該實(shí)例指定了對特定信號的處理,如果設(shè)置為空,進(jìn)程會執(zhí)行默認(rèn)處理。
// 參3 和參數(shù)act類似,只不過保存的是原來對相應(yīng)信號的處理,也可設(shè)置為NULL。
#define CATCHSIG(X) sigaction(X, &handler, &old_sa[X])
CATCHSIG(SIGILL); // 信號4 非法指令
CATCHSIG(SIGABRT); // 信號6 來自abort函數(shù)的終止信號
CATCHSIG(SIGBUS); // 信號7 總線錯(cuò)誤
CATCHSIG(SIGFPE); // 信號8 浮點(diǎn)異常
CATCHSIG(SIGSEGV); // 信號11 無效的存儲器引用(段故障)
CATCHSIG(SIGSTKFLT);// 信號16 協(xié)處理器上的棧故障
CATCHSIG(SIGPIPE); // 信號13 向一個(gè)沒有讀用戶的管道做寫操作
#endif
}
JNIEXPORT jboolean JNICALL
Java_com_wtest_wlib_android_catchs_NativeCacheHandler_nativeMakeError()
{
// 故意制造一個(gè)信號11異常
char *ptr = NULL; // 賦值為NULL,空指針,值為0
*ptr = '!'; // ERROR HERE! // 因?yàn)樵诖蠖鄶?shù)操作系統(tǒng)中,程序不允許訪問地址為 0 的內(nèi)存,因?yàn)樵搩?nèi)存是操作系統(tǒng)保留的
}
}