How can the app never crash

Good text recommended:
Author: slovenly

How to deal with program crashes in your project?

  • 当然是try住了

How to collect exception logs?

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

This classmate, did you not wake up? I asked about the abnormal log. It is an abnormality in your unknown state. Do you want to try to live the whole project?

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

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

Why does the program stop running if there is an exception?

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

If an exception occurs, will the program stop running?

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

In the case of unknown exceptions, is there a way to prevent the program from crashing?

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

Okay, go back and wait for the notice.


The above is an interview scene with added drama, which examines the degree of understanding of exception handling and the corresponding principle of Handler. Next, we analyze the problems one by one.


Will try catch affect the performance of the program?

First of all, try catch use, to narrow the scope as much as possible, when the try catch scope does not throw an exception, the performance impact is not big, but as long as the exception is thrown, the performance impact is doubled. Specifically, I conducted a simple test, respectively targeting the following three situations.

  • No try catch
  • There is try catch but no exception
  • There are both try catch and exception.
   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

Two conclusions can be drawn very clearly through the log

    1. When there is no exception, there is little effect with try and without try, both are 0 milliseconds.
    1. Performance drops 289 times when there are exceptions

Of course, the above test is an extreme case, the purpose is to enlarge the problem and face the problem, so in the future, try catch should narrow the scope as much as possible.


How to collect exception logs?

This question has been answered at the beginning of this article. Log collection can be achieved by inheriting Thread.UncaughtExceptionHandler and overriding uncaughtException(). 注意:需要在Application调用初始化

class MyCrashHandler : Thread.UncaughtExceptionHandler {
    override fun uncaughtException(t: Thread, e: Throwable) {
        Log.e("e", "Exception:" + e.message);
    }

    fun init() {
        Thread.setDefaultUncaughtExceptionHandler(this)
    }
}

At this point, you can do log collection and upload work in the uncaughtException() method.


Why does the program stop running if there is an exception?

This problem requires understanding of Android's exception handling mechanism. Before we set Thread.UncaughtExceptionHandler, the system will set one by default. For details, please refer toZygoteInit.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));
        ...
}

You can see that setDefaultUncaughtExceptionHandler() has been set in ZygoteInit.zygoteInit(), and ZygoteInit is the process of process initialization. Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

When the program has an exception, it will call back toKillApplicationHandler.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);
            }
        }

In direct observation finally, it is called Process.killProcess(Process.myPid()); System.exit(10);, which triggers the process termination logic, which also causes the program to stop running.


If an exception occurs, will the program stop running?

  • First of all, we need to determine what the concept of stopping operation is. Generally, there are two main situations.
    1. The program process exits (flash back as often referred to as the standard)
    1. The process of the program continues, but the click does not respond to the user event (targeting ANR)

The first problem is well understood, that is, the process of the above process exits. We mainly study the second case, the process continues but cannot respond to user events.

Here I want to popularize a little knowledge. Why can the Android system respond to various (man-made/non-man-made) events?

  • The concept of Handler is involved here. In fact, the operation of the entire operating system depends on the Handler Message Looper mechanism. All behaviors will be assembled into one Message message, and then Looper opens a for loop (endless loop) to take out one by one. The Message is handed over to the Handler for processing, and the Hander processing is completed to respond, and our behavior is also answered. The faster the impact, we will think that the system is smoother.

Handler mechanism described here, but more, I may have a look in need of this has been authorized to HONGYANG blog, it really makes a rude, one will ensure you thoroughly understand the entire process.

Learn about the Handler mechanism in 5 minutes, and the wrong usage scenarios of Handler

OK, let's come back and continue to talk about why the process continues, but it can't respond to user events? In fact, I have already mentioned it when I just described the Handler. That is 出现了异常,导致主线程的Looper已经退出循环了, how to respond to you after exiting the loop.

The above two situations have been analyzed clearly, so let's focus on how to solve these two problems, and let's fix the first one first.

How to prevent the process from exiting when an exception occurs? As mentioned above, the process exit is actually KillApplicationHandler.uncaughtException()called by default Process.killProcess(Process.myPid()); System.exit(10). Is it okay to prevent the exit and not to call KillApplicationHandler.uncaughtException()?

The approach is the same as we described at the beginning of this article, we only need to implement a Thread.UncaughtExceptionHandler class ourselves and initialize it in Application.

class MyCrashHandler : Thread.UncaughtExceptionHandler {
    override fun uncaughtException(t: Thread, e: Throwable) {
       Log.e("e", "Exception:" + e.message);
    }

    fun init() {
        Thread.setDefaultUncaughtExceptionHandler(this)
    }
}

The above logic sets the default UncaughtExceptionHandler of Thread, so it will be called when there is a crash ThreadGroup.uncaughtException()again, and then the exception will be handled by our own MyCrashHandler, so it will not exit the process.

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);
            }
        }
    }

The above logic also triggers the second stop operation, that is, although the process does not exit, the user clicks and does not respond. Since the user's unresponsiveness is caused by Looper exiting the loop, can't we solve the problem by starting the loop, just call it in Application onCreate() in the following way

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

What does it mean? We post a message to the Message queue through the Handler, this message is an infinite loop. Every time an exception occurs in loop(), loop() will be restarted to solve the unresponsive problem. However, the exception handling logic must be controlled here. Although the loop() is restarted indefinitely, it is not a long-term solution if the exception continues. This try is equivalent to trying to live the running logic of the entire App.

At the beginning, we also explained that the scope of try is as small as possible. Doesn't this approach maximize the scope of try? ? ? In fact, the main thing we have to strive for is to improve the quality of the code and reduce the probability of exceptions. This approach is only a remedy, and efficiency is exchanged for user experience.

To sum up, in fact, the essence of exception handling is to investigate the logical relationship between Handler, Looper mechanism, and the timing of application startup. As long as you know the corresponding relationship, you will have a thorough grasp of the methods of exception handling. It is recommended that you look at the Android source code. For example, this article about the startup process of Activity is enough


One small question, will the following code cause a crash when Looper.loop() is not started in a loop? why?

  Thread(Runnable {
          throw Exception()
     }).start()

Guess you like

Origin blog.csdn.net/u012165769/article/details/114044633