Android收集程序崩溃日志

作者: DaZenD | 来源:发表于2021-05-08 17:10 被阅读0次

前言

简述一下故事背景:

这篇文章是基于我们自己项目的方案完善的,,首先市面的技术方案都一样,所以,前半部分的基础知识不全是我码的字,有参考部分别的优秀文章

废话

release版app崩溃了怎么整,直接闪退,闪退多了,用户不保啊。

所以,开发时候多测试,调试,优化。。上线了,就要配套有release的异常收集。大问题赶紧发热更新,小问题,也不至于闪退

正文

异常分两类,可检测的,编译时候as就给提示了。不可检测的,也就是要等到运行时,才会异常:

exception-type.png

unCheckedException(非检查异常):Error和RuntimeException以及他们各自的子类,都是非检查异常。换句话说,当我们编译程序的时候,编译器并不会提示我们这些异常。要么我们在编程的时候,对于可能抛出异常的代码加上try…catch,要么就等着运行的时候崩溃就好了。

checkedException(检查异常):除了UncheckedException之外,其他的都是checkedExcption。对于这种异常,我们的代码通常都无法进行编译,因为as都会提示我们出错了。这个时候要强制加上try…catch,或者将异常throw。

原理

方案:基于UncaughtExceptionHandler自定义CrashHandler

三方:bugly,友盟统计。也是基于以上方案来的

 /**
     * Interface for handlers invoked when a <tt>Thread</tt> abruptly
     * terminates due to an uncaught exception.
     * 处理接口,当一个线程由于未捕获的异常突然停止的时候调用。
     * 
     * <p>When a thread is about to terminate due to an uncaught exception
     * the Java Virtual Machine will query the thread for its
     * <tt>UncaughtExceptionHandler</tt> using
     * {@link #getUncaughtExceptionHandler} and will invoke the handler's
     * <tt>uncaughtException</tt> method, passing the thread and the
     * exception as arguments.
     * 当一个线程由于一个未捕获的异常即将崩溃的时候,Java虚拟机将会通过【getUncaughtExceptionHandler()】方法,来
     * 查询这个线程的【UncaughtExceptionHandler】,并且会调用他的【uncaughtException()】方法,并且把当前线程
     * 和异常作为参数传进去。
     * 
     * If a thread has not had its <tt>UncaughtExceptionHandler</tt>
     * explicitly set, then its <tt>ThreadGroup</tt> object acts as its
     * <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object
     * has no
     * special requirements for dealing with the exception, it can forward
     * the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
     * default uncaught exception handler}.
     *如果一个线程没有设置他的【UncaughtExceptionHandler】,那么他的ThreadGroup对象就会作为他的
     *【UncaughtExceptionHandler】。如果【ThreadGroup】没有特殊的处理异常的需求,那么就会转调
     *【getDefaultUncaughtExceptionHandler】这个默认的处理异常的handler。
     *(线程组的东西我们先不管,我们只需要知道,如果Thread没有设置【UncaughtExceptionHandler】的话,那么
     *最终会调用【getDefaultUncaughtExceptionHandler】获取默认的【UncaughtExceptionHandler】来处理异常)
     *
     * @see #setDefaultUncaughtExceptionHandler
     * @see #setUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
@FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * 当传过来的【Thread】因为传过来的未捕获的异常而停止时候调用这个方法。
         * 所有被这个方法抛出的异常,都将会被java虚拟机忽略。
         */
        void uncaughtException(Thread t, Throwable e);
    }

如果给一个线程设置了UncaughtExceptionHandler 这个接口:

1、这个线程中,所有未处理或者说未捕获的异常都将会由这个接口处理,也就说被这个接口给try…catch了。

2、在这个线程中抛出异常时,java虚拟机将会忽略,也就是说,java虚拟机不会让程序崩溃了。

3、如果没有设置,那么最终会调用getDefaultUncaughtExceptionHandler 获取默认的UncaughtExceptionHandler 来处理异常。

我们都知道我们的android程序是跑在UI线程中的,而且我们会在程序中创建各种子线程。为了统一,如果我们给每个线程都通过setUncaughtExceptionHandler() 这个方法来设置UncaughtExceptionHandler 的话,未免太不优雅了。在上面官方代码的注释中有一句,就是如果线程没有设置UncaughtExceptionHandler ,那么会通过getDefaultUncaughtExceptionHandler 来获取默认的UncaughtExceptionHandler 来处理异常。
这样的话,我们只需要在我们应用程序打开的时候,设置一个默认的UncaughtExceptionHandler ,就可以统一处理我们应用程序中所有的异常了!

