Potato
Potato copied to clipboard
Android:How to dramatically reduce crashes
[TOC]
线上的崩溃是无法忍受的,那么有没有一种方法,将所有的崩溃进行 try catch,在开发过程中弹对话框提示,在线上环境上报进行分析。这样遇到崩溃时,结果就是用户的某些操作可能无效果,但相比崩溃,用户体验会好上很多。
思路
收到 https://github.com/jenly1314/NeverCrash 的启发。下面是具体的方法。
import android.app.Activity
import android.app.AlertDialog
import android.app.Application
import android.content.pm.ApplicationInfo
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import java.io.PrintWriter
import java.io.StringWriter
object CrashUtil {
var resumeActivity: Activity? = null
lateinit var application: Application
var debug = false
fun init(application: Application) {
this.application = application
debug = (application.applicationInfo.flags
and ApplicationInfo.FLAG_DEBUGGABLE) != 0
// 获得当前的 Activity
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks() {
override fun onActivityResumed(activity: Activity?) {
resumeActivity = activity
}
})
// 对主线程阻塞,通过下面的 loop 取出消息执行。
// 捕获主线程的异常
Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
//捕获异常处理
caughtException(Looper.getMainLooper().thread, e)
}
}
}
// 捕获子线程的异常
Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler())
}
private class UncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread?, e: Throwable?) {
caughtException(t, e)
}
}
private fun caughtException(t: Thread?, e: Throwable?) {
if (debug) {
val sw = StringWriter()
val pw = PrintWriter(sw)
e?.printStackTrace(pw)
val message = "线程信息: \n${t?.name}\n" +
"堆栈信息:\n $sw"
Handler(Looper.getMainLooper()).post {
AlertDialog.Builder(resumeActivity)
.setTitle("发生崩溃,信息如下")
.setMessage(message)
.setPositiveButton("确认", null)
.setCancelable(false)
.show()
}
} else {
// report
}
}
private open class ActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity?) {
}
override fun onActivityResumed(activity: Activity?) {
}
override fun onActivityStarted(activity: Activity?) {
}
override fun onActivityDestroyed(activity: Activity?) {
}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity?) {
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
}
}
}
在 init 函数中主要做了几个事情,
- 判断是否处于 debug 模式。
- 注册 activity 生命周期的监听器,获取 onResume 状态的 activity。
- 接管主线程的所有的消息执行,进行 try catch。
- 捕获所有非主线程的异常。
上述中着重解释第三点。
首先我们知道,app 的运行是从 ActivityThread 的 main 函数开始执行的。
public static void main(String[] args) {
...
Environment.initForCurrentUser();
...
Looper.prepareMainLooper();
.....
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// loop
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这里有一个 Looper.loop()。
进一步分析,这里会取出 MessageQueue 的下一个消息,如果消息为空,那么函数结束执行。这里就会有一个疑问,当 app 不执行任何操作时,但为什么 app 还在运行。
实际上queue.next()
其实就是一个阻塞的方法,如果没有任务或没有主动退出,会一直在阻塞,一直等待主线程任务添加进来。
当队列有任务,就会打印信息 Dispatching to ...
,然后就调用 msg.target.dispatchMessage(msg);
执行任务,执行完毕就会打印信息 Finished to ...
,我们就可以通过打印的信息来分析 ANR,一旦执行任务超过5秒就会触发系统提示ANR,但是我们对自己的APP肯定要更加严格,我们可以给我们设定一个目标,超过指定的时长就上报统计,帮助我们进行优化。
如果主线程发生了异常,就会退出循环,意味着APP崩溃,所以我们我们需要进行try-catch,避免APP退出,我们可以在主线程再启动一个 Looper.loop()
去执行主线程任务,然后try-catch这个Looper.loop()方法,就不会退出。
缺点
如果是在 Activity 的 onCreate 中发生崩溃,那么就会黑屏。但是这种情况基本在开发过程中都能发现,所以能大幅降低我们应用的崩溃率。
Message 的执行时间
在 Looper.loop() 方法中,会对消息的执行过程进行记录。
尤其是会打印出消息的执行时间。
因此我们可以根据时间差,来优化函数的执行。
public static void loop() {
...
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
if (debug) {
Looper.getMainLooper().setMessageLogging {
if (it.startsWith(">>>>> Dispatching to Handler")) {
startTs = System.currentTimeMillis()
msg = it
} else if (it.startsWith("<<<<< Finished to Handler")) {
val duration = System.currentTimeMillis() - startTs
if (duration > 200) {
Logger.d("主线程执行耗时过长\nduration:$duration\n msg:$msg")
}
}
}
}
##MessageQueue
MessageQueue.next() 是一个带有阻塞的方法,只有退出或者有任务才会return,起阻塞的实现是使用Native层的 nativePollOnce()
函数,如果消息队列中没有消息存在nativePollOnce就不会返回,一直处于Native层等待状态。直到调用 quit()
退出或者调用 enqueueMessage(Message msg, long when)
有新的任务进来调用了Native层的nativeWake()
函数,才会重新唤醒。
总结
性能优化的范围,大幅减少崩溃和优化函数的执行时间。