Recently, I wanted to add a function of automatic restart after the crash to the App, so I went to search the information, after all, it has not been done for a long time.

I was surprised to see the implementation idea of this library, which can make the App produce abnormalities without crashing.

My face looked like this:

After learning, the expression is like this:

All right, without further ado, let’s get to the text.


The GitHub address for this library is:

https://github.com/android-notes/Cockroach
Copy the code

There are two versions, both of which have different ideas but do the same thing — the App doesn’t crash.

Before explaining how it works, let’s briefly review how to catch exceptions when the App crashes:

UncaughtExceptionHandler

Write an exception catching class:

MyUncaughtExceptionHandler. Kt:

object MyUncaughtExceptionHandler : Thread. UncaughtExceptionHandler {/ / get the default system the exception handling mechanism of private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() override fun uncaughtException(t: Thread, e: Log.e("uncaughtException TAG", E.m essage. ToString ()) / / / / to the system default processing / / defaultUncaughtExceptionHandler uncaughtException (t, e)}}Copy the code

Registering in a custom Application:

MyApplication. Kt:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Thread.setDefaultUncaughtExceptionHandler(MyUncaughtExceptionHandler)
    }
}
Copy the code

Register in Androidmanifest.xml:

        android:name=".MyApplication"
Copy the code

Write an exception inside the class:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        "a".toInt()
    }
}
Copy the code

Run, crash!

The 2021-03-31 00:31:10. 461, 352-352 /? E/TAG: For input string: "a"Copy the code

Cockroach 1.0

It’s actually a very simple implementation, and it’s basically just try catch code.

Are you surprised?

It starts with the Handler mechanism.

In activityThread.java’s main(String[] args) method, there is this code:

Public static void main(String[] args) {··· prepareMainLooper(); ... stars. The loop (); ...}Copy the code

So the main thread is always in the loop of the loop() method. If the main() method is not executed, the program will be cut.

The Handler sends a message to the MessageQueue. The mainLooper keeps going to the MessageQueue to retrieve the message and execute it. All main thread operations are performed in loop(), so why not just try and catch it?

Public static void main(String[] args) {··· prepareMainLooper(); ... try {stars. The loop ()} the catch (throwable: throwable) {the e (" TAG ", throwable. Message. The toString ())}...}Copy the code

Why Throwable? Exceptions and errors both inherit from Throwable.

However, this still doesn’t work because we can’t change the source code.

MyApplication = MyApplication = MyApplication = MyApplication

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Thread.setDefaultUncaughtExceptionHandler(MyUncaughtExceptionHandler)
        try{
            Looper.loop()
        }catch (throwable: Throwable){
            Log.e("try catch TAG", throwable.message.toString())
        }
    }
}
Copy the code

Same effect!

Let’s run:

The 2021-03-31 00:56:56. 838, 774-774 / com. BJSDM. Handlecrash E/try catch the TAG: Unable to start activity ComponentInfo{com.bjsdm.handlecrash/com.bjsdm.handlecrash.MainActivity}: java.lang.NumberFormatException: For input string: "a"Copy the code

UncaughtException = uncaughtException = uncaughtException = uncaughtException = uncaughtException A try catch succeeds instead of causing the App to crash.

Why is that?

Looper.loop() is an infinite loop. If you call looper.loop () again in an infinite loop, the previous one will not work, but the function will still work.

But we’re going to have to optimize it a little bit, because we’re going to get out of a try catch once.

        while (true){
            try{
                Looper.loop()
            }catch (throwable: Throwable){
                Log.e("try catch TAG", throwable.message.toString())
            }
        }
Copy the code

This is not enough, because once looper.loop () is called, an infinite loop is created, which makes it possible that the last looper.loop () message has not yet been executed.

        Handler(Looper.getMainLooper()).post {
            while (true) {
                try {
                    Looper.loop()
                } catch (throwable: Throwable) {
                    Log.e("try catch TAG", throwable.message.toString())
                }
            }
        }
Copy the code

You can also increase the priority:

        Handler(Looper.getMainLooper()).postAtFrontOfQueue() {
            while (true) {
                try {
                    Looper.loop()
                } catch (throwable: Throwable) {
                    Log.e("try catch TAG", throwable.message.toString())
                }
            }
        }
Copy the code

So that’s the general idea, so that if you raise an exception on the main thread, it won’t crash.

If an exception occurs in the child thread, UncaughtExceptionHandler is required.

Of course, I also have a stupid method:

Since all child threads run basically on the Runnable interface, how about we try and catch its run method?

Consider bytecode staking. If you don’t understand bytecode staking, you can customize Gradle Plugin+ bytecode staking

Of course, you can also consider inheriting the Runnable interface, and then use the encapsulated one when using Runnable:

abstract class CaughtExceptionRunnable : Runnable {
    override fun run() {
        try {
            myRun();
        } catch (throwable: Throwable) {
            Log.e(" run try catch TAG", throwable.message.toString())
        }
    }
    abstract fun myRun()
}
Copy the code
        Thread(object : CaughtExceptionRunnable() {
            override fun myRun() {
                "a".toInt()
            }
        }).start()
Copy the code
The 2021-03-31 01:32:22. 081, 1137-1152 / com. BJSDM. Handlecrash E/run the try catch TAG: For input string: "a"Copy the code

Cockroach 2.0

I won’t say much more about this, because the idea is to get the main thread Handler by reflection, and then intercept the message for its lifetime, and then try catch, so that you can handle exceptions more precisely. However, as versions change, reflection becomes more restrictive, and the identity of the lifecycle can change. So, just clear your head.

em…

The library class can now remove reflection restrictions

Github.com/tiann/FreeR…