<
Android如何捕获异常
>
上一篇

Java内存模型与线程
下一篇

MediaExtractor、MediaMuxer 分离和合成MP4

Android如何捕获异常

crash日志分类:

  1. JVM异常(Exception)堆栈信息
  2. native代码崩溃日志

一、JVM异常堆栈信息

Java中异常分两种:

  1. 检查异常

    在代码编译时期,AS会提示代码有错误,无法通过编译,IOException。如果直接抛出则也会导致崩溃

  2. 非检查异常

    AS不会在编译时期提示这些异常信息,而是在程序运行时期因为代码错误而直接导致崩溃,例如OOM或空指针异常,包含:

    1. error
    2. 运行时异常

对于上述两种异常,都可以使用UncaughtExceptionHandler来进行捕获,他是Thread的一个内部接口

    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

从注释来看,对于传入的Thread,如果因为“未捕获”异常而导致被终止,uncaughtException则会被调用,可以借助他来间接捕获程序异常,并进行异常信息的记录。

自定义异常处理类

自定义类实现UncaughtExceptionHandler接口,并实现uncaughtException方法:

private Context mContext;

private Thread.UncaughtExceptionHandler mDefaultHandler;

public void init(Context context) {
	this.mContext = context;
	//系统默认UncaughtExceptionHandler
	this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
	//设置改CrashHandler为系统默认的
	Thread.setDefaultUncaughtExceptionHandler(this);
}

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            // 如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            Log.e("CrashHandler", "system.exit");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            restartApp(mContext);
        }
    }
    
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        // 使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常。", Toast.LENGTH_LONG)
                        .show();
                Looper.loop();
            }
        }.start();

        // 收集设备参数信息
        collectDeviceInfo(mContext);

        // 保存日志文件
        String str = saveCrashInfo2File(ex);

        return true;
    }

需要注意:

  1. 在自定义异常处理类中,需要持有线程默认异常处理类。目的是在自定义异常处理类无法处理或者处理异常失败时,可以交给系统做默认处理。
  2. 如果自定义异常处理类处理成功时,需要进行页面跳转,或者将程序杀死。否则程序会一直卡死在崩溃界面,并弹出无响应对话框。

一般情况下,在handleException中需要做一下几件事:

  1. 收集crash现场的相关信息,如APP版本信息,手机设备相关信息
  2. 日志的记录工作,将收集到的信息保存在本地,如文件方式进行保存,除了自己收集的日志,还需要将系统抛出的异常信息也保存到文件中。

二、native异常

当程序中的native代码发生崩溃时,系统会在/data/tombstones/ 目录下保存一份崩溃日志信息。如果是必现的可以直接看日志,如果不是必现的就需要将日志信息保存到设备中,目前使用广泛且成熟的就是Google的BreakPad。

可以在GitHub上看到介绍文件。我们可以直接使用CMake方式将其编译为一个静态库。在捕获native crash之前,需要初始化BreakPad保存crash日志的路径。

三、线上崩溃日志获取

上述介绍的Java和native崩溃的捕获都是基于现场能够复现bug的前提下。但对于线上用户来说不现实。所以一般会使用腾讯的Bugly,Bugly可以满足线上版本捕获crash的所有需求,包括Java层和native层的crash。

除了Bugly外,还有其他的工具,比如XCrash和Sentry。这两者比Bugly好的地方就是除了自动拦截界面崩溃事件,还可以主动上报错误信息,设置过滤条件,使用更加灵活。

Top
Foot