安卓捕獲RuntimeException,ANR,Native信號異常

三大崩潰

眾所周知,安卓端有三大崩潰,都會造成應(yīng)用崩掉,分別是

  1. RuntimeException
    1. java端的運行時異常.比如一些空指針之類的,發(fā)生時應(yīng)用會崩潰.
  2. ANR
    1. 安卓為了用戶體驗設(shè)的保護(hù)機(jī)制,在應(yīng)用在主線程做耗時操作的時候,長時間無響應(yīng)會產(chǎn)生,一個問用戶是否要繼續(xù)等待的選擇框,若用戶選擇關(guān)閉,或者長時間不選擇,都會造成應(yīng)用關(guān)閉.
  3. Native信號異常
    1. 當(dāng)我們的代碼導(dǎo)入第三方的so包的時候,由于c/c++代碼的一些問題,產(chǎn)生native信號,就會造成應(yīng)用直接崩掉,然后報一大堆的匯編的堆棧信息.

下面就分別講講如何捕獲這三種異常

捕獲RuntimException

/**
 * <p>
 * <h1>捕獲java運行時異常(Runtime-Exception)</h1>
 * <p>
    Uncaught異常發(fā)生時會終止線程蝗岖,此時门扇,系統(tǒng)便會通知UncaughtExceptionHandler暂幼,告訴它被終止的線程以及對應(yīng)的異常寸士,
    然后便會調(diào)用uncaughtException函數(shù)庭敦。如果該handler沒有被顯式設(shè)置,則會調(diào)用對應(yīng)線程組的默認(rèn)handler辈双。
    如果我們要捕獲該異常烛缔,必須實現(xiàn)我們自己的handler馏段,并通過以下函數(shù)進(jìn)行設(shè)置:
    <p>
        MyCrashHandler myCrashHandler = new MyCrashHandler();
        Thread.setDefaultUncaughtExceptionHandler(myCrashHandler);
    <p>
    實現(xiàn)自定義的handler,只需要繼承JavaCrashHandler践瓷,并實現(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這個文件拿愧,掐頭去尾
        System.loadLibrary("wlibcatchs");
    }
    
    /**
     * 注冊捕獲本地Native信號異常
     */
    public void registerNativeCacheHandler(){
        nativeRegisterHandler();
    }
    
    /**
     * 發(fā)生本地native信號異常的時候,會回調(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));
    }
    
    /**
     * 警告!!!
     * 用于測試,故意制造一個本地信號異常,非測試不要用該函數(shù)
     * 
     */
    @Deprecated
    public void makeError() {
        nativeMakeError();
    }
    
    
    private native int nativeRegisterHandler();
    private native boolean nativeMakeError();
}

NativeCacheHandler.cpp

/**
    預(yù)處理指令是以#號開頭的代碼行。
    #號必須是該行除了任何空白字符外的第一個字符碌尔。
    #后是指令關(guān)鍵字赶掖,在關(guān)鍵字和#號之間允許存在任意個數(shù)的空白字符。
    整行語句構(gòu)成了一條預(yù)處理指令七扰,該指令將在編譯器進(jìn)行編譯之前對源代碼做某些轉(zhuǎn)換奢赂。
 */

