最近想给 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.java
的main(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...
该库类目前可以取消反射限制