ErQi

APP崩溃处理

解决APP出现异常崩溃,尔后而系统重启,导致一系列的空值问题

异常崩溃处理

每行代码都有潜在的崩溃风险,可能是逻辑有问题,可能是性能问题,可能是系统限制,版本迭代各种兼容性问题.
在无法测试出每一种可能出线的bug的情况下,针对崩溃的进行处理,给予用户更好的体验就很有必要了.

系统默认崩溃处理

APP发生的未被捕获的异常,统一会回调Thread.UncaughtExceptionHandler接口的uncaughtException(Thread t, Throwable e)方法,系统在RuntimeInit中实现了该接口,如下就是系统处理异常的代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void uncaughtException(Thread t, Throwable e) {
try {
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
if (mApplicationObject == null) {
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);
}
// Bring up crash dialog, wait for it to be dismissed
ActivityManagerNative.getDefault().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
} catch (Throwable t2) {
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的处理机制,此类异常关闭的Activity,会被系统重新创建并启动,但是启动的仅仅只是当前的崩溃的Activity,由于进程已经被干掉,所以之前在进程中的各种对象都是重新创建的,需要用到其他地方保存下来,或传递过来的值时,就会出现各种错误.导致一系列报错,体验感十分不佳.

自定义异常处理

首先我们要自己实现Thread.UncaughtExceptionHandler接口,尔后在Application中取代系统原来的.
为了给用户良好的体验,数据异常了,当前进程已经被暂停了,我们想恢复APP那么肯定就需要开一个新的进程把初始逻辑在从新走一遍.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (mCrashing) return;
mCrashing = false;
try {
handleException(ex);
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
Intent intent = new Intent(mContext, SplashActivity.class);
PendingIntent restartIntent = PendingIntent.getActivity(mContext, 0, intent, Intent.FILL_IN_DATA);
AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, restartIntent); // 1秒钟后重启应用
android.os.Process.killProcess(android.os.Process.myPid());
}

处理方式很简单,在handleException(ex)中对异常信息进行本地化保存方便后续处理.核心就是使用PendingIntentAlarmManager在进程被销毁之后重启APP流程.
需要注意的在杀掉进程前需要把所有Activity关掉,不然系统会对Activity进行恢复,这过程中又出现异常就会导致重启流程被调用两次

小疑问关于重启进程

通过PendingIntent 和 AlarmManager 可以重启进程.
那么 ContextImpl 和当前 进程 关系到底是怎样的,看相关APP启动分析都是说先启动进程,然后创建ContextImpl对象,那就是ContextImpl依赖于进程.
但是进程被销毁了,ContextImpl对象应该也就被销毁了,又是如何重新开启一个进程来运行APP的?