5

制作一个永远不会崩溃的App

 2 years ago
source link: http://www.androidchina.net/11763.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近想给 App 加上一个崩溃后自动重启的功能,便去查找了下资料,毕竟有很长一段时间没弄过。

不搜不知道,一搜吓一跳,居然看到这库的实现思路,居然能够让 App 产生异常后,不会崩溃。

我当场的表情是这样的:

学完后,表情是这样的:

好了,废话不多说,赶紧进正文。


该库的 GitHub 地址为:

https://github.com/android-notes/Cockroach

其有两个版本,两个版本的思路是不一样的,但是能够实现同样的功能——App 不会崩溃。

在讲解它的原理之前,我们还是来简单复习下,如何在 App 崩溃的时候,捕获异常进行处理:

UncaughtExceptionHandler

写个异常捕获类:

MyUncaughtExceptionHandler.kt

object MyUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
    //获取系统默认的异常处理机制
    private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler =
        Thread.getDefaultUncaughtExceptionHandler()
    override fun uncaughtException(t: Thread, e: Throwable) {
        //自己处理
        Log.e("uncaughtException TAG", e.message.toString())
//        //交给系统默认处理
//        defaultUncaughtExceptionHandler.uncaughtException(t, e)
    }
}

在自定义 Application 中注册:

MyApplication.kt

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

在 AndroidManifest.xml 中注册:

        android:name=".MyApplication"

在类里面写个异常:

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

运行,崩溃!

2021-03-31 00:31:10.461 352-352/? E/TAG: For input string: "a"

Cockroach 1.0

它的实现思路其实很简单,其实就是 try catch 全部代码就行。

是不是很惊讶?

这要从 Handler 机制讲起了。

ActivityThread.javamain(String[] args)方法中,有这样一段代码:

    public static void main(String[] args) {
		···
		Looper.prepareMainLooper();
		···
		Looper.loop();
		···
	}

所以的话,主线程其实一直在 loop() 方法的死循环中,至于为什么是死循环?那就不用说了,必须是死循环,不是死循环的话,运行完 main() 方法,整个程序不就执行完了,那程序就被 cut 掉了。

至于在死循环中是如何启动 Activity 等操作的,是通过 Handler 发消息到 MessageQueue 中(这里默认讲的都是主线程),mainLooper 不断去 MessageQueue 获取消息并执行。所以的话,其实一切主线程的操作都是在 loop() 中执行的,既然如此,那么我们对其 try catch 不就行了吗?

    public static void main(String[] args) {
		···
		Looper.prepareMainLooper();
		···
   		try{
            Looper.loop()
        }catch (throwable: Throwable){
            Log.e("TAG", throwable.message.toString())
        }
		···
	}

为什么是 Throwable?因为 Exception 和 Error 都是继承 Throwable 的。

但是,这样还是不行,因为我们不可能去改源码。

好,那我们不改源码,直接在 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())
        }
    }
}

效果一样!

我们运行下:

2021-03-31 00:56:56.838 774-774/com.bjsdm.handlecrash E/try catch TAG: Unable to start activity ComponentInfo{com.bjsdm.handlecrash/com.bjsdm.handlecrash.MainActivity}: java.lang.NumberFormatException: For input string: "a"

注意看,是try catch TAG,不是uncaughtException TAG。说明是被 try catch 成功了,而非引起 App 崩溃。

Looper.loop() 里面是个死循环,在死循环里面再调用 Looper.loop() ,这样前一个就无效了,但是功能还是能正常运行。

但是我们还要优化下,毕竟一次异常就跳出 try catch 了。

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

这样还是不够,因为一旦调用 Looper.loop() 就进入死循环,这就有可能上一个 Looper.loop() 的消息还未执行完毕。

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

还可以提高一下优先级:

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

大致思路就是这样了,这样在主线程产生异常的话,是不会崩溃的。

注意,是主线程,所以的话,假如在子线程出现异常的话,还是不行,这时候就需要 UncaughtExceptionHandler 了。

当然,我也有一个笨的方法:

因为一切子线程的运行基本都是基于 Runnable 接口的,要不,我们 try catch 它的 run 方法?

可以考虑字节码插桩。不懂字节码插桩可以看看这篇文章自定义Gradle Plugin+字节码插桩

当然也可以考虑继承 Runnable 接口,后续使用 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()
}

        Thread(object : CaughtExceptionRunnable() {
            override fun myRun() {
                "a".toInt()
            }
        }).start()

2021-03-31 01:32:22.081 1137-1152/com.bjsdm.handlecrash E/ run try catch TAG: For input string: "a"

Cockroach 2.0

关于这个我就不多说了,因它的原理是通过反射获取主线程的 Handler,然后拦截它的生命周期的消息,然后进行 try catch,这样能够更精准地处理异常。但是,随着版本更改,反射的限制会越来越强,另外,生命周期的标识也可以会更改。所以的话,理清思路就行了。

该库类目前可以取消反射限制

github.com/tiann/FreeR…


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK