Android 应用中未捕获异常的处理

Android 应用开发中常常需要捕获全局异常,以进行一些个性化的处理,如弹出提示,上传错误报告等;

一般的做法是实现 UncaughtExceptionHandler 接口,并重写 uncaughtException() 方法:

public class CrashHandler implements UncaughtExceptionHandler{
	private static CrashHandler handler;//单例模式
	private Context context;
	
	public static CrashHandler getInstance() {
		if (handler == null){
			handler = new CrashHandler();
		}
		return handler;
	}

	public void init(Context context){
		this.context.context;
		Thread.setDefaultUncaughtExceptionHandler(this);//设置默认handler
	}

	@Override  
	public void uncaughtException(Thread thread, final Throwable error) {
		ex.printStackTrace();
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				Toast.makeText(context, "程序出现异常!", Toast.LENGTH_LONG).show();
				//TODO: 上传错误日志等
				Looper.loop();
			}
		}.start();
		try {//休眠以等待上面的操作完成,严格地说应该采用synchronized + wait() + notify()实现
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		android.os.Process.killProcess(android.os.Process.myPid());//杀掉当前进程
		//System.exit(0);
	}
}

上面的代码乍一看没有什么大问题,但是实际开发中会发现,如果发生 crash 的 Activity 不是第一个页面(栈内还有其他 Activity ),则会自动回到之前的页面。而以上操作只是杀掉了当前 Activity ,而且由于很多内容被回收,之前这些页面布局显示会变得混乱,严重影响用户体验。

实际上上面代码的 34 、35 两行代码效果差不多:都是杀掉当前进程,而我们要的效果是杀掉所有相关进程(同一个包下)。

关于 Android 怎么杀掉所有 Activity ,网上的相关文章有不少,例如我从 StackOverflow 上看到的:

Android: How to kill an application with all its activities?

里面大致有三种方案:

  • 重写所有 ActivityonActivityRsult 方法,在里面调用 finish()

  • 跳转到一指定 Activity 并清除栈顶,在该 ActivityonCreate() 中接收到 Intent 后调用 finish() ;

  • 在每个里面动态注册一个 BroadcastReceiver ,然后通过它去调用 finish() .

前两种没有亲测,第三种测过,结果是不但没能杀掉所有 Activity ,而且引起了ANR!只能另辟蹊径了。

每次系统启动都会开启一个 ActivityManagerService ( AMS )服务,在 AMS 服务中有一个 ActivityStack 实例专门管理手机上的 ActivityRecord 实例,这样 AMS 理论上应该能管理进程。

因此很自然想到 ActivityManager 这个类。看了下 API ,果然有个 killBackgroundProcesses() 方法,而且参数正是包名!果断将上面代码第 35 行改为:

((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
	.killBackgroundProcesses(context.getPackageName());

经过测试,部分机型 OK ,但仍有很多机型依旧会重新打开之前页面。但据同事说,这些机型上的其他应用 crash 时不会这样。

会不会是我们的人工干预(捕获异常)出了问题?如果我们不做任何处理会怎么样?

事实发现这样依然会自动重新打开之前页面。那就说明这应该是系统的一个特性,我们还是得自己处理 crash ,而且之前处理的不够。

于是又上 StackOverflow 找答案,这回找到个貌似比较靠谱的,至少标题看起来是这样:

Android App Restarts upon Crash/force close

而且里面一位仁兄的回复很符合我当时的想法:

However, if your app is crashing, then there’s not much you can do about it (apart from periodically saving important stuff to preferences/databases/etc.).

It is probably better to focus on preventing crashes, rather than trying to handle crashes!

呵呵~ 这问题看起来确实无解,可能我们能做的确实只有避免 crash ,而不是怎么优雅的处理 crash 的善后工作。

但是理智地想一想,Android 碎片化如此严重,要想完全避免 crash 那就是痴人说梦!

虽然从老外那里没有找到完美的解决方案,但是突然想到了一种可能的方案:强制跳转到桌面(Home),然后再杀掉当前进程,并 kill 后台进程。

在上面代码的 34 行插入跳转操作:

Intent intent=new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);

哈哈,这回亲测基本 OK :跳转到桌面后不会再返回到之前的页面,虽然手动重启时会跳转到 crash 之前的那个页面,但只需退出那个页面并再次启动即可,而不是像之前那样要将栈内的所有历史页面逐个 pop 出来。

虽然还谈不上完美得解决问题,但至少是个不错的折衷方案了。

Okay,先这样吧。