android进阶篇11、crash监控与anr分析简述

·  阅读 627

一、crash监控

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

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

1、Java Crash

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

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、NDK Crash

1)、Linux信号机制

信号机制是Linux进程间通信的一种重要方式,Linux信号一方面用于正常的进程间通信和同步,另一方 面它还负责监控系统异常及中断。当应用程序运行异常时,Linux内核将产生错误信号并通知当前进 程。当前进程在接收到该错误信号后,可以有三种不同的处理方式。

  • 忽略该信号;
  • 捕捉该信号并执行对应的信号处理函数(信号处理程序);
  • 执行该信号的缺省操作(如终止进程);

当Linux应用程序在执行时发生严重错误,一般会导致程序崩溃。其中,Linux专门提供了一类crash信 号,在程序接收到此类信号时,缺省操作是将崩溃的现场信息记录到核心文件,然后终止进程。 常见崩溃信号:

  • SIGSEGV 内存引用无效。
  • SIGBUS 访问内存对象的未定义部分。
  • SIGFPE 算术运算错误,除以零。
  • SIGILL 非法指令,如执行垃圾或特权指令
  • SIGSYS 糟糕的系统调用
  • SIGXCPU 超过CPU时间限制。
  • SIGXFSZ 文件大小限制。

一般的出现崩溃信号,Android系统默认缺省操作是直接退出我们的程序。但是系统允许我们给某一个 进程的某一个特定信号注册一个相应的处理函数(signal),即对该信号的默认处理动作进行修改。因 此NDK Crash的监控可以采用这种信号机制,捕获崩溃信号执行我们自己的信号处理函数从而捕获NDK Crash。

2)、墓碑文件

Android本机程序本质上就是一个Linux程序,当它在执行时发生严重错误,也会导致程序崩溃,然后产 生一个记录崩溃的现场信息的文件,而这个文件在Android系统中就是 tombstones 墓碑文件。

普通应用无权限读取墓碑文件,墓碑文件位于路径/data/tombstones/下。解析墓碑文件与后面的breakPad都可使用 addr2line 工具。

3)、BreakPad

Google breakpad是一个跨平台的崩溃转储和分析框架和工具集合,其开源地址是:github.co m/google/breakpad。breakpad在Linux中的实现就是借助了Linux信号捕获机制实现的。因为其实现 为C++,因此在Android中使用,必须借助NDK工具。

二、ANR分析简介

anr全称application not responding,应用程序无响应,是通过AMS检测的,分为四种类型

  • KeyDispatchTimeout 输入事件无响应,超时时间5s
  • BroadcastTimeout 广播超时,前台广播10s,后台广播60s
  • ServiceTimeout 服务响应超时,前台服务20s,后台服务200s
  • ContentProviderTimeout 内容提供器超时,10s内没有处理完触发

上述四种类型比较特殊的是输入事件超时,其他三种类型如果到了指定的时间没有处理完一定会出发anr,但是输入事件如果到了5s没有处理完不一定触发anr,只要没有下一次输入事件等待处理就不会触发anr,我们平常接触最多的也是输入事件超时;

主要有以下三种原因导致anr

  • 在主线程中频繁的进行耗时IO操作,比如文件读写、数据库操作、操作sp等
  • 多线程下主线程被死锁
  • 系统资源被耗尽,比如cpu、io、管道等资源

anr问题解决主要方法

我们在开发过程中发生anr,我们可以结合logcat日志以及trace文件定位问题发生的地方,然后根据业务具体情况具体分析;我们在分析trace文件时注意几个点

  • 如果CPU的使用量很高,说明当前设备很忙,可能是CPU饥饿导致了anr
  • 如果cpu使用很低,说明主线程可能被block住了
  • 如果IOwait占比很高,那很大可能就是在主线程进行了IO耗时操作

anr线上监控方法

  1. 通过watchdog
  2. 通过FileObserver监听data/anr文件夹的变化

anr触发的原理,一个事件发生后,不管是广播还是服务还是输入事件,都会在system_server进程中AMS中埋下一条记录,然后去处理这个事件,如果在规定的时间内处理完毕,则把这条记录消除,如果没有处理完,就会通过引爆这条记录,触发anr。

开发过程中我们要保持良好的习惯,UI线程不要执行耗时的操作,耗时的操作去放到子线程中处理,然后通过handler在子线程和UI线程之间通信,子线程处理完了通过Handler通知到UI线程即可;

分类:
Android