Android性能优化—Crash监控方案

 一、Crash(应用崩溃)

Crash是由于代码异常而导致 App 非正常退出,导致应用程序无法继续使用,所有工作都
停止的现象。发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得多么优秀,也无法避免 Crash 发生,因此对Crash的监控是必不可少的。

在 Android 应用中发生的 Crash 有两种类型,Java 层的 Crash 和 Native 层 Crash。这两种Crash 的监控和获取堆栈信息有所不同。

二、Java Crash

Java的Crash监控非常简单,Java中的Thread定义了一个接口: UncaughtExceptionHandler ;用于处理未捕获的异常导致线程的终止(注意:catch了的是捕获不到的),当我们的应用crash的时候,就会走 UncaughtExceptionHandler.uncaughtException ,在该方法中可以获取到异常的信息,我们通过 Thread.setDefaultUncaughtExceptionHandler 该方法来设置线程的默认异常处理器,我们可以将异常信息保存到本地或者是上传到服务器,方便我们快速的定位问题。 

1、自定义Java CrashHandler

public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String FILE_NAME_SUFFIX = ".trace";
    private static Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    private static Context context;


    public static void init(Context applicationContext) {
        context = applicationContext;
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());
    }

    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
        try {
            File file = dealException(t, e);

        } catch (Exception exception) {

        } finally {
            if (defaultUncaughtExceptionHandler != null) {
                defaultUncaughtExceptionHandler.uncaughtException(t, e);
            }
        }
    }

    private File dealException(Thread thread, Throwable throwable) throws JSONException, IOException, PackageManager.NameNotFoundException {
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

        //私有目录,无需权限
        File f = new File(context.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: " + thread.getName());
        pw.println(getPhoneInfo());
        throwable.printStackTrace(pw); //写入crash堆栈
        pw.flush();
        pw.close();
        return crashFile;
    }

    private String getPhoneInfo() throws PackageManager.NameNotFoundException {
        PackageManager pm = context.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(context.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();
    }
}

 2、在Application里面调用

CrashHandler.init(applicationContext);

三、NDK Crash

相对于Java的Crash,NDK的错误及其麻烦难懂,不说监控,就算是错误堆栈都不知道怎么看。
NDK的Crash监控使用BreakPad来实现。Google breakpad是一个跨平台的崩溃转储和分析框架和工具集合,其开源地址是:https://github.com/google/breakpad。breakpad在Linux中的实现就是借助了Linux信号捕获机制实现的。因为其实现为C++,因此在Android中使用,必须借助NDK工具。

1、引入项目

将Breakpad源码下载解压,引入项目中

Android性能优化—Crash监控方案_第1张图片

2、配置Breakpad的CMakeLists

cmake_minimum_required(VERSION 3.4.1)

set(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})

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)

3、配置cpp的CMakeLists

cmake_minimum_required(VERSION 3.4.1)


include_directories(breakpad/src breakpad/src/common/android/include)
add_subdirectory(breakpad)

add_library(
        bugly
        SHARED
        ndk_crash.cpp)


target_link_libraries(
        bugly
        breakpad
        log)

4、Native层Crash监控代码实现

#include 
#include 
#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, "ndk_crash", "Dump path: %s", descriptor.path());
    //如果回调返回true,Breakpad将把异常视为已完全处理,禁止任何其他处理程序收到异常通知。
    //如果回调返回false,Breakpad会将异常视为未处理,并允许其他处理程序处理它。
    return false;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_enjoy_crash_CrashReport_initBreakpad(JNIEnv *env, jclass type, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);

    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1);
    env->ReleaseStringUTFChars(path_, path);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_enjoy_crash_CrashReport_testNativeCrash(JNIEnv *env, jclass clazz) {

    int *i = NULL;
    *i = 1;
}

5、Java层启动Native层Crash监控

public class CrashReport {

    static {
        System.loadLibrary("bugly");
    }


    public static void init(Context context) {
        File file = new File(context.getExternalCacheDir(), "native_crash");
        if (!file.exists()) {
            file.mkdirs();
        }
        initBreakpad(file.getAbsolutePath());
    }

    private static native void initBreakpad(String path);

    public static native void testNativeCrash();

}

6、Crash文件解析

此时,如果出现NDK Crash,会在我们指定的目录生成.dmp的Crash文件。

采集到的Crash信息记录在minidump文件中,我们可以将此文件上传到服务器完成上报,但是此文件没有可读性,要将文件解析为可读的崩溃堆栈。在 Android Studio 的安装目录下的 bin\lldb\bin 里面就存在一个对应平台的 minidump_stackwalk 。

Android性能优化—Crash监控方案_第2张图片

使用这里的minidump_stackwalk工具执行: 

minidump_stackwalk xxxx.dump > crash.txt

打开解析后的Crash文件 crash.txt 内容为:

Operating system: Android
0.0.0 Linux 4.4.124+ #1 SMP PREEMPT Wed Jan 30 07:13:09 UTC
2019 i686
CPU: x86 // abi类型
GenuineIntel family 6 model 31 stepping 1
3 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV //内存引用无效 信号
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed) //crashed:出现crash的线程
0 libbugly.so + 0x1feab //crash的so与寄存器信息
eip = 0xd5929eab esp = 0xffa85f30 ebp = 0xffa85f38 ebx = 0x0000000c
esi = 0xd71a3f04 edi = 0xffa86128 eax = 0xffa85f5c ecx = 0xefb19400
edx = 0x00000000 efl = 0x00210286
Found by: given as instruction pointer in context
1 libart.so + 0x5f6a18
eip = 0xef92ea18 esp = 0xffa85f40 ebp = 0xffa85f60
Found by: previous frame's frame pointer
Thread 1
......

接下来使用 Android NDK 里面提供的 addr2line 工具将寄存器地址转换为对应符号。addr2line 要用和自己 so 的 ABI 匹配的目录,同时需要使用有符号信息的so(一般debug的就有)。

因为我使用的是模拟器x86架构,因此addr2line位于:
Android\Sdk\ndk\21.3.6528147\toolchains\x86-4.9\prebuilt\windows-x86_64\bin\i686-linuxandroid-addr2line.exe

i686-linux-android-addr2line.exe -f -C -e libbugly.so 0x1feab

Android性能优化—Crash监控方案_第3张图片

定位到问题所在行数,修复Crash。

你可能感兴趣的:(android,性能优化,android,java)