How do you handle crashes in your projects?

  • B: Try it

How are exception logs collected?

  • A utility class is written by hand, logged in a special way where things might go wrong, and then uploaded when the time is right

Dear classmate, are you not awake? What I am asking is the abnormal log, which is the abnormality of your unknown state. Do you want to try the whole project?

  • Ah, so that you can write a CrashHandler: Thread. UncaughtExceptionHandler, registered in the Application.

The log information is then collected in the overwritten uncaughtException(T: Thread, E: Throwable).

Why does an exception occur and the program stop running?

  • Should be the system ended the entire process

So there’s an exception, does the program have to stop running?

  • B: well... It should be... !

Is there a way to keep the program from crashing in the case of an unknown exception?

  • B: well... That should be ok...

All right, go back and wait for the call.


The above is a bit of a play-through of the interview scenario, which is to test the understanding of exception handling and how handlers work. Let’s go through the problems one by one.


Does try catch affect performance?

First, try catch is used. The scope should be as narrow as possible. When no exception is thrown in the scope of the try catch, the performance impact is not significant, but when an exception is thrown, the performance impact is multiplied. I conducted a simple test for the following three situations.

  • There is no try catch
  • There is a try catch but no exception
  • There are both try and catch and exceptions.
   fun test(a) {
        val start = System.currentTimeMillis()
        var a = 0
        for (i in 0.1000.) {
            a++
        }
        Log.d("timeStatistics"."noTry:" + (System.currentTimeMillis() - start))
    }

    fun test2(a) {
        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(a) {
        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
Copy the code

Two conclusions are obvious from the log

    1. If you have a try or no try, it doesn’t matter. It’s 0 milliseconds.
    1. When there was an exception, performance was reduced by 289 times

Of course, the above tests are extreme cases, and the purpose is to amplify the problem and face it head-on, so in the future try catch should be as narrow in scope as possible.


How do you collect the exception log?

The problem at the beginning of this article has given the answer can be through inheritance Thread. UncaughtExceptionHandler rewrite uncaughtException () implementation log collection. Note: Initialization needs to be called in the Application

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

    fun init(a) {
        Thread.setDefaultUncaughtExceptionHandler(this)}}Copy the code

Log collection and upload can be done in the uncaughtException() method.


Why does an exception occur and the program stop running?

This problem need to know about the exception handling mechanism of Android, we not set in the Thread. UncaughtExceptionHandler before the system will set a default, specific we reference the 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(a) {
        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(newKillApplicationHandler(loggingHandler)); . }Copy the code

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

When the program is an exception will callback to KillApplicationHandler. The 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); }}Copy the code

In the finally section, process.killprocess (process.mypid ()) is called; System.exit(10); , which triggers the process termination logic and causes the program to stop running.


If an exception occurs, must the program stop running?

  • First of all, we need to define the concept of shutdown. There are two main cases.
    1. The process exits (often called a flash exit).
    1. The program process persists, but clicking does not respond to user events (standard ANR)

The first problem is well understood, which is the process exit from our process above. We will focus on the second case, where the process persists but cannot respond to user events.

Here’s a quick point to make: why does Android respond to all kinds of (artificial/non-artificial) events?

  • The concept of Handler is involved here. In fact, the operation of the entire operating system depends on the mechanism of Handler Message Looper. All behaviors are assembled into one Message Message. Then Looper opens a for loop (an infinite loop) and takes out one Message and gives it to Handler for processing. After Hander completes the processing and responds, our behavior will be answered. The faster the impact is, the smoother the system will be.

I won’t describe the Handler mechanism much here, if necessary, you can read my blog which has been authorized to Hongyang, it is really a rough way, you will be sure to understand the whole process in a few minutes.

5 minutes Learn about the Handler mechanism and usage scenarios

OK, let’s go back to the question: why does the process survive but cannot respond to user events? Actually, I just described Handler. The Looper of the main thread has already quit the loop. How can I respond to you if the Looper of the main thread has already quit the loop?

The above two cases are clear analysis, then let’s focus on how to solve these two problems, the first whole.

If an exception occurs, how do I prevent the process from exiting? Has said above, Process exits, actual it is the default KillApplicationHandler. UncaughtException () call the Process. The killProcess (Process. MyPid ()); System. The exit (10). Prevent quit, don’t let call KillApplicationHandler. UncaughtException () is not ok?

Practice like we described in the beginning of this article, we only need to implement a Thread UncaughtExceptionHandler class, and in Application initialization

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

    fun init(a) {
        Thread.setDefaultUncaughtExceptionHandler(this)}}Copy the code

More than set up a Thread gets a default logic UncaughtExceptionHandler, so collapse again invoked to ThreadGroup. The uncaughtException (), to handle exceptions will be to our own MyCrashHandler implementation, So you’re not going to 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(! (einstanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" "); e.printStackTrace(System.err); }}}Copy the code

This logic also triggers a second kind of stop, where the user clicks no response even though the process does not exit. Since Looper exits the loop, we can start the loop to solve the problem. We just need to call Application onCreate () in the following way

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

What does that mean? We post a Message to the Message queue via Handler. This Message is an infinite loop. Every time loop() is abnormal, loop() is restarted to solve the unresponsive problem. However, it is important to control the exception handling logic here. Even if loop() is restarted indefinitely, it is not a permanent solution if it is always abnormal. This try is equivalent to the try of the entire App running logic.

The scope of the try is as small as possible. In fact, what we are trying to do is to improve the quality of the code and reduce the probability of exceptions, which is just a remedy, trading efficiency for user experience.

To sum up, in fact, the essence of exception handling is to investigate the Handler, Looper mechanism, the timing of Application startup and other logical relationships, as long as you know the corresponding relationship will completely master the method of exception handling, or recommend you to read more Android source code. For example, the Activity startup process is sufficient for this article


One small question: does the following code crash without looper.loop () being started? Why is that?

  Thread(Runnable {
          throw Exception()
     }).start()
Copy the code