zoukankan      html  css  js  c++  java
  • App怎么做才能永不崩溃

    你们项目中怎么处理程序崩溃?

    • 当然是try住了

    那异常日志怎么收集呢?

    • 一般会手写一个工具类,然后在可能出问题的地方通过特殊的方法进行记录日志,然后找时机上传

    这位同学,你是不是没有睡醒,我问的是异常日志,是你未知状态的异常,难道你要把整个项目try住?

    • 这样啊,那可以写一个CrashHandler : Thread.UncaughtExceptionHandler,在Application中注册。

    然后在重写的uncaughtException(t: Thread, e: Throwable)中收集日志信息。

    为什么出现异常了,程序会停止运行呢?

    • 应该是系统结束了整个程序进程吧

    那出现异常了,程序一定会停止运行么?

    • 嗯...应该会.....吧

    在未知异常的情况下,有办法让程序不崩溃么?

    • 嗯...应该可以吧...

    好了,回去等通知吧.


    以上是一段加过戏的面试场景,考察的是对异常处理,以及Handler对应原理的了解程度。 接下来我们一个一个分析问题。


    try catch会影响程序运行性能么?

    首先,try catch使用,要尽可能的缩小作用域,当try catch作用域内未抛出异常时,性能影响并不大,但是只要抛出了异常就对性能影响是成倍的。 具体我进行了简单的测试,分别针对了以下三种情况。

    • 没有try catch
    • 有try catch但是没有异常
    • 既有try catch又有异常。
       fun test() {
            val start = System.currentTimeMillis()
            var a = 0
            for (i in 0..1000) {
                a++
            }
            Log.d("timeStatistics", "noTry:" + (System.currentTimeMillis() - start))
        }
    
        fun test2() {
            val start = System.currentTimeMillis()
            var a = 0
            for (i in 0..1000) {
                try {
                    a++
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }
            }
            Log.d("timeStatistics", "tryNoCrash:" + (System.currentTimeMillis() - start))
        }
    
        fun test3() {
            val start = System.currentTimeMillis()
            var a = 0
            for (i in 0..1000) {
                try {
                    a++
                    throw java.lang.Exception()
                } catch (e: java.lang.Exception) {
                    e.printStackTrace()
                }
            }
            Log.d("timeStatistics", "tryCrash:" + (System.currentTimeMillis() - start))
        }
    
         2021-02-04 17:10:27.823 22307-22307/com.ted.nocrash D/timeStatistics: noTry:0
         2021-02-04 17:10:27.823 22307-22307/com.ted.nocrash D/timeStatistics: tryNoCrash:0
         2021-02-04 17:10:28.112 22307-22307/com.ted.nocrash D/timeStatistics: tryCrash:289
    复制代码
    

    通过日志可以非常明显的得出两个结论

      1. 无异常时,有try与无try影响不大,都是0毫秒。
      1. 有异常时候性能下降了289 倍

    当然,以上测试为极端情况,目的是放大问题,直面问题,所以以后try catch要尽可能的缩小作用域。


    异常日志要怎么收集呢?

    这个问题在本文开头已经给出了答案,可以通过继承Thread.UncaughtExceptionHandler并重写uncaughtException()实现日志收集。 注意:需要在Application调用初始化

    class MyCrashHandler : Thread.UncaughtExceptionHandler {
        override fun uncaughtException(t: Thread, e: Throwable) {
            Log.e("e", "Exception:" + e.message);
        }
    
        fun init() {
            Thread.setDefaultUncaughtExceptionHandler(this)
        }
    }
    复制代码
    

    此时可以在uncaughtException()方法中做日志收集和上传工作。


    为什么出现异常了,程序会停止运行呢?

    这个问题需要了解下Android 的异常处理机制,在我们未设置Thread.UncaughtExceptionHandler之前,系统会默认设置一个,具体我们参考下ZygoteInit.zygoteInit()

        public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
                String[] argv, ClassLoader classLoader) {
            if (RuntimeInit.DEBUG) {
                Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
            }
    
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
            RuntimeInit.redirectLogStreams();
    
            RuntimeInit.commonInit();
            ZygoteInit.nativeZygoteInit();
            return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
                    classLoader);
        }
    
     protected static final void commonInit() {
            if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
    
            /*
             * set handlers; these apply to all threads in the VM. Apps can replace
             * the default handler, but not the pre handler.
             */
            LoggingHandler loggingHandler = new LoggingHandler();
            RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
            Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
            ...
    }
    复制代码
    

    可以看到在ZygoteInit.zygoteInit()中已经设置了setDefaultUncaughtExceptionHandler(),而ZygoteInit是进程初始化的过程。 Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

    当程序出现议程会回调到KillApplicationHandler.uncaughtException(Thread t, Throwable e)

       @Override
            public void uncaughtException(Thread t, Throwable e) {
                try {
                    ensureLogging(t, e);
    
                    // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                    if (mCrashing) return;
                    mCrashing = true;
    
                    // Try to end profiling. If a profiler is running at this point, and we kill the
                    // process (below), the in-memory buffer will be lost. So try to stop, which will
                    // flush the buffer. (This makes method trace profiling useful to debug crashes.)
                    if (ActivityThread.currentActivityThread() != null) {
                        ActivityThread.currentActivityThread().stopProfiling();
                    }
    
                    // Bring up crash dialog, wait for it to be dismissed
                    ActivityManager.getService().handleApplicationCrash(
                            mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(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.
                    Process.killProcess(Process.myPid());
                    System.exit(10);
                }
            }
    复制代码
    

    直接观察finally中,调用了 Process.killProcess(Process.myPid()); System.exit(10);,触发了进程结束逻辑,也就导致了程序停止运行。


    如果出现了异常,程序一定会停止运行么?

    • 首先我们需要定一下停止运行的概念是啥,一般主要有两种情况。
      1. 程序进程退出(对标常说的闪退)
      1. 程序进程存续,但是点击无响应用户事件(对标ANR)

    第一个问题很好理解,就是我们上述过程的进程退出,我们主要研究第二种情况,进程存续但是无法响应用户事件。

    这里我先要普及个小知识点,Android系统为啥能响应来自各种(人为/非人为)的事件?

    • 这里就要涉及Handler的概念了,其实整个操作系统的运行全部依赖Handler Message Looper这套机制,所有的行为全部会组装成一个个的Message消息,然后Looper开启一个for循环(死循环)取出一个个Message交给Handler处理,而Hander处理完成进行了响应,我们的行为也就得到了应答,影响的越快我们就会认为系统越流畅。

    这里不过多描述Handler机制,有需要的可以看下我这篇已经授权给 鸿洋 的博客,那真叫一个粗暴,保证你一会就搞明白整个流程。

    5分钟了解Handler机制,Handler的错误使用场景

    OK,我们回来继续扯为啥进程存续,却无法响应用户的事件呢?其实刚刚描述Handler的时候已经说到了。 就是出现了异常,导致主线程的Looper已经退出循环了,都退出循环了还怎么响应你。

    以上2种情况分析清楚了,那我们着重说下怎么解决这两种问题,先整第一种。

    出现异常,怎么防止进程退出? 上述已经说到,进程退出,实际是默认的KillApplicationHandler.uncaughtException()调用了Process.killProcess(Process.myPid()); System.exit(10)。 防止退出,不让调用KillApplicationHandler.uncaughtException()不就可以了?

    做法跟我们本文开头描述的一样,我们只需要自己实现一个Thread.UncaughtExceptionHandler类,并在Application初始化就可以了

    class MyCrashHandler : Thread.UncaughtExceptionHandler {
        override fun uncaughtException(t: Thread, e: Throwable) {
           Log.e("e", "Exception:" + e.message);
        }
    
        fun init() {
            Thread.setDefaultUncaughtExceptionHandler(this)
        }
    }
    复制代码
    

    以上逻辑设置了Thread默认的UncaughtExceptionHandler,所以再出现崩溃的时候会调用到 ThreadGroup.uncaughtException(),再处理异常就会到我们自己实现的MyCrashHandler了,所以也就不会退出进程了。

    public void uncaughtException(Thread t, Throwable e) {
            if (parent != null) {
                parent.uncaughtException(t, e);
            } else {
                Thread.UncaughtExceptionHandler ueh =
                    Thread.getDefaultUncaughtExceptionHandler();
                if (ueh != null) {
                    ueh.uncaughtException(t, e);
                } else if (!(e instanceof ThreadDeath)) {
                    System.err.print("Exception in thread ""
                                     + t.getName() + "" ");
                    e.printStackTrace(System.err);
                }
            }
        }
    复制代码
    

    以上逻辑同时就触发了第二种停止运行,也就是虽然进程没有退出,但是用户点击无响应。 既然用户无响应是Looper退出循环导致的,那我们启动循环不就解决了么,只需要通过以下方式,在Application onCreate()调用

       Handler(mainLooper).post {
                while (true) {
                    try {
                        Looper.loop()
                    } catch (e: Throwable) {
                    }
                }
            }
    复制代码
    

    这是什么意思?我们通过Handler往Message队列post一个消息,这个消息是一个死循环。 每次loop()出现了异常,都会重新启动loop()也就解决了无响应的问题。但是这里一定要控制好异常处理逻辑,虽然无限重启loop(),但是如果一直异常也不是长久之计,这个try相当于try住了整个App的运行逻辑。

    开头我们也说明了try的作用域尽可能小,这种做法岂不是把try的作用域整到了最大??? 其实我们要努力的主要还是提高代码质量,降低异常出现的概率,这种做法只是补救,用效率换取了用户体验。

    总结一下,其实异常处理本质考察的就是Handler,Looper机制,Application启动的时机等逻辑的相互关系,只要知道对应关系也就彻底整掌握了异常处理的手法,还是推荐大家多看Android源码。

    本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

    作者:吊儿郎当
    链接:https://juejin.cn/post/6925702262102687758

  • 相关阅读:
    修改MyEclipse工作空间
    Servlet
    Java虚拟机类加载机制
    编译执行和解释执行
    awt和swing
    构造函数
    小知识点
    [剑指Offer]42-连续子数组的最大和/ [LeetCode]53. 最大子序和
    [剑指Offer]40-最小的k个数
    [剑指Offer]47-礼物的最大价值(DP)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/14383901.html
Copyright © 2011-2022 走看看