Android
的稳定性是Android
性能的一个重要指标,它也是App质量构建体系中最基本和最关键的一环。Crash
率多少算优秀呢?NullPPointerException
是空指针,OutOfMemoryError
是资源不足)Logcat
中我们可以看到当时系统的一些行为跟手机的状态,当从一条崩溃日志中无法看出问题的原因,或者得不到有用信息时,不要放弃,建议查看相同崩溃点下的更多崩溃日志。java.lang.NumberFormatException: For input string: "12.3"
,如果出现多次,需要统计到具体的次数。JVM
将调用Thread
中的dispatchUncaughtException
方法把异常传递给线程的未捕获异常处理器。Android
遇到异常会发生崩溃,然后找一些哪里用到设置setDefaultUncaughtExceptionHandler
,即可定位到RuntimeInit
类。KillApplicationHandler
,发生异常之后,会调用handleApplicationCrash
打印输出崩溃crash
信息,最后会杀死应用app
。RuntimeInit
类,由于是java代码,所以首先找main
方法入口。代码如下所示public static final void main(String[] argv) {
commonInit();
}commonInit()
方法,看看里面做了什么操作?setDefaultUncaughtExceptionHandler
方法,设置了自定义的Handler
类protected static final void commonInit() {
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
}KillApplicationHandler
类,可以发现该类实现了Thread.UncaughtExceptionHandler
接口fork
出app
进程的时候,系统已经为app
设置了一个异常处理,并且最终崩溃后会直接导致执行该handler
的finally
方法最后杀死app直接退出app。Thread.UncaughtExceptionHandler
。而调用setDefaultUncaughtExceptionHandler
多次,最后一次会覆盖之前的。KillApplicationHandler
类中的uncaughtException
方法ActivityManager.getService().handleApplicationCrash
被调用,那么这个是用来做什么的呢?app
为null
时,processName
就设置为system_server
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);
}handleApplicationCrashInner
方法做了什么。调用addErrorToDropBox
将应用crash
,进行封装输出void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
mAppErrors.crashApplication(r, crashInfo);
}> ActivityManagerService#findAppProcess(),这个是根据binder去找对应的crash的ProcessRecord对象
> ActivityManagerService#handleApplicationCrashInner(),这个方法很关键
> ActivityManagerService#addErrorToDropBox(),这个就是将crash,anr,装到盒子里。这个主要在下面会说到
> ActivityManagerService#appendDropBoxProcessHeaders,这个方法是拼接app的进程,pid,package包名等等
> SystemServer#run(),在这个线程run方法中,调用startOtherServices开启各种服务逻辑
> SystemServer#startOtherServices(),在这个方法里,是系统system_server进程开启众多服务,比如IMS输入事件服务,NMS通知栏服务等
> ActivityManagerService#startObservingNativeCrashes(),在这个类中创建NativeCrashListener去监控native崩溃
void consumeNativeCrashData(FileDescriptor fd) {
try {
//3.启动NativeCrashReporter作为上报错误的新线程
final String reportString = new String(os.toByteArray(), "UTF-8");
(new NativeCrashReporter(pr, signal, reportString)).start();
} catch (Exception e) {
}
}
}
```
native_crash
的线程-->NativeCrashReporter:class NativeCrashReporter extends Thread {
@Override
public void run() {
try {
//1.包装崩溃信息
CrashInfo ci = new CrashInfo();
//2.转到ams中处理,跟普通crash一致,只是类型不一样
mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
} catch (Exception e) {
}
}
}native crash
跟到这里就结束了,后面的流程就是跟application crash
一样,都会走到addErrorToDropBox
中。addErrorToDropBox
是殊途同归呢,因为无论是crash
、native_crash
、ANR
或是wtf
,最终都是来到这里,交由它去处理。public void addErrorToDropBox(……) {
//只有这几种类型的错误,才会进行上传
final boolean shouldReport = ("anr".equals(eventType)
|| "crash".equals(eventType)
|| "native_crash".equals(eventType)
|| "watchdog".equals(eventType));
//1.如果DropBoxManager没有初始化,或不是要上传的类型,则直接返回
if (dbox == null || !dbox.isTagEnabled(dropboxTag)&& !shouldReport)
return;
//2.添加一些头部log信息
//3.添加崩溃进程和界面的信息
//4.添加进程的状态到dropbox中
//5.将dataFile文件定入dropbox中,一般只有anr时,会将traces文件通过该参数传递进来者,其他类型都不传.
//6.如果是crash类型,会传入crashInfo,此时将其写入dropbox中
if (shouldReport) {
synchronized (mErrorListenerLock) {
try {
//7.关键,在这里可以添加一个application error的接口,用来实现应用层接收崩溃信息
mIApplicationErrorListener.onError(fEventType,
packageName, fProcessName, subject, dropboxTag + "-" + uuid, crashInfo);
}
}
}
}finish
直接推出App的首页,Activity
会调用onDestroy
。这种情况进程其实是未杀死的情况,moveTaskToBack
推出App,这种类似home键作用,Activity
是调用onStop
回到后台。activity
推出App,这种情况下,进程可能存活。killProcess
杀死进程,然后在调用System.exit
推出程序。这种方式是彻底杀死进程,比较粗暴【系统就是这种】。finish
掉所有activity
页面,然后在杀死进程和推出程序。可以避免闪一下……RuntimeInit
创建之初便准备好了defaultUncaughtHandler,用来来处理Uncaught Exception,并输出当前crash基本信息;AMP.handleApplicationCrash
,经过binder ipc机制,传递到system_server进程;AMS.handleApplicationCrash
;AMS.findAppProcess
查找到目标进程的ProcessRecord对象;然后调用AMS.handleApplicationCrashInner
,并将进程crash信息输出到目录/data/system/dropbox;ActivityManagerService#addErrorToDropBox()
,这个就是将crash,anr,装到盒子里。这个主要在下面会说到;RuntimeInit
处理崩溃finally中,执行杀死进程操作,当crash进程被杀,通过binder死亡通知,告知system_server进程来执行appDiedLocked();UncaughtExceptionHandler
接口,官方介绍为:@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}UncaughtExceptionHandler
接口用于处理因为一个未捕获的异常而导致一个线程突然终止问题。getUncaughtExceptionHandler()
函数去查询该线程的UncaughtExceptionHandler
并调用处理器的uncaughtException
方法将线程及异常信息通过参数的形式传递进去。如果一个线程没有明确设置一个UncaughtExceptionHandler,那么ThreadGroup对象将会代替UncaughtExceptionHandler完成该行为。如果ThreadGroup没有明确指定处理该异常,ThreadGroup将转发给默认的处理未捕获的异常的处理器。Thread
中的dispatchUncaughtException
方法把异常传递给线程的未捕获异常处理器。public final void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;
}Thread
中存在两个UncaughtExceptionHandler
。defaultUncaughtExceptionHandler
,另一个是非静态uncaughtExceptionHandler
。private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;defaultUncaughtExceptionHandler
:设置一个静态的默认的UncaughtExceptionHandler
。Exception
在抛出并且未捕获的情况下,都会从此路过。进程fork
的时候设置的就是这个静态的defaultUncaughtExceptionHandler
,管辖范围为整个进程。Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("所有线程异常都会被捕获,捕获所有线程:"+t.toString() + " throwable : " + e.getMessage());
}
});uncaughtExceptionHandler
:为单个线程设置一个属于线程自己的uncaughtExceptionHandler
,辖范围比较小。//为单个线程设置一个属于线程自己的uncaughtExceptionHandler,捕获单个线程异常。设置后,线程可以完全控制它对未捕获到的异常作出响应的处理。
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获单个线程:"+t.toString() + " throwable : " + e.getMessage());
}
});uncaughtExceptionHandler
怎么办?uncaughtExceptionHandler
,将使用线程所在的线程组来处理这个未捕获异常。ThreadGroup
实现了UncaughtExceptionHandler
,所以可以用来处理未捕获异常。ThreadGroup类定义:private ThreadGroup group;
//可以发现ThreadGroup类是集成Thread.UncaughtExceptionHandler接口的
class ThreadGroup implements Thread.UncaughtExceptionHandler{}ThreadGroup
中实现uncaughtException(Thread t, Throwable e)
方法,代码如下defaultUncaughtExceptionHandler
来处理异常,System.err
。也就是JVM提供给我们设置每个线程的具体的未捕获异常处理器,也提供了设置默认异常处理器的方法。public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
//返回线程由于未捕获到异常而突然终止时调用的默认处理程序。如果返回值为 null,则没有默认处理程序。
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
}
}
}UncaughtExceptionHandler
吗?UncaughtExceptionHandler
未免太过麻烦。Thread.setDefaultUncaughtExceptionHandler(handler)
就可以。setDefaultUncaughtExceptionHandler
被调用多次如何理解?Thread.setDefaultUncaughtExceptionHandler(handler)
方法如果被多次调用的话,会以最后一次传递的 handler 为准。handler
之前,可以先通过 getDefaultUncaughtExceptionHandler()
方法获取并保留旧的handler
,然后在默认handler
的uncaughtException
方法中调用其他handler
的uncaughtException
方法,保证都会收到异常信息。java
层的uncaughtException
方法。Hotspot
虚拟机源码的 thread.cpp
中的 JavaThread::exit 方法发现了这样的一段代码,并且还给出了注释:if (HAS_PENDING_EXCEPTION) {
ResourceMark rm(this);
jio_fprintf(defaultStream::error_stream(),
"\nException: %s thrown from the UncaughtExceptionHandler"
" in thread \"%s\"\n",
pending_exception()->klass()->external_name(),
get_thread_name());
CLEAR_PENDING_EXCEPTION;
}Thread.dispatchUncaughtException
方法。这个则是java层处理异常的入口!StackTraceElement
此类在 java.lang
包下Throwable.getStackTrace()
返回。每个元素表示单独的一个【堆栈帧】。jvm
是如何构造Throwable
异常的呢?int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
```
- 从代码中我们可以看出,main() 函数调用了 add() 函数,获取计算结果,并且与临时变量 a 相加,最后打印 res 的值。
- 为了让你清晰地看到这个过程对应的函数栈里出栈、入栈的操作,我画了一张图。图中显示的是,在执行到 add() 函数时,函数调用栈的情况。
- 
ThreadHandler
这个类就是实现了UncaughtExceptionHandler
这个接口。handler
将会报告线程终止和不明原因异常这个情况。public class ThreadHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mDefaultHandler;
public void init(Application ctx) {
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
}Logcat
。(包括应用、系统的运行日志,其中会记录 App 运行的一些基本情况)fd
。一般单个进程允许打开的最大文件句柄个数为 1024。但是如果文件句柄超过 800 个就比较危险,需要将所有的 fd 以及对应的文件名输出到日志中,进一步排查是否出现了有文件或者线程的泄漏data/data/包名
目录下的缓存文件。/sdcard/Android/data/包名
下存储文件。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。