Java异常及异常处理


我们首先来看Java的异常及异常处理。

Java异常分类

  • 可查的异常(checked exceptions): 编译器要求必须处置的异常(使用 try…catch…finally 或者 throws )。在方法中要么用try-catch语句捕获它并处理,要么用 throws 子句声明抛出它,否则编译不会通过。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。

  • 不可查的异常(unchecked exceptions):包括运行时异常(RuntimeException与其子类)和错误(Error)。在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。

UncaughtExceptionHandler

Thread中存在两个UncaughtExceptionHandler:

  1. 一个是静态的defaultUncaughtExceptionHandler:来自所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler,管辖范围为整个进程。
  2. 另一个是非静态uncaughtExceptionHandler:为单个线程设置一个属于线程自己的uncaughtExceptionHandler,辖范围比较小。

Thread类的异常处理变量声明:

    //成员变量,线程独有的    // null unless explicitly set    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;     //静态变量,用于所有线程    // null unless explicitly set    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

当一个线程由于未捕获异常即将终止时,Java虚拟机将使用Thread的getuncaughtexceptionhandler()方法查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。一个线程如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。

ThreadGroup实现的uncaughtException如下:

    public void uncaughtException(Thread t, Throwable e) {        if (parent != null) {            parent.uncaughtException(t, e);        } else {            Thread.UncaughtExceptionHandler ueh =                Thread.getDefaultUncaughtExceptionHandler();            if (ueh != null) {                ueh.uncaughtException(t, e);            } else if (!(e instanceof ThreadDeath)) {                System.err.print("Exception in thread \""                                 + t.getName() + "\" ");                e.printStackTrace(System.err);            }        }    }

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

Android中异常(Crash)处理和捕获


在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。

Crash是App稳定性的一个重要指标,大量的Crash是非常差的用户体验,会导致用户的流失。Crash发生之后,我们应该及时处理并解决,然后发版对其进行修复。

那么,问题来了,我们想要解决Crash,但是Crash发生在用户手机上,我们如何能拿到我们想要的错误信息呢?

Android中添加全局的Crash监控实战

在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。

1. 创建一个自定义UncaughtExceptionHandler类

public class CrashHandler implements Thread.UncaughtExceptionHandler {    @Override    public void uncaughtException(Thread thread, Throwable ex) {        //回调函数,处理异常        //在这里将崩溃日志读取出来,然后保存到SD卡,或者直接上传到日志服务器                //如果我们也想继续调用系统的默认处理,可以先把系统UncaughtExceptionHandler存下来,然后在这里调用。    }}

2. 设置全局监控

CrashHandler crashHandler = new CrashHandler();Thread.setDefaultUncaughtExceptionHandler(crashHandler);

完成以上2个步骤,我们就可以实现全局的Crash监控了。这里所说的全局,是指针对整个进程生效。

Android系统中Crash的处理、分发逻辑


上面我们了解了怎么在Android中捕获Crash,实现Crash的监控、上报。那么,在Android中,系统是如何处理、分发Crash的呢?

异常处理的注册

App启动时,会通过zygote进程fork一个进程,然后创建VM虚拟机,然后会调用到zygoteInit进行初始化工作。

zygoteInit方法

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的zygoteInit方法:

    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,            ClassLoader classLoader) {        ……        RuntimeInit.commonInit();        ……    }

zygoteInit调用了RuntimeInit.commonInit()方法。

RuntimeInit.commonInit()方法

