Android 未捕获异常处理


Java 在默认在线程中发生不可捕获异常时,如果不对异常进行处理,则会抛出异常,但是不会导致整个进程奔溃,这得益于异常退出JVM仅在一个线程栈中有效。而对于其他线程栈的执行来说是没有影响的。

但是在Java执行程序中,当异常发生在不同线程时,对于整个进程的返回值是不同的结果。

Java 的异常表现

当未捕获异常发生在main入口时。

  public static void main(String[] args) {
        throw new RuntimeException("throw main thread exception");
    }

控制台输出。

Exception in thread "main" java.lang.RuntimeException: throw main thread exception
	at com.bevis.java.example.ThrowableTest.main(ThrowableTest.java:24)

Process finished with exit code 1

当异常发生在异步线程时。

   public static void main(String[] args) {
        new Thread(() -> {
            throw new RuntimeException("throw asynchronous thread exception");
        }).start();
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("exit process");
    }

控制台输出。

Exception in thread "Thread-0" java.lang.RuntimeException: throw asynchronous thread exception
	at com.bevis.java.example.ThrowableTest.lambda$main$0(ThrowableTest.java:7)
	at java.lang.Thread.run(Thread.java:748)
exit process

Process finished with exit code 0

对比以上结果,当由main入口进入的线程发生异常时,则进程退出状态码为1(异常状态),而异步线程则返回 0 (正常状态)。

Android 的异常表现

Android 中并不是终端程序,因此不能对比状态码,但是无论是在主线程或者异步线程下,都会发生App崩溃,并抛出异常。

2020-04-08 20:45:45.618 5200-5247/com.bevis.test E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.bevis.test, PID: 5200
    java.lang.RuntimeException: throw asynchronism thread exception
        at com.bevis.test.MainActivity$onCreate$1$1.run(MainActivity.kt:25)

UncaughtExceptionHandler 捕获

UncaughtExceptionHandler 是Java平台提供的一套对未捕获异常的处理机制,对于在线程中所发生的未捕获异常,最终都将在这里面进行处理。如果未设置则通过对应线程的 ThreadGroup 进行处理。

对应 Android 和 Java 平台的不同表现,猜测是通过该拦截进行处理。

ThreadGroup 的处理

public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
    ...
    public void uncaughtException(Thread t, Throwable e) {
        // 如果 ThreadGroup 存在 parent ThreadGroup 则交由 parent 处理。否则由Thread.getDefaultUncaughtExceptionHandler 进行处理。在没有复写ThreadGroup方法的情况下,root ThreadGroup 最终还是由 getDefaultUncaughtExceptionHandler 进行处理
        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);
            }
        }
    }    
    ...
}

从以上可以得到,对应 root ThreadGroup最终处理该异常的还是 Thread.getDefaultUncaughtExceptionHandler

通过Java程序获得,该对象为null 而 Android 则为

com.android.internal.os.RuntimeInit$KillApplicationHandler@804b454

KillApplicationHandler

KillApplicationHandler 主要处理三个工作:

  • 处理日志输出
  • 通知AMS进行Applicatin Crash 处理
  • 杀死App发生Crash的进程(多进程下其他进程不影响)
   private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
        public volatile boolean mTriggered = false;

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            mTriggered = true;

            if (mCrashing) return;
            
            // 区分系统异常和普通异常,组装异常信息并抛出
            if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
            } else {
                StringBuilder message = new StringBuilder();
                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                final String processName = ActivityThread.currentProcessName();
                if (processName != null) {
                    message.append("Process: ").append(processName).append(", ");
                }
                message.append("PID: ").append(Process.myPid());
                Clog_e(TAG, message.toString(), e);
            }
        }
    }


private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        // 作为异常日志输出
        private final LoggingHandler mLoggingHandler;

        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                // 调用 LoggingHandler 输出异常信息
                ensureLogging(t, e);

                // 防止重复进入异常处理
                if (mCrashing) return;
                mCrashing = true;

                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                // 通知AMS Application崩溃处理
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                // 杀死进程(APP退出)
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

        private void ensureLogging(Thread t, Throwable e) {
            // 避免重复触发 mLoggingHandler
            if (!mLoggingHandler.mTriggered) {
                try {
                    mLoggingHandler.uncaughtException(t, e);
                } catch (Throwable loggingThrowable) {
                    // Ignored.
                }
            }
        }
    }

总结

对于Android在Crash的处理差异,是由于Android系统对默认未捕获进行了额外的处理,如果不希望这么处理,则可以重新对 Thread.setDefaultUncaughtExceptionHandler 进行设置,改变Android系统对未捕获异常的默认行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值