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

三大崩溃

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

  1. RuntimeException
    1. java端的运行时异常.比如一些空指针之类的,发生时应用会崩溃.
  2. ANR
    1. 安卓为了用户体验设的保护机制,在应用在主线程做耗时操作的时候,长时间无响应会产生,一个问用户是否要继续等待的选择框,若用户选择关闭,或者长时间不选择,都会造成应用关闭.
  3. Native信号异常
    1. 当我们的代码导入第三方的so包的时候,由于c/c++代码的一些问题,产生native信号,就会造成应用直接崩掉,然后报一大堆的汇编的堆栈信息.

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

捕获RuntimException

/**
 * <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是无法捕获的,但是你可以在事后收到该消息,做你需要做的操作

/**
 * 主要通过收听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.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 的内存,因为该内存是操作系统保留的
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 一.概述   本文主要介绍Android平台下bug类型和产生原因、崩溃捕获和收集解决方案、以及bugly的使用方...
    Haraway阅读 9,184评论 3 14
  • 很多同学曾经问我,该学习什么技术,怎么样去学习技术?其实每当我听到这个问题,我是无比纠结。这是一个无法回答的大问题...
    小立狐狸阅读 470评论 1 0
  • 文/锦心明道 父亲 与共和国同龄 虽说是一介农夫 但却有着对国家无比的忠诚 用他的话说 当年珍宝岛保卫战 如果...
    锦心明道阅读 349评论 0 2