    protected static final void commonInit() {        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");        /*         * set handlers; these apply to all threads in the VM. Apps can replace         * the default handler, but not the pre handler.         */        LoggingHandler loggingHandler = new LoggingHandler();        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));    }
逻辑解析:
  1. LoggingHandler用于处理打印日志,我们不做详细分析,感兴趣的可以自己看下。
  2. Thread.setDefaultUncaughtExceptionHandler用于注册系统默认异常处理的逻辑。

这里的RuntimeHooks.setUncaughtExceptionPreHandler方法,其实是调用了Thread的setUncaughtExceptionPreHandler方法。

Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。

我们先来看异常的分发逻辑,后面再分析KillApplicationHandler中对异常的处理逻辑。

异常的分发

Thread的dispatchUncaughtException负责处理异常的分发逻辑。

Thread的dispatchUncaughtException

    public final void dispatchUncaughtException(Throwable e) {        // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.        Thread.UncaughtExceptionHandler initialUeh =                Thread.getUncaughtExceptionPreHandler();        if (initialUeh != null) {            try {                initialUeh.uncaughtException(this, e);            } catch (RuntimeException | Error ignored) {                // Throwables thrown by the initial handler are ignored            }        }        // END Android-added: uncaughtExceptionPreHandler for use by platform.        getUncaughtExceptionHandler().uncaughtException(this, e);    }

这里首先处理setUncaughtExceptionPreHandler注册的异常处理方法,然后在调用通过setDefaultUncaughtExceptionHandler或setUncaughtExceptionHandler方法注册的异常处理方法。

我们来看getUncaughtExceptionHandler()方法的返回值。

Thread的getUncaughtExceptionHandler()方法

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {        return uncaughtExceptionHandler != null ?            uncaughtExceptionHandler : group;    }
逻辑解析:
  1. 当uncaughtExceptionHandler不为空时,返回uncaughtExceptionHandler。uncaughtExceptionHandler是通过setUncaughtExceptionHandler方法注册异常处理Handler。
  2. 否则,返回group。
  3. 这里的group,其实就是当前线程所在的线程组。并且线程组ThreadGroup同样实现了UncaughtExceptionHandler接口。

ThreadGroup的uncaughtException

在本文第一部分,我们其实已经介绍了,这里来回顾下:

    public void uncaughtException(Thread t, Throwable e) {        if (parent != null) {            parent.uncaughtException(t, e);        } else {            Thread.UncaughtExceptionHandler ueh =                Thread.getDefaultUncaughtExceptionHandler();            if (ueh != null) {                ueh.uncaughtException(t, e);            } else if (!(e instanceof ThreadDeath)) {                System.err.print("Exception in thread \""                                 + t.getName() + "\" ");                e.printStackTrace(System.err);            }        }    }
逻辑解析:

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

异常的处理

系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,我们来看代码。

KillApplicationHandler类的uncaughtException方法

        public void uncaughtException(Thread t, Throwable e) {            try {                //确保LoggingHandler的执行                ensureLogging(t, e);                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.                if (mCrashing) return;                mCrashing = true;                // Try to end profiling. If a profiler is running at this point, and we kill the                // process (below), the in-memory buffer will be lost. So try to stop, which will                // flush the buffer. (This makes method trace profiling useful to debug crashes.)                if (ActivityThread.currentActivityThread() != null) {                    ActivityThread.currentActivityThread().stopProfiling();                }                //调用AMS,展示弹出等逻辑                // Bring up crash dialog, wait for it to be dismissed                ActivityManager.getService().handleApplicationCrash(                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));            } catch (Throwable t2) {                if (t2 instanceof DeadObjectException) {                    // System process is dead; ignore                } else {                    try {                        Clog_e(TAG, "Error reporting crash", t2);                    } catch (Throwable t3) {                        // Even Clog_e() fails!  Oh well.                    }                }            } finally {                //杀死进程和退出虚拟机                // Try everything to make sure this process goes away.                Process.killProcess(Process.myPid());                System.exit(10);            }        }
逻辑解析:
  1. 调用ensureLogging(t, e)确保LoggingHandler的执行(有去重逻辑,不用担心重复执行)。
  2. 然后调用了ActivityManager.getService().handleApplicationCrash方法来进行处理。
  3. 最后调用Process.killProcess(Process.myPid())来杀死进程,并且退出VM。

AMS的handleApplicationCrash方法

我们继续来看ActivityManagerService的handleApplicationCrash方法:
位置:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    public void handleApplicationCrash(IBinder app,            ApplicationErrorReport.ParcelableCrashInfo crashInfo) {        ProcessRecord r = findAppProcess(app, "Crash");        final String processName = app == null ? "system_server"                : (r == null ? "unknown" : r.processName);        handleApplicationCrashInner("crash", r, processName, crashInfo);    }

这里通过Application的binder,取得进程的ProcessRecord对象,然后调用handleApplicationCrashInner方法。

AMS的handleApplicationCrashInner方法

    final AppErrors mAppErrors;    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,            ApplicationErrorReport.CrashInfo crashInfo) {        /*        处理一些错误日志相关的逻辑        */        //调用        mAppErrors.crashApplication(r, crashInfo);    }

这里处理一些错误日志相关的逻辑,然后调用AppErrors的crashApplication方法。

AppErrors的crashApplication方法

/frameworks/base/services/core/java/com/android/server/am/AppErrors.java

    void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {        final int callingPid = Binder.getCallingPid();        final int callingUid = Binder.getCallingUid();        final long origId = Binder.clearCallingIdentity();        try {            crashApplicationInner(r, crashInfo, callingPid, callingUid);        } finally {            Binder.restoreCallingIdentity(origId);        }    }

crashApplication调用crashApplicationInner方法,处理系统的crash弹框等逻辑,这里我们就不再详细分析了,感兴趣的可以看源码了解下。

到了这里,关于系统默认的异常捕获及处理逻辑我们也就分析完成了。

Crash优化建议


Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性,以下是几点实践经验:

  1. 要有可靠的Crash日志收集方式:可以自己实现,也可以集成第三方SDK来采集分析。
  2. 当一个Crash发生了,我们不但需要针性的解决这一个Crash,而且要考虑这一类Crash怎么去解决和预防,只有这样才能使得该类Crash真正的解决,而不是反复出现。
  3. 不能随意的使用try-catch,这样只会隐蔽真正的问题,要从根本上了解Crash的原因,根据原因去解决。
  4. 增加代码检测,预防常规可检测的代码问题的产生,预防胜于治理。

总结


这里来总结下:

  1. Java异常可分为:可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
  2. Java中,可以通过设置Thread类的uncaughtExceptionHandler属性或静态属性defaultUncaughtExceptionHandler来设置不可查异常的回调处理。
  3. 在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。
  4. 在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。
  5. 通过Thread的静态方法setDefaultUncaughtExceptionHandler方法,可以注册全局的默认Crash监控。通过Thread的setUncaughtExceptionHandler方法来注册某个线程的异常监控。
  6. setDefaultUncaughtExceptionHandler方法和setUncaughtExceptionHandler方法有注册顺序的问题,多次注册后,只有最后一次生效。
  7. Android系统中,默认的Crash处理Handler,是在进程创建时,通过RuntimeInit.commonInit()方法进行注册的。
  8. Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。
  9. Thread的dispatchUncaughtException负责处理异常的分发逻辑。
  10. Android中,异常分发顺序为:
    • 首先处理setUncaughtExceptionPreHandler注册的异常处理方法;
    • 然后处理线程私有的(uncaughtExceptionHandler)Handler异常处理方法;
    • 如果私有Handler不存在,则处理ThreadGroup的Handler异常处理方法;
    • ThreadGroup中,优先调用父线程组的处理逻辑,否则,调用通过setUncaughtExceptionHandler方法注册异常处理Handler。
  11. 系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,系统默认Crash弹框等逻辑是通过AMS的handleApplicationCrash方法执行的。
  12. Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性。

更多相关文章

  1. android 显示消息框的方法
  2. android中对apk文件反编译的方法(详细)
  3. Android(安卓)源码分析之旅3.4--onConfigurationChanged
  4. 一文全面了解Android单元测试
  5. 好的android程序该这样编写
  6. AsyncTask源码深入分析和巧记线程池
  7. 深入理解Binder通信原理及面试问题
  8. Android(java方法)上实现mp4的分割和拼接 (一)
  9. AsyncTask使用以及源码分析

随机推荐

  1. AIR Native Extension的使用(Android)一
  2. Android中GPS定位的简单应用
  3. android源码下载方式
  4. Android(安卓)组件资源库
  5. Android(安卓)Porting Environment Set
  6. Android调用.NET Webservice报org.ksoap2
  7. Android(安卓)Studio bug - attribute 'a
  8. Android(安卓)Studio & ADT 快捷键配置文
  9. Ionic 运行报错No resource identifier f
  10. 从 Android(安卓)Sample ApiDemos 中学习