自定义CrashHandler

https://blog.csdn.net/xy4_android/article/details/80846610

感受一下自定义crashhandler啥样的,,代码太多,就不贴我们项目的代码了

注意点

如上,所有未捕获的异常都可以回调到自定义的handler中。工程里可能用了三方统计,如果自己也实现一套方案。Thread.setDefaultUncaughtExceptionHandler(handler)方法被多次调用,会以最后一次调用时传递的handler为准,所以三方统计的可能失效。

下面先讲三方方案,然后讲我们的方案

三方方案

Cockroach

https://github.com/android-notes/Cockroach/blob/master/原理分析.md

我们项目是基于这个的,内部也是Thread.setDefaultUncaughtExceptionHandler

但是注意:UncaughtExceptionHandler 拦截到的子线程的异常,并不会闪退,对用户是无感的。但是如果是ui线程的异常呢?还是会闪退,重启。

Cockroach很巧妙的拦截了主线程的异常:

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (t == Looper.getMainLooper().getThread()) {
                
                
                //mainlooper,就活在主线程中
                    safeMode();
                }
            }
        });

    private static void safeMode() {
        ...
        while (true) {
            //死循环,保证ui线程一直阻塞到这
            try {
                //不断的读取queue中的Message并执行
                //这样就可以保证以后主线程的所有异常都会从我们手动调用的Looper.loop()处抛出,一旦抛出就会被try{}catch捕获,这样主线程就不会crash了
                Looper.loop();
            } catch (Throwable e) {
                isChoreographerException(e);
                if (sExceptionHandler != null) {
                    sExceptionHandler.bandageExceptionHappened(e);
                }
            }
        }
    }

来吧,看张图

cockroach-joke.jpeg

这就是Cockroach的心态,其他的自行去看看吧,总之所有的异常都捕获了,具体什么情况下重启app,你决定。比如Cockroach的onMayBeBlackScreen回调中,可以重启app

bugly

这里看一下腾讯bugly的实现就知道了

//路径,bugly的jar包 - com.tencent.bugly - crashreport - crash - e
//bugly混淆处理了,异常处理类就是e

public class e implements UncaughtExceptionHandler {
    
    public synchronized void a() {
        if (this.j >= 10) {
        } else {
            Thread.setDefaultUncaughtExceptionHandler(this);
        }
    }
}

只看关键就行了,一是基于UncaughtExceptionHandler的,二是内部也设置了Thread.setDefaultUncaughtExceptionHandler(this);

所以,集成了三方,又自己实现异常收集的话,是要注意这个问题的

我们项目里同时有自己实现的异常处理,也用了bugly,神奇,怎么处理的,我觉得是瞎猫撞到死耗子了:::详细看下bugly咋处理的

...

protected UncaughtExceptionHandler e;
    protected UncaughtExceptionHandler f;
...

    public synchronized void a() {
        if (this.j >= 10) {
            an.a("java crash handler over %d, no need set.", new Object[]{10});
        } else {
            
            //下面两行是重点
            //下面两行是重点
            //下面两行是重点
            
            // 获取先前设置的 ExceptionHandler
            // 不要把 ExceptionHandler 的初始化放在 Bugly 初始化后面,就是别的异常处理设置的handler不要在bugly之后就对了。。
            
            UncaughtExceptionHandler var1 = Thread.getDefaultUncaughtExceptionHandler();
            if (var1 != null) {
                ...

                if ("com.android.internal.os.RuntimeInit$UncaughtHandler".equals(var1.getClass().getName())) {
                // 这里是安卓系统的 ExceptionHandler
                
                // 把原先的暂存起来,改为走自己的
                // f 存系统的 ExceptionHandler
                // e 存java(也就是app)的 ExceptionHandler
                    an.a("backup system java handler: %s", new Object[]{var1.toString()});
                    this.f = var1;
                    this.e = var1;
                } else {
                // 这里是是java层面手动设置的 ExceptionHandler
                //重点是这,我们自己实现了一套异常收集,,
                
                
                
                //这是重点
                //这是重点
                //这是重点

                // 把原先的暂存起来,改为走自己的
                    an.a("backup java handler: %s", new Object[]{var1.toString()});
                    this.e = var1;
                    
                }
            }

            //这里表示拦截UncaughtException
           Thread.setDefaultUncaughtExceptionHandler(this);
        }
    }
    
    
    //UncaughtExceptionHandler的接口方法,异常回调
    public void uncaughtException(Thread thread, Throwable ex) {
        synchronized(i) {
            this.b(thread, ex, true, (String)null, (byte[])null);
        }
    }
    
    
    //这里是处理异常的逻辑
    
    public void b(Thread var1, Throwable var2, boolean var3, String var4, byte[] var5) {
        ...

        try {
            
        } catch (Throwable var11) {
            
        } finally {
            if (var3) {
                if (this.e != null && this.a(this.e)) {
                //这是重点
                //这是重点
                //这是重点
                
                //最后回到原处理流程上去,,
                
                    an.e("sys default last handle start!", new Object[0]);
                    this.e.uncaughtException(var1, var2);
                    an.e("sys default last handle end!", new Object[0]);
                } else if (this.f != null) {
                    an.e("system handle start!", new Object[0]);
                    this.f.uncaughtException(var1, var2);
                    an.e("system handle end!", new Object[0]);
                } else {
                    an.e("crashreport last handle start!", new Object[0]);
                    this.a(var1, var2);
                    an.e("crashreport last handle end!", new Object[0]);
                }
            }

        }

    }

