Android异常监控

作者: Heezier | 来源:发表于2020-11-09 22:15 被阅读0次

    Android异常监控

    Crash就是由于代码异常而导致App非正常退出现象,也就是我们常说的『崩溃

    通常情况下会有以下两种类型Crash:

    • Java Crash
    • Native Crash

    Java Crash

    Java的Crash监控非常简单,Java中的Thread定义了一个接口: UncaughtExceptionHandler ;用于

    处理未捕获的异常导致线程的终止(注意:****catch****了的是捕获不到的),当我们的应用crash的时候,就

    会走 UncaughtExceptionHandler.uncaughtException ,在该方法中可以获取到异常的信息,我们通

    过 Thread.setDefaultUncaughtExceptionHandler 该方法来设置线程的默认异常处理器,我们可以

    将异常信息保存到本地或者是上传到服务器,方便我们快速的定位问题。

    Android系统崩溃处理的流程:

    图片来源:https://xiang-yu.blog.csdn.net/article/details/106647428

    20200609193238913
    具体的处理过程可参考:https://xiang-yu.blog.csdn.net/article/details/106647428

    ZygoteInit每次fork出一个进程之后,会先初始化日志、crash监控,再利用反射调用 ActivityThread 的main()方法启动app。那么我们可以自己的app中重新注册Crash监控回调接口,然后在自己的app中处理Crash。

    具体的实现类如下:

    public class CrashHandler implements Thread.UncaughtExceptionHandler{
    
        private static final String FILE_NAME_SUFFIX = ".trace";
        private static Thread.UncaughtExceptionHandler mDefaultCrashHandler;
        private static Context mContext;
        private static CrashHandler crashHandler;
        private CrashHandler() {
        }
        public static CrashHandler getCrashHander(){
            if (crashHandler == null){
                synchronized (CrashHandler.class){
                    if (crashHandler == null){
                        crashHandler = new CrashHandler();
                    }
                }
            }
            return crashHandler;
        }
    
        public void init(@NonNull Context context) {
            //默认为:RuntimeInit#KillApplicationHandler
            mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(this);
            mContext = context.getApplicationContext();
        }
    
        /**
         * 当程序中有未被捕获的异常,系统将会调用这个方法
         *
         * @param t 出现未捕获异常的线程
         * @param e 得到异常信息
         */
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                //自行处理:保存本地
                File file = dealException(t, e);
                //上传服务器
                //......
            } catch (Exception e1) {
                e1.printStackTrace();
            } finally {
                //交给系统默认程序处理
                if (mDefaultCrashHandler != null) {
                    mDefaultCrashHandler.uncaughtException(t, e);
                }
            }
        }
    
        /**
         * 导出异常信息到SD卡
         *
         * @param e
         */
        private File dealException(Thread t, Throwable e) throws Exception{
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            File f = new File(mContext.getExternalCacheDir().getAbsoluteFile(), "crash_info");
            if (!f.exists()) {
                f.mkdirs();
            }
            File crashFile = new File(f, time + FILE_NAME_SUFFIX);
            //往文件中写入数据
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
            pw.println(time);
            pw.println("Thread: " + t.getName());
            pw.println(getPhoneInfo());
            e.printStackTrace(pw); //写入crash堆栈
            pw.close();
            return crashFile;
        }
    
    
        private String getPhoneInfo() throws PackageManager.NameNotFoundException {
            PackageManager pm = mContext.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
            StringBuilder sb = new StringBuilder();
            //App版本
            sb.append("App Version: ");
            sb.append(pi.versionName);
            sb.append("_");
            sb.append(pi.versionCode + "\n");
    
            //Android版本号sb.append("OS Version: ");
            sb.append(Build.VERSION.RELEASE);
            sb.append("_");
            sb.append(Build.VERSION.SDK_INT + "\n");
    
            //手机制造商sb.append("Vendor: ");
            sb.append(Build.MANUFACTURER + "\n");
    
            //手机型号sb.append("Model: ");
            sb.append(Build.MODEL + "\n");
            //CPU架构
            sb.append("CPU: ");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                sb.append(Arrays.toString(Build.SUPPORTED_ABIS));
            } else {
                sb.append(Build.CPU_ABI);
            }
            return sb.toString();
        }
    
    }
    

    NDK异常

    NDK异常指的是app在使用c和c++ so库的时候,如果库运行引起app崩溃,我们同样可以采用类似的方式来收集和处理异常,用找出代码问题的具体位置。

    Linux信号机制了解

    信号机制是Linux进程间通信的一种重要方式,Linux信号一方面用于正常的进程间通信和同步,另一方

    面它还负责监控系统异常及中断。当应用程序运行异常时,Linux内核将产生错误信号并通知当前进

    程。当前进程在接收到该错误信号后,可以有三种不同的处理方式。

    忽略该信号;

    捕捉该信号并执行对应的信号处理函数(信号处理程序);

    执行该信号的缺省操作(如终止进程);

    当Linux应用程序在执行时发生严重错误,一般会导致程序崩溃。其中,Linux专门提供了一类crash信

    号,在程序接收到此类信号时,缺省操作是将崩溃的现场信息记录到核心文件,然后终止进程。

    常见崩溃信号列表:

    SIGSEGV

    内存引用无效。

    SIGBUS

    访问内存对象的未定义部分。

    SIGFPE

    算术运算错误,除以零。

    SIGILL

    非法指令,如执行垃圾或特权指令

    SIGSYS

    糟糕的系统调用

    SIGXCPU

    超过CPU时间限制。

    SIGXFSZ

    文件大小限制。

    一般的出现崩溃信号,Android系统默认缺省操作是直接退出我们的程序。但是系统允许我们给某一个

    进程的某一个特定信号注册一个相应的处理函数(signal),即对该信号的默认处理动作进行修改。因

    此NDK Crash的监控可以采用这种信号机制,捕获崩溃信号执行我们自己的信号处理函数从而捕获NDK

    Crash。

    BreakPad

    Google breakpad是一个跨平台的崩溃转储和分析框架和工具集合,其开源地址是:https://github.co

    m/google/breakpad。breakpad在Linux中的实现就是借助了Linux信号捕获机制实现的。因为其实现

    为C++,因此在Android中使用,必须借助NDK工具。

    引入项目

    将Breakpad源码下载解压,首先查看README.ANDROID文件。

    If you're using the ndk-build build system, you can follow
    these simple steps:
    
      1/ Include android/google_breakpad/Android.mk from your own
         project's Android.mk
    
         This can be done either directly, or using ndk-build's
         import-module feature.
    
      2/ Link the library to one of your modules by using:
    
         LOCAL_STATIC_LIBRARIES += breakpad_client
    
    NOTE: The client library requires a C++ STL implementation,
          which you can select with APP_STL in your Application.mk
    
          It has been tested succesfully with both STLport and GNU libstdc++
          
          使用步骤:
    
    1.将
    
    android/google_breakpad/Android.mk 导入到我们自己项目中;
    
    2.在自己的Android.mk中依赖breakpad_client
    

    第一步:我们先新建一个module命名为breakpad;

    第二步:在该module下的main目录新建cpp文件夹;

    第三步:将Breakpad下载解压后的src拷贝至cpp包下面;

    image-20201109014337179

    第四步:在Breakpad下与src同级目录下新建CMakeLists.txt,并编写编译内容,该内容来源于android/google_breakpad/Android.mk文件,但是需要用cmake语法来完成编译脚本,具体内容如下:

    cmake_minimum_required(VERSION 3.4.1)
    
    
    include_directories(src src/common/android/include)
    
    enable_language(ASM)
    
    add_library(breakpad STATIC
            src/client/linux/crash_generation/crash_generation_client.cc
            src/client/linux/dump_writer_common/thread_info.cc
            src/client/linux/dump_writer_common/ucontext_reader.cc
            src/client/linux/handler/exception_handler.cc
            src/client/linux/handler/minidump_descriptor.cc
            src/client/linux/log/log.cc
            src/client/linux/microdump_writer/microdump_writer.cc
            src/client/linux/minidump_writer/linux_dumper.cc
            src/client/linux/minidump_writer/linux_ptrace_dumper.cc
            src/client/linux/minidump_writer/minidump_writer.cc
            src/client/minidump_file_writer.cc
            src/common/convert_UTF.cc
            src/common/md5.cc
            src/common/string_conversion.cc
            src/common/linux/breakpad_getcontext.S
            src/common/linux/elfutils.cc
            src/common/linux/file_id.cc
            src/common/linux/guid_creator.cc
            src/common/linux/linux_libc_support.cc
            src/common/linux/memory_mapped_file.cc
            src/common/linux/safe_readlink.cc)
    
    
    target_link_libraries(breakpad log)
    

    第五步:在cpp目录下新建breakpad.cpp文件,编写breakpad crash监控代码,具体内容如下;

    //
    // Created by Administrator on 2020/11/5.
    //
    
    
    #include <jni.h>
    #include <android/log.h>
    #include "breakpad/src/client/linux/handler/minidump_descriptor.h"
    #include "breakpad/src/client/linux/handler/exception_handler.h"
    
    bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
                      void *context,
                      bool succeeded) {
        __android_log_print(ANDROID_LOG_ERROR, "native", "native crash:%s", descriptor.path());
        return false;
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_breakpad_crash_BreakpadCrash_initNativeCrash(JNIEnv *env, jclass clazz, jstring path_) {
    
        const char *path = env->GetStringUTFChars(path_, 0);
    
        __android_log_print(ANDROID_LOG_INFO, "native", "===> %s", path);
        google_breakpad::MinidumpDescriptor descriptor(path);
        static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback,
                                                    NULL, true, -1);
        env->ReleaseStringUTFChars(path_, path);
    
    }
    
    

    第六步:在cpp目录新建CMakeLists.txt来将breakpad库和crash 监控编译为可移植的so;

    cmake_minimum_required(VERSION 3.4.1)
    
    include_directories(breakpad/src breakpad/src/common/android/include)
    # libbugly.so
    add_library(breakpadcrash-lib SHARED breakpad.cpp)
    
    add_subdirectory(breakpad)
    
    # 链接ndk中的log库
    target_link_libraries(breakpadcrash-lib  breakpad log)
    

    第七步:新建java文件调用so 的native方法

    public class BreakpadCrash {
        static {
            System.loadLibrary("breakpadcrash-lib");
        }
    
        public static void init(Context context) {
            Context applicationContext = context.getApplicationContext();
            File file = new File(applicationContext.getExternalCacheDir(), "native_crash");
            if (!file.exists()) {
                file.mkdirs();
            }
            initNativeCrash(file.getAbsolutePath());
        }
    
        public static native void initNativeCrash(String file);
    }
    

    第八步:在build.gradle文件中添加编译配置

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.3"
    
    
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles 'consumer-rules.pro'
    
            externalNativeBuild {
                cmake {
    //                cppFlags "-std=c++11"
                    cppFlags ""
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        externalNativeBuild {
            cmake {
                path "src/main/cpp/CMakeLists.txt"
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        implementation 'androidx.appcompat:appcompat:1.2.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    

    第九步:之后build该模块就可以在build\intermediates\cmake目录中看到生成的so

    使用:在Application的oncreate中调用

    image-20201109015041781

    另外我们需要新建一个moudle来模拟第三方的jni so库,然后在activity中使用该测试库实现崩溃;

    image-20201109015240136
    image-20201109015501092

    应用崩溃之后可以在data.包名.cache.native下面找到日志文件,改文件为dmp文件,需要使用minidump_stackwalk 工具来编写为正常的日志文件,在 Android Studio 的安装目录下的 bin\lldb\bin 里面就存在一

    个对应平台的 minidump_stackwalk,进入该目录或者配置该目录到环境变量,然后执行:

    minidump_stackwalk xxxx.dump > crash.txt
    

    打开txt文件可以看到:

    image-20201109015903606

    有对应的错误类型和堆栈信息。

    接下来使用 Android NDK 里面提供的 addr2line 工具将寄存器地址转换为对应符号。addr2line 要用和

    自己 so 的 ABI 匹配的目录,同时需要使用有符号信息的so(一般debug的就有)。

    因为我使用的是模拟器x86架构,因此addr2line位于:

    E:\sdk\ndk\21.1.6352462\toolchains\x86_64-4.9\prebuilt\windows-x86_64\bin

    image-20201109020201718
    x86_64-linux-android-addr2line.exe -f -C -e E:\workspace\AndroidCrash\jnibug\build\intermediates\cmake\debug\obj\x86\libbugly-lib.so 0x5a4
    
    image-20201109020241444
    image-20201109020311629

    项目源码:https://github.com/heezier/AndroidCrash

    相关文章

      网友评论

        本文标题:Android异常监控

        本文链接:https://www.haomeiwen.com/subject/bogrbktx.html