Android Crash的防护与追踪
来源:互联网 发布:淘宝圭亚那粉趾 编辑:程序博客网 时间:2024/06/07 07:44
一. 序
Android系统中,抛出Exception 或者 Error都会导致Crash.进而导致App强制退出.简单的来说就是因为抛出异常的代码.并未被Try catch包围..就会导致进程被杀.
二. 原理
从Fork进程伊始,就已经存在的UncaughtExceptionHandler(大致描述了AMS对于异常处理的过程.).
1. 进程Fork之后就注册了一个UncaughtHandler
//RuntimeInit.java中的zygoteInit函数public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { ............ //跟进commonInit commonInit(); ............}private static final void commonInit() { ........... /* set default handler; this applies to all threads in the VM */ //到达目的地! Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler()); ...........}
2. 异常处理
当UncaughtHandler接收到未捕获异常的时候.进程会自杀,并且弹出大家最熟悉不过的Force Close对话框.
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler { 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 { //打印进程的crash信息 ............. } ............. // Bring up crash dialog, wait for it to be dismissed //调用AMS的接口,进行处理 ActivityManagerNative.getDefault().handleApplicationCrash( mApplicationObject, new ApplicationErrorReport.CrashInfo(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. //crash的最后,会杀死进程 Process.killProcess(Process.myPid()); //并exit System.exit(10); } }}
又发现了神秘的System.exit(10);
这里面的魔法数字.
Difference in System. exit(MagicCode) in Java
3.UncaughtExceptionHandler
3.1 简单说说UncaughtExceptionHandler
UncaughtExceptionHandler存在于Thread中.当异常发生且未捕获时.异常会透过UncaughtExceptionHandler
抛出.并且该线程会消亡.所以在Android中子线程死亡是允许的.主线程死亡就会导致ANR.
下面是相关源码的截取.仔细阅读会发现.Thread中存在两个UncaughtExceptionHandler.一个是静态的defaultUncaughtExceptionHandler,另一个是非静态uncaughtExceptionHandler.
- defaultUncaughtExceptionHandler:设置一个静态的默认的UncaughtExceptionHandler.来自所有线程中的Exception在抛出,并且为捕获的情况下.都会从此路过.大家可以看到进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler.管辖范围为整个进程.
- uncaughtExceptionHandler:为单个线程设置一个.属于线程自己的uncaughtExceptionHandler.也就是说.他的管辖范围比较小.
public class Thread implements Runnable { ........... @FunctionalInterface public interface UncaughtExceptionHandler { void uncaughtException(Thread t, Throwable e); } // null unless explicitly set private volatile UncaughtExceptionHandler uncaughtExceptionHandler; // null unless explicitly set private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { defaultUncaughtExceptionHandler = eh; } public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { checkAccess(); uncaughtExceptionHandler = eh; } ...}
3.2 UncaughtExceptionHandler的”职责连”
当我们自定一个
CrashHandler
并register()
,本质上这个CrashHandler
就已经持有进程中上一个注册成DefaultUncaughtExceptionHandler
的引用..并且将自己设置成进程中DefaultUncaughtExceptionHandler
.异常来了.我们先在
uncaughtException
中处理,如果不拦截.就包装一些扩展信息,并且交给我手中的人质mUncaughtExceptionHandler
继续处理.大家可能看出来了.这是一个链式的结构.直到丢给最后进程中的UncaughtExceptionHandler.然后就ForceClose了.
public class CrashHandler implements Thread.UncaughtExceptionHandler { private Thread.UncaughtExceptionHandler mUncaughtExceptionHandler; ...... void register() { mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); } } @Override public void uncaughtException(Thread thread, Throwable throwable) { //做些事情 ...... if(心情不好){ return; } //心情不好的话,异常就不能继续传递了. mUncaughtExceptionHandler.uncaughtException(thread, facadeThrowable); } ......}
总结:很重要! 很重要!! 很重要!!!
UncaughtExceptionHandler
是以链式结构存在.原则上,谁后注册的,谁优先处理异常,并且决定,这个异常是否交给上一个注册的.这点很重要,牢记!假如我们注册在其他UncaughtExceptionHandler
后边很有可能导致,因为他们并未继续传递Exception.导致一些其他问题.所以我们要注册在最后.以便优先处理.
三. App层可以做的Crash防护
1.Crash统计平台
例如Fabric等错误日志上报平台,可以当Crash发生时,收集异常信息.到平台,此处不扩展讲.网上相关文档很多.本质也是注册一个UncaughtExceptionHandler
然后将Throw上报给服务器.
2. try-catch大法好.
Java的异常处理可以让程序具有更好的容错性,程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序。大家肯定会考虑到性能损耗问题。毕竟做了“额外”的事情。这里我从两种方式去探究一下:
写两个一样逻辑的函数,只不过一个包含try-catch代码块,一个不包含,分别循环调用百万次,通过System.nanoTime()来比较两个函数百万次调用的耗时。本机跑了一下基本上没什么区别。
可以看看.java文件经过编译生成的JVM可以执行的.class文件里的字节码指令。
javap -verbose ReturnValueTest xx.class 命令可以查看字节码
《深入Java虚拟机》作者Bill Venners于1997年所写的文章How the Java virtual machine handles exceptions比较详尽地分析了一番。文章从反编译出的指令发现加了try-catch块的代码跟没有加的代码运行时的指令是完全一致的(你也可以按照上面命令自行进行对比)。 * 如果程序运行过程中不产生异常的话try catch 几乎是不会对运行产生任何影响的*。只是在产生异常的时候jvm会追溯异常栈。这部分耗时就相对较高了。
3.上文地提到的”职责连”
我们能做的就是在所有第三方的UncaughtExceptionHandler
注册之后,注册一个自己的CrashHandler
.这样我们就可以在第一时间接收到异常之后,做异常拦截或者异常包装.
4.异常拦截
上文同样提到了.我们注册一个自己的CrashHandler
的目的之一,就是优先与异常见面..当发现可以拦截的异常的时候.就不将其继续传递.异常拦截最重要的原则就是不能拦截主线程中的异常:
这是一段异常拦截的代码:
@Override public void uncaughtException(Thread thread, Throwable throwable) { //先尝试拦截拦截 if (crashInterceptor()) { return; } uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable); }
//异常拦截器public static boolean crashInterceptor(Throwable throwable, Thread thread) { if (thread.getId() == 1 || throwable == null || throwable.getMessage() == null || throwable.getStackTrace() == null) { //异常发生之后,所在线程会挂掉.所以主线程异常,拦截了也没用.主线程也会死掉. //除非,后续判断,app在前台,触发APP重启. return false; } String classpath = null; if (throwable.getStackTrace() != null && throwable.getStackTrace().length > 0) { classpath = throwable.getStackTrace()[0].toString(); } if (classpath == null) { return false; } //拦截GMS异常. if (throwable.getMessage().contains("Results have already been set") && classpath.contains( "com.google.android.gms")) { logException(throwable); return true; } //拦截GMS的 NPE. if (classpath.contains("com.google.android.gms") && throwable instanceof NullPointerException) { CrashHelper.logException(CrashFacade.facadeThrowable(throwable)); return true; } //拦截ssl_NPE if (throwable instanceof NullPointerException && throwable.getMessage() .contains("ssl_session == null")) { CrashHelper.logException(CrashFacade.facadeThrowable(throwable)); return true; } return false; }
5.异常信息包装上传
当我们用了平台之后,发现除了我们自己能看到的又明确调用栈的异常信息.还有许许多多看不到调用栈的.或者是第三方SDK里的Crash.这些Crash因为没有调用栈.一直是个很头疼的问题.
@Override public void uncaughtException(Thread thread, Throwable throwable) { //先尝试拦截拦截 if (crashInterceptor()) { return; } //再包装扩展信息,交给Fabric上报服务器 Throwable facadeThrowable = facadeThrowable(throwable , "<HelloWorld>"); uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable); }
//通过反射,在detailMessage后面追加信息 public static Throwable facadeThrowable(Throwable throwable , String facadeMessage) { try { Field field = getDeclaredField(throwable, "detailMessage"); if (field == null) { return throwable; } field.setAccessible(true); String originDetailMessage = (String) field.get(throwable); String newDetailMessage = originDetailMessage + facadeMessage; field.set(throwable, newDetailMessage); } catch (Exception ignore) { CrashHelper.logExceptionWithoutFacade(ignore); } return throwable; }
此处我们追加的部分信息的截图.两个例子.没有调用栈的情况.
我这里都是先通过包装Crash.收集没有调用栈信息异常和第三方库的异常的所在线程.在谨慎的增加对应的异常拦截.确保没有在主线程中拦截异常..毕竟ANR了..也不合适.就直接挂掉吧…
WARNING:之前尝试.在UnCaughtHandler中,将Exception放到一个new Throwable
的cause中.并追加信息.这种方式会导致平台日志堆叠,因为new Throwable都产生在同样的地方.平台会把日志合并.所以才考虑用反射的方法加到detailMessage后面的.
Fabric会给与
大结局
哦?FinalizerWatchdogDaemon是什么线程?
引发了我研究从Daemons到finalize timed out after 10 seconds这个问题
简书个人主页欢迎大家关注
- Android Crash的防护与追踪
- android crash 追踪方式
- iOS中的crash防护(二)KVC造成的crash
- iOS中的crash防护(三)KVO造成的crash
- iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的追踪方法
- bughd追踪crash的具体代码
- iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的追踪方法
- 炒冷饭系列--iOS crash 崩溃问题的追踪方法
- iOS crash 崩溃问题的定位和追踪方法
- iOS Xcode 自带crash 崩溃问题的追踪方法
- Android resources.arsc文件与资源防护
- android的crash log
- android的crash log
- POJ 3096.Surprising Strings
- gradle 使用心得
- QTableWidget 基本操作(一)
- Android——API23以上需要的动态权限
- Spring IoC原理理解
- Android Crash的防护与追踪
- Java内存模型—JMM
- Android Java8 新特性
- 机器学习入门笔记第1课:朴素贝叶斯
- Android签名详解
- 异常-常见的异常
- LeetCode(28) Implement strStr()
- swagger2快速搭建《一》
- MyEclipse下复制web项目或更改项目名称后重新部署到Tomcat后找不到项目页面解放方法