Open yunshuipiao opened 4 years ago
[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 函数中主要做了几个事情,
上述中着重解释第三点。
首先我们知道,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()其实就是一个阻塞的方法,如果没有任务或没有主动退出,会一直在阻塞,一直等待主线程任务添加进来。
queue.next()
当队列有任务,就会打印信息 Dispatching to ...,然后就调用 msg.target.dispatchMessage(msg);执行任务,执行完毕就会打印信息 Finished to ...,我们就可以通过打印的信息来分析 ANR,一旦执行任务超过5秒就会触发系统提示ANR,但是我们对自己的APP肯定要更加严格,我们可以给我们设定一个目标,超过指定的时长就上报统计,帮助我们进行优化。
Dispatching to ...
msg.target.dispatchMessage(msg);
Finished to ...
如果主线程发生了异常,就会退出循环,意味着APP崩溃,所以我们我们需要进行try-catch,避免APP退出,我们可以在主线程再启动一个 Looper.loop() 去执行主线程任务,然后try-catch这个Looper.loop()方法,就不会退出。
Looper.loop()
如果是在 Activity 的 onCreate 中发生崩溃,那么就会黑屏。但是这种情况基本在开发过程中都能发现,所以能大幅降低我们应用的崩溃率。
在 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.next() 是一个带有阻塞的方法,只有退出或者有任务才会return,起阻塞的实现是使用Native层的 nativePollOnce() 函数,如果消息队列中没有消息存在nativePollOnce就不会返回,一直处于Native层等待状态。直到调用 quit() 退出或者调用 enqueueMessage(Message msg, long when) 有新的任务进来调用了Native层的nativeWake()函数,才会重新唤醒。
nativePollOnce()
quit()
enqueueMessage(Message msg, long when)
nativeWake()
性能优化的范围,大幅减少崩溃和优化函数的执行时间。
[TOC]
线上的崩溃是无法忍受的,那么有没有一种方法,将所有的崩溃进行 try catch,在开发过程中弹对话框提示,在线上环境上报进行分析。这样遇到崩溃时,结果就是用户的某些操作可能无效果,但相比崩溃,用户体验会好上很多。
思路
收到 https://github.com/jenly1314/NeverCrash 的启发。下面是具体的方法。
在 init 函数中主要做了几个事情,
上述中着重解释第三点。
首先我们知道,app 的运行是从 ActivityThread 的 main 函数开始执行的。
这里有一个 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() 方法中,会对消息的执行过程进行记录。
尤其是会打印出消息的执行时间。
因此我们可以根据时间差,来优化函数的执行。
MessageQueue
MessageQueue.next() 是一个带有阻塞的方法,只有退出或者有任务才会return,起阻塞的实现是使用Native层的
nativePollOnce()
函数,如果消息队列中没有消息存在nativePollOnce就不会返回,一直处于Native层等待状态。直到调用quit()
退出或者调用enqueueMessage(Message msg, long when)
有新的任务进来调用了Native层的nativeWake()
函数,才会重新唤醒。总结
性能优化的范围,大幅减少崩溃和优化函数的执行时间。