上面bugly的处理可以看到,如果在bugly之前设置的有Thread.setDefaultUncaughtExceptionHandler,那么bugly会临时保存起来,等到自己处理完,再接着让别的handler处理。。

如果,我们自己的异常处理类在bugly之后初始化的,那,就没bulgy那些故事了

我们的方案

我们用bugly是配合热更新的,实际没咋用,,我们有自己的方案,基于Cockroach实现的,如果不影响用户使用的,都不崩溃,除非页面新建时候渲染失败了,用户用不了,这会触发toast然后重启应用

我们收集的数据,上传到服务器,结合kibana平台图表化异常数据,便于分析

所以,我们基于Cockroach实现的有CrashHandler,用的也有bugly。。

重点就是自己的CrashHandler要放到bugly初始化之前

关于重启app

private void reloadApp() {
        try {
            Thread.sleep(5000);
            Intent intent = new Intent();
            intent.setClass(context, LoadActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            PendingIntent restartIntent = PendingIntent.getActivity(context, 9527, intent, PendingIntent.FLAG_CANCEL_CURRENT);
            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 100, restartIntent);
        } catch (Exception e) {

        }

        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
    }
参考文章:

https://blog.csdn.net/xy4_android/article/details/80846610

相关文章

  • 代码:android崩溃日志收集和处理

    用来处理android崩溃日志收集的代码,详情的使用请转:android崩溃日志收集和处理 第一个类 /** ...

  • Android收集程序崩溃日志

    前言 简述一下故事背景: 这篇文章是基于我们自己项目的方案完善的,,首先市面的技术方案都一样,所以,前半部分的基础...

  • Android捕获崩溃日志并发送JavaMail邮件

    Android机型太多Android机型更新太快崩溃日志无法收集 请参考Demo:SendMail-Demo 一、...

  • android崩溃日志收集和处理

    android的崩溃很常见,我们往往通过日志收集避免下次更新的重复出错。 android的崩溃发生后通常是...

  • 漫谈iOS Crash收集框架 (转载)

    来源:程序媛念茜的博客 Crash日志收集 为了能够第一时间发现程序问题,应用程序需要实现自己的崩溃日志收集服务,...

  • Android 崩溃日志收集

    原理:应用出现异常后,会由默认的异常处理器来处理异常,我们要做的就是把这个任务接管过来,自己处理异常,包括收集日志...

  • Xcode常用目录

    iOS程序员经常需要使用到的目录来调试程序 Xcode调试崩溃日志目录:可以查看Xcode帮我们收集运行崩溃信息 ...

  • Android 收集Native层崩溃日志

    环境 Mac、breakpad源码、minidump_stackwalk 步骤 NDK编写崩溃操作 通过cmake...

  • Cordova崩溃日志收集-Android篇

    一、需求 收集移动端app闪退日志 支持离线收集 开始准备使用腾讯的bugly来统一收集崩溃日志,但是存在一下特殊...

  • Android CrashHandler-收集崩溃日志

    前言 作为一个Android的开发,实现的功能即使经过测试同事的大量检测,在安卓各种手机型号和手机版本中,遇到闪退...

网友评论

    本文标题:Android收集程序崩溃日志

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