最新消息:欢迎访问Android开发中文站!商务联系微信:loading_in

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

热点资讯 loading 710浏览 0评论

最近想给 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,这样能够更精准地处理异常。但是,随着版本更改,反射的限制会越来越强,另外,生命周期的标识也可以会更改。所以的话,理清思路就行了。

em…

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

github.com/tiann/FreeR…

转载请注明:Android开发中文站 » 制作一个永远不会崩溃的App

您必须 登录 才能发表评论!