美文网首页
FirebaseCrash和自定义Thread.Uncaught

FirebaseCrash和自定义Thread.Uncaught

作者: CarlosLuo | 来源:发表于2018-06-09 23:37 被阅读0次

      最近在项目中集成了Firebase的crash报告插件,遇到了一个小的问题,由于项目中之前也使用的自定义的Thread.UncaughtExceptionHandler(具体实现是重启了app,并屏蔽掉了系统的应用程序停止的弹框),导致覆盖掉了Firebase这个对异常处理的设置.

    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }
    

      上面是UncaughtExceptionHandler这个接口的定义,Thread.UncaughtExceptionHandler这个是个什么东西呢,了解的同学可能大致都明白,它是一个由系统收集线程异常并可以被进行处理的一个时机,最终触发uncaughtException这个方法的执行,意味着我们可以自定义设置这个handler,当系统发生异常时,我们可以进行自己的处理.首先分析下它的设值的实现,下面看代码,其实比较好理解.

    /**
     * Set the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception, and no other handler has been defined
     * for that thread.
     *
     */
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
     }
    
    /**
     * Returns the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception. If the returned value is <tt>null</tt>,
     */
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }
    
    

      上面是它在Thread类中的set和get方法,可以看到,defaultUncaughtExceptionHandler是一个静态属性,即说明了它在java程序中是全局唯一的对象.至于get方法,是在哪里被调用,我们就不讨论了.不知道大家是否有印象,每次我们开发的app出错的时候,系统就给我们弹了一个经典的提示,应用程序停止运行,其实它里面也是根据这个UncaughtExceptionHandler来实现的.我们可以简单看看,我们知道,一个应用的启动,系统是帮我们做了很多事情的,那么系统是在什么时候帮我们设置这个UncaughtExceptionHandler的呢.我以android 5.1的代码为例子,简单看看这个设置的地方.

    在源码RuntimeInit这个类中,定义了一个默认的UncaughtHandler,它的定义如下,实现了Thread类的UncaughtExceptionHandler接口.

    /**
     * Use this to log a message when a thread exits due to an uncaught
     * exception.  The framework catches these for the main threads, so
     * this should only matter for threads created by applications.
     */
    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 {
                    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);
            }
        }
    }
    

    当系统出现异常时,常见的就是那几种运行时异常,空指针等等,这个时候,它的uncaughtException方法就会被触发,可以看到在这个方法中,try代码块中最重要的就是把这个事件上报给了AMS,上报完成之后,最后finally代码块就是调用结束这个进程.AMS中收到这个上报之后,就会进行一些相关处理,比如系统那个"应用程序停止",就是在AMS中进行处理的.这里我也就不继续跟进了,毕竟android整个都是这样一个C-S的架构,感兴趣的同学,可以继续源码分析.

      讲了这么多,似乎还没有进入本文的主题,为什么自定义的CrashHandler和Firebase Crash会有一点冲突呢,原因就在于前面提到的,Thread的defaultUncaughtExceptionHandler在一个java程序中是全局唯一的对象(不考虑多classloader的情况,抓住问题的重点),并且Firebase Crash初始化的时机比我们Application更早,一般来讲我们的CrashHandler大都是在application#onCreat中初始化,稍后我们再提到这个.下面我们先看看一般一个自定义CrashHandler的实现.

    public class CrashHandler implements Thread.UncaughtExceptionHandler {
    
        private Thread.UncaughtExceptionHandler mDefaultHandler;
    
        public CrashHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
            mDefaultHandler = uncaughtExceptionHandler;
        }
    
        @Override
        public void uncaughtException(Thread thread, Throwable ex) {
          // ..... self handler.
        }
    }
    
    // init code.
    CrashHandler crashHandler = new CrashHandler(Thread.getDefaultUncaughtExceptionHandler());
    Thread.setDefaultUncaughtExceptionHandler(crashHandler);
    

    一般的,我们自定义的CrashHandler,它内部会包装一个当前默认的UncaughtExceptionHandler,进行一次包装,在我们不想要自己处理的时候,可以直接委托给mDefaultHandler,让它自行处理,不破坏整个流程.

    到这里,我想大家也应该大致明白了,为什么会和firebase crash有所冲突,其实它也是根据这个原理,只不过在uncaughtException这个方法中,实现了自己的一套把错误信息上报的一个工作,并没有什么很高大上的东西,我们简单看看FirebaseCrash它的处理,让大家有个更深的认识.它的代码默认是混淆过的,不过并不影响我们分析.在FirebaseCrash这个文件中,我的firebase版本是11.8.0

    class zzc implements UncaughtExceptionHandler {
           private final UncaughtExceptionHandler zzmin;
    
           public zzc(@Nullable UncaughtExceptionHandler var2) {
               this.zzmik = FirebaseCrash.this;
               super();
               this.zzmin = var2;
           }
    
           public final void uncaughtException(Thread var1, Throwable var2) {
               Log.e("UncaughtException", "", var2);
               if(!this.zzmik.zzbsm()) {
                   try {
                       Future var3;
                       if((var3 = this.zzmik.zzh(var2)) != null) {
                           var3.get(10000L, TimeUnit.MILLISECONDS);
                       }
                   } catch (Exception var4) {
                       Log.e("UncaughtException", "Ouch! My own exception handler threw an exception.", var4);
                   }
               }
    
               if(this.zzmin != null) {
                   this.zzmin.uncaughtException(var1, var2);
               }
    
           }
       }
    
    // init code.
    com.google.firebase.crash.zzc var4 = new com.google.firebase.crash.zzc(var0, (String)null);
                  Thread.setDefaultUncaughtExceptionHandler(var3.new zzc(Thread.getDefaultUncaughtExceptionHandler()));
    
    

    可以看到,zzc这个就是它自定义的一个UncaughtExceptionHandler实现类,和我们自定义的其实是一个思路,也是直接包装了系统默认的UncaughtExceptionHandler.然后在uncaughtException中,是进行了它的错误报告方法,接着,不打断系统的默认实现,委托给当前默认的UncaughtExceptionHandler继续执行,保证整个调用链的完整性.相当于是加了一层处理,任何时候,我们也应该是要按这个思想编写代码,不破坏原有的行为下,实现我们自己想要的功能.

      到这里,CrashHandler和Firebase Crash的冲突我想大家已经明白了,因为它的初始化时机比Application还要早,那么怎么解决这个问题呢,二者如果不可兼得,那就有点不完美了.其实解决这个问题有几个方法,第一就是我们在它之前初始化,提前一步包装系统的UncaughtExceptionHandler,这样在Firebase uncaughtException中,自然会执行到我们的CrashHandler的方法,完美解决问题.第二个方法就是,既然我们已经慢了一部,那么,就可以通过反射的方法,达到第一种方法的效果,保证UncaughtExceptionHandler这样一个顺序,Firebase -> 自定义CrashHandler -> 系统默认的handler,这样也是完全能够解决问题.一旦系统的handler生效,我们就无法屏蔽"应用程序停止的"弹框了,其实整体来讲也不是什么冲突,可能是看业务的需求吧.当然上面两种能满足我这个业务,但我不推荐第二种,如果我们有更好的选择,显然反射是最后的杀手锏,因为它有一些缺点,这里必须依赖于类的名字,会增加代码的不稳定性.

      如何在Firebase Crash插件之前初始化我们的UncaughtExceptionHandler,既然它也是属于我们app下的一个模块而已,那么我们肯定是有办法在它之前初始化的.Firebase Crash它的初始化也没有什么很高深的技术,只不过是抓住了有点,在android应用程序中,ContentProvider的初始化更加早于application,这点大家可以自行看看源码ActivityThread这个文件.那么现在问题就简单了,你不是在Provider里面加载,那么照葫芦画瓢,我们也建一个Provider只要优先级比你的高,那么肯定最先加载的是我的,所以最终我这个业务的解决方案就是,自定义一个优先级更高的Provider,初始化相关的代码,保证了最终UncaughtExceptionHandler的包装顺序,Firebase -> 自定义CrashHandler -> 系统默认的handler.这样在我自己的CrashHandler里面,就可以想做什么做什么了,要不要调用系统的handler也就完全由自己掌控了,同时crash报告也完美工作了.

    相关文章

      网友评论

          本文标题:FirebaseCrash和自定义Thread.Uncaught

          本文链接:https://www.haomeiwen.com/subject/jvwxeftx.html