// #include包含一個源代碼文件
#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è)置不同的條件膳灶,在編譯時編譯不同的代碼,預(yù)編譯指令中的表達(dá)式與C語言本身的表達(dá)式基本一至如邏輯運算立由、算術(shù)運算轧钓、位運算等均可以在預(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ù)。這是給鏈接器用的道盏,告訴鏈接器在鏈接的時候用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; // 定義一個靜態(tài)的JNIEnv類型的指針變量 // JNIEnv類型實際上代表了Java環(huán)境粹排,通過這個JNIEnv* 指針,就可以對Java端的代碼進(jìn)行操作涩澡。
                                // 例如顽耳,創(chuàng)建Java類中的對象,調(diào)用Java對象的方法妙同,獲取Java對象中的屬性等等射富。JNIEnv的指針會被JNI傳入到本地方法的實現(xiàn)函數(shù)中來對Java端的代碼進(jìn)行操作。
        static jobject g_sigObj; // 如果native方法不是static的話粥帚,這個obj就代表這個native方法的類實例
                                // 如果native方法是static的話胰耗,這個obj就代表這個native方法的類的class對象實例(static方法不需要類實例的,所以就代表這個類的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ù)時茎辐,首先會去執(zhí)行C組件里的JNI_OnLoad()函數(shù)宪郊。
    // 當(dāng)沒有JNI_OnLoad()函數(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)境對每個線程來說是不同的拖陆,
             *  由于Dalvik虛擬機(jī)通常是Multi-threading的弛槐。每一個線程調(diào)用JNI_OnLoad()時,
             *  所用的JNI Env是不同的依啰,因此我們必須在每次進(jìn)入函數(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_); // 全局引用在一個本機(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個字節(jié)設(shè)置成字符c
                    &handler,   // 參1:指向要填充的內(nèi)存塊速警。
                    0,  // 參2:要被設(shè)置的值叹誉。該值以 int 形式傳遞,但是函數(shù)在填充內(nèi)存塊時是使用該值的無符號字符形式闷旧。
                    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忽略接送到的信號酸舍,或者一個信號處理函數(shù)指針。這個函數(shù)只有信號編碼一個參數(shù)里初。
    //            void     (*sa_sigaction)(int, siginfo_t *, void *); // 當(dāng)sa_flags中存在SA_SIGINFO標(biāo)志時啃勉,sa_sigaction將作為signum信號的處理函數(shù)。
    //            sigset_t   sa_mask;   // 指定信號處理函數(shù)執(zhí)行的過程中應(yīng)被阻塞的信號双妨。
    //            int        sa_flags; // 指定一系列用于修改信號處理過程行為的標(biāo)志淮阐,由0個或多個標(biāo)志通過or運算組合而成叮阅,比如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ù)運行時不會收到 SIGCHLD 信號竞阐。
                        //    SA_NOCLDWAIT:使父進(jìn)程在它的子進(jìn)程退出時不會收到SIGCHLD信號,這時子進(jìn)程如果退出也不會成為僵 尸進(jìn)程暑劝。
                        //    SA_NODEFER:使對信號的屏蔽無效骆莹,即在信號處理函數(shù)執(zhí)行期間仍能發(fā)出這個信號。
                        //    SA_RESETHAND:信號處理之后重新設(shè)置為默認(rèn)的處理方式担猛。
                        //    SA_SIGINFO:使用sa_sigaction成員而不是sa_handler作為信號處理函數(shù)幕垦。
            handler.sa_flags = SA_RESETHAND;

            // 注冊信號處理函數(shù)
                // 參1  代表信號編碼,可以是除SIGKILL及SIGSTOP外的任何一個特定有效的信號傅联,如果為這兩個信號定義自己的處理函數(shù)先改,將導(dǎo)致信號安裝錯誤。
                // 參2  指向結(jié)構(gòu)體sigaction的一個實例的指針蒸走,該實例指定了對特定信號的處理仇奶,如果設(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   總線錯誤
            CATCHSIG(SIGFPE);   // 信號8   浮點異常
            CATCHSIG(SIGSEGV);  // 信號11   無效的存儲器引用(段故障)
            CATCHSIG(SIGSTKFLT);// 信號16 協(xié)處理器上的棧故障
            CATCHSIG(SIGPIPE);  // 信號13   向一個沒有讀用戶的管道做寫操作
        #endif

    }


    JNIEXPORT jboolean JNICALL
    Java_com_wtest_wlib_android_catchs_NativeCacheHandler_nativeMakeError()
    {

        // 故意制造一個信號11異常
        char *ptr = NULL;   // 賦值為NULL,空指針,值為0
            *ptr = '!'; // ERROR HERE! // 因為在大多數(shù)操作系統(tǒng)中,程序不允許訪問地址為 0 的內(nèi)存狈茉,因為該內(nèi)存是操作系統(tǒng)保留的
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掸掸,隨后出現(xiàn)的幾起案子氯庆,更是在濱河造成了極大的恐慌,老刑警劉巖猾漫,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件点晴,死亡現(xiàn)場離奇詭異,居然都是意外死亡悯周,警方通過查閱死者的電腦和手機(jī)粒督,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禽翼,“玉大人屠橄,你說我怎么就攤上這事族跛。” “怎么了锐墙?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵礁哄,是天一觀的道長。 經(jīng)常有香客問我溪北,道長桐绒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任之拨,我火速辦了婚禮茉继,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚀乔。我一直安慰自己烁竭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布吉挣。 她就那樣靜靜地躺著派撕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪睬魂。 梳的紋絲不亂的頭發(fā)上终吼,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機(jī)與錄音汉买,去河邊找鬼衔峰。 笑死,一個胖子當(dāng)著我的面吹牛蛙粘,可吹牛的內(nèi)容都是我干的垫卤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼出牧,長吁一口氣:“原來是場噩夢啊……” “哼穴肘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舔痕,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤评抚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伯复,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慨代,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年啸如,在試婚紗的時候發(fā)現(xiàn)自己被綠了侍匙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡叮雳,死狀恐怖想暗,靈堂內(nèi)的尸體忽然破棺而出妇汗,到底是詐尸還是另有隱情,我是刑警寧澤说莫,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布杨箭,位于F島的核電站,受9級特大地震影響储狭,放射性物質(zhì)發(fā)生泄漏互婿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一晶密、第九天 我趴在偏房一處隱蔽的房頂上張望擒悬。 院中可真熱鬧模她,春花似錦稻艰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畜侦,卻和暖如春元扔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旋膳。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工澎语, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人验懊。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓擅羞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親义图。 傳聞我的和親對象是個殘疾皇子减俏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,264評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)碱工,斷路器娃承,智...
    卡卡羅2017閱讀 134,693評論 18 139
  • 一.概述 ??本文主要介紹Android平臺下bug類型和產(chǎn)生原因、崩潰捕獲和收集解決方案怕篷、以及bugly的使用方...
    Haraway閱讀 9,398評論 3 14
  • 很多同學(xué)曾經(jīng)問我廊谓,該學(xué)習(xí)什么技術(shù)梳猪,怎么樣去學(xué)習(xí)技術(shù)?其實每當(dāng)我聽到這個問題蹂析,我是無比糾結(jié)舔示。這是一個無法回答的大問題...
    小立狐貍閱讀 475評論 1 0
  • 文/錦心明道 父親 與共和國同齡 雖說是一介農(nóng)夫 但卻有著對國家無比的忠誠 用他的話說 當(dāng)年珍寶島保衛(wèi)戰(zhàn) 如果...
    錦心明道閱讀 354評論 0 2