快捷搜索:  as  1111

Android Crash战斗日记(一、原理篇)

媒介

Crash预计是所有Android开拓者的一块芥蒂,无论是新手小白照样高手大年夜牛,都无法避免碰到Crash。然则Crash是怎么孕育发生的呢,这篇将深入的解说Crash。

一、非常

说到Crash,得先从非常讲起,NullPointerException是大年夜家最认识的非常之一,下面这个图片就Bugly上面的一个NullPointerException:

image.png

先看看类布局:

image

发明着实内部什么都没有,只是承袭了RuntimeException,看起来似乎完全没故意义,这种形式的设计更多的是将类本身算作一个类型作为判断。下图是java标注库中的类承袭图。

image

Throwable 类是 Java 说话中所有差错或非常的超类。只有当工具是此类(或其子类之一)的实例时,才能经由过程 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可所以 catch 子句中的参数类型。

Error 是 Throwable 的子类,用于唆使合理的利用法度榜样不应该试图捕获的严重问题。大年夜多半这样的差错都长短常前提。虽然 ThreadDeath 差错是一个“正规”的前提,但它也是 Error 的子类,由于大年夜多半利用法度榜样都不应该试图捕获它。

Exception 类及其子类是 Throwable 的一种形式,它指出了合理的利用法度榜样想要捕获的前提。

RuntimeException 是那些可能在 Java 虚拟机正常运行时代抛出的非常的超类。可能在履行措施时代抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。

RuntimeException是一种Unchecked Exception,即表示编译器不会反省法度榜样是否对RuntimeException作了处置惩罚,在法度榜样中不必捕获RuntimException类型的非常,也不必在措施体声明抛出RuntimeException类。一样平常来说,RuntimeException发生的时刻,表示法度榜样中呈现了编程差错,以是应该找出差错改动法度榜样,而不是去捕获RuntimeException。常见的RuntimeException有NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException等。

CheckedException是相对付Unchecked Exception而言的,Java中并没有一个名为Checked Exception的类。它是在编程中应用最多的Exception,所有承袭自Exception并且不是RuntimeException的非常都是Checked Exception。JAVA 说话规定必须对checked Exception作处置惩罚,编译器会对此作反省,要么在措施体中声明抛出checked Exception,要么应用catch语句捕获checked Exception进行处置惩罚,不然不能经由过程编译。常用的Checked Exception有IOException、ClassNotFoundException等。

二、非常孕育发生历程

非常孕育发生的历程必要从虚拟机讲起,虚拟机运行时数据区如下图所示:

image

此中虚拟机栈是线程私有的,每个Java措施的调用对应一个栈帧在虚拟机栈中的入栈和出栈。当线程履行一个Java措施履行时,就会创建一个新的栈帧并压入到该线程的虚拟机栈的栈顶,Java措施履行停止后栈顶的该栈帧就会弹出栈并销毁。

image

1.措施出口(返回地址)

当一个措施被履行后,有两种要领退出这个措施。第一种要领是履行引擎碰到随意率性一个措施返回的字节码指令,这时刻可能会有返回值通报给上层的措施调用者(调用当前措施的措施称为调用者),是否有返回值和返回值的类型将根据碰到何种措施返回指令来抉择,这种退出措施的要领称为正常完成出口(Normal Method Invocation Completion)。

别的一种退出要领是,在措施履行历程中碰到了非常,并且这个非常没有在措施体内得到处置惩罚,无论是Java虚拟机内部孕育发生的非常,照样代码中应用athrow字节码指令孕育发生的非常,只要在本措施的非常表中没有搜索到匹配的非常处置惩罚器,就会导致措施退出,这种退出措施的要领称为非常完成出口(Abrupt Method Invocation Completion)。一个措施应用非常完成出口的要领退出,是不会给它的上层调用者孕育发生任何返回值的。

无论采纳何种退出要领,在措施退出之后,都必要返回到措施被调用的位置,法度榜样才能继承履行,措施返回时可能必要在栈帧中保存一些信息,用来赞助规复它的上层措施的履行状态。一样平常来说,措施正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而措施非常退出时,返回地址是要经由过程非常处置惩罚器来确定的,栈帧中一样平常不会保存这部分信息。

措施退出的历程实际上等同于把当前栈帧出栈,是以退出时可能履行的操作有:规复上层措施的局部变量表和操作数栈,把返回值(假如有的话)压入调用者栈帧的操作数栈中,调剂PC计数器的值以指向措施调用指令后面的一条指令等。

2.虚拟机栈Error

Java虚拟机栈有可能呈现的error便是StackOverflowError和OutOfMemoryError。当线程哀求的栈深度大年夜于Java虚拟机栈容许的深度时,就会抛出StackOverflowError差错。比如将一个措施反复递归,终极就会呈现StackOverflowError。当Java虚拟机栈可以动态扩展时(大年夜部分的 Java 虚拟机都可动态扩展,不过 Java 虚拟机规范中也容许固定长度的虚拟机栈),假如无法申请到足够的内存来扩展栈,就会抛出OutOfMemoryError差错

终极假如非常不停没有处置惩罚,就会经由过程Thread.dispatchUncaughtException(Throwable e)进行非常分发:

image

image

优先经由过程自身的uncaughtExceptionHandler处置惩罚非常,假如为null,则经由过程自身的ThreadGroup处置惩罚,ThreadGroup承袭UncaughtExceptionhandler,在类初始化时默认会创建两个ThreadGroup:main、system,system是main的父ThreadGroup。

image

终极非常到了system的uncaughtException(Thread t, Throwable e),在defaultUncaughtExceptionHandler中进行处置惩罚,Android中默认的是KillApplicationHandler

/**

* Handle application death from an uncaught exception.The framework

* catches these for the main threads, so this should only matter for

* threads created by applications. Before this method runs, the given

* instance of {@link LoggingHandler} should already have logged details

* (and if not it is run first).

*/

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

private final LoggingHandler mLoggingHandler;

/**

* Create a new KillApplicationHandler that follows the given LoggingHandler.

* If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called

* on the created instance without {@code loggingHandler} having been triggered,

* {@link LoggingHandler#uncaughtException(Thread, Throwable)

* loggingHandler.uncaughtException} will be called first.

*

* @param loggingHandler the {@link LoggingHandler} expected to have run before

*this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}

*is being called.

*/

public KillApplicationHandler(LoggingHandler loggingHandler) {

this.mLoggingHandler = Objects.requireNonNull(loggingHandler);

}

@Override

public void uncaughtException(Thread t, Throwable e) {

try {

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();

}

// 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);

}

}

三、Android主线程非常阐发

为什么要零丁阐发Android主线程非常呢?大年夜家可能都想过一个问题,假如在uncaughtExceptinHandler中将非常拦截下来,那是不是我们的利用就永世不会崩溃了。读者不用再去考试测验了,笔者已经去考试测验过一次了,结果当然是不可的。作为基于事故机制的系统,从轮询义务的历程中跳出后,着实系统就竣事了。

image

以上是Android 26中的ActivityThread.java源码,看的出来这是一个进程进口,主要的是做Looper的初始化,也是App全部事故机制的开始,此中Looper.loop()便是事故轮询的开始。

public static void loop() {

for(;;) {

...

Message msg = queue.next(); // might block

msg.target.dispatchMessage(msg);

...

}

}

当呈现UncaughtException时,会打断事故轮询机制,导致App退出。

四、总结

本篇文章总结了Android中Java层中Crash的孕育发生和历程,这将赞助我们去定位问题和办理问题。

您可能还会对下面的文章感兴趣: