性能优化基础
性能优化工具
TraceView
[[TraceView]]
Systrace
[[Systrace入门]]
Profiler
Android Studio 自带工具
BTrace
AGP 8.0 不支持,AGP 8.0+ 移除了 Transform 的 API
Perfetto(Android9 及以上)
LeakCanary
MAT/MMAT
KOOM
Matrix 的 ResouceCanary
性能监控
监控应用卡顿?
Looper Printer 替换方案
很常见的方案,使用系统方法 setMessageLogging 替换掉主线程 Looper 的 Printer 对象,通过计算 Printer 打印日志的时间差,来拿到系统 dispatchMessage 方法的执行时间
1
2
3
Looper.getMainLooper().setMessageLogging(str -> {
// 计算相邻两次日志时间间隔
});
这种方式的优点就是:实现简单,不会漏报,
缺点就是,一些类型的卡顿无法被监控到。
但有一些卡顿仍然无法监控到:
- **queue.next() 卡顿 **
nativePollOnce 方法很重要,除了主线程空闲时会阻塞在这里,view 的 touch 事件也都是在这里被处理的。所以如果应用内包含了很多自定义 view,或处理了很多 onTouch 事件,就很难接受了。不仅这样,Native Message 也会卡在 nativePollOnce 方法内,所以同样无法监控到。
- IdleHandler 卡顿
在 MessageQueue 消息列表都执行完的时候,会执行 idleHandlers 中的消息,而 idleHandler 也有可能产生卡顿,并且 Looper printer 无法监控到,我们可以反射 MessageQueue 中的 mIdleHandlers 对象,这个变量保存了所有将要执行的 IdleHandler,我们只需要把 ArrayList 类型的 mIdleHandlers,通过反射,替换为 MyArrayList,在我们自定义的 MyArrayList 中重写 add 方法,再将我们自定义的 MyIdleHandler 添加到 MyArrayList 中,就完成了 “ 偷天换日 “。从此之后 MessageQueue 每次执行 queueIdle 回调方法,都会执行到我们的 MyIdleHandler 中的的 queueIdle 方法,就可以在这里监控 queueIdle 的执行时间了
- SyncBarrier 卡顿
SyncBarrier 卡顿表现为同步消息无法执行,这种情况我们可以通过去获取队列中 message.target = null 的对象(同步屏障),判断该对象的 when 属性是否存在超时,如果该同步屏障已经存在很久了,我们可以再进一步验证是否是 SyncBarrier 发生了泄露,为什么需要进一步验证, 因为别的卡顿情况也会导致 SyncBarrier 一直存在队列中,我们可以分别发送一个异步消息和一个同步消息,如果异步消息被消费了而同步消息未消费,说明 SyncBarrier 发生了泄露,此时我们甚至可以尝试去将该 SyncBarrier 从 MessageQueue 中 remove 掉。
基于 WatchDog
参考系统的 WatchDog 原理,我们启动一个卡顿检测线程,该线程定期的向 UI 线程发送一条延迟消息,执行一个标志位加 1 的操作,如果规定时间内,标志位没有变化,则表示产生了卡顿。如果发生了变化,则代表没有长时间卡顿,我们重新执行延迟消息即可。
该方案并不能完全检测到所有的设定条件内的卡顿问题,但可以配合 Handler Printer 替换的方案来实现交叉覆盖,基本可以满足我们的需求
帧率监控
业界都使用 Choreographer 来监控应用的帧率(通过 Choreographer.FrameCallback 回调来实现帧率监控)。跟卡顿不同的是,需要排除掉页面没有操作的情况,我们应该只在 界面存在绘制 的时候才做统计。
那么如何监听界面是否存在绘制行为呢?可以通过 addOnDrawListener 实现。
getWindow().getDecorView().getViewTreeObserver().addOnDrawListener
线程监控
- 插桩将无名字的 Thread 添加名字,在监控的时候能得到更有效的信息
- Java 线程监控,插桩 thread 构造
- Native 线程监控,native hook
pthread_create()
- 线程收敛
- 线程栈帧优化,默认 1 M
- 三方的插件:Booster, ByteX