Android 渲染(二):Choreographer、SurfaceFlinger、HWComposer
WatchDog
- 开启一个子线程不断向UI线程发送Message,每隔一段时间检查一次刚刚发送的Message是否被处理,如果没有被处理,则说明这段时间主线程被卡住了。
- 轮询的时间间隔越小,对性能的负面影响就越大,而时间间隔选择的越大,漏报的可能性也就越大。
Looper Printer
- 替换主线程Looper的Printer,从而监控dispatchMessage的执行时间。(在Android源码中,主线程Looper也会根据执行dispatchMessage的时间来判断是否有卡顿,有则会打印一些日志)
- 无论是通过反射替换Looper的mLogging还是通过setMessageLogging设置printer,我们只需要替换主线程Looper的printer对象,通过计算执行dispatchMessage方法之后和之前打印字符串的时间的差值,就可以拿到到dispatchMessage方法执行的时间。而大部分的主线程的操作最终都会执行到这个dispatchMessage方法中。
- 某些类型的卡顿无法被监控到
- View的TouchEvent中的卡顿无法监控到
- Touch事件最终是通过server端的InputDispatcher线程传递给Client端的UI线程的,并且使用的是一对Socket进行通讯的
- 通过PLT Hook,成功hook到libinput.so中的recvfrom和sendto方法,使用我们自己的方法进行替换。当调用到了recvfrom时,说明我们的应用接收到了Touch事件,当调用到了sendto时,说明这个Touch事件已经被成功消费掉了,当两者的时间相差过大时即说明产生了一次Touch事件的卡顿。
- IdleHandler的queueIdle()回调方法也是无法监控到
- 反射MessageQueue中的mIdleHandlers,替换为MyArrayList,在我们自定义的MyArrayList中重写add方法,再将我们自定义的MyIdleHandler添加到MyArrayList中。MessageQueue每次执行queueIdle回调方法,都会执行到我们的MyIdleHandler中的的queueIdle方法,就可以在这里监控queueIdle的执行时间了。
- SyncBarrier(同步屏障)的泄漏同样无法被监控到
- 不断轮询主线程Looper的MessageQueue的mMessage。SyncBarrier本身也是一种特殊的Message,其特殊在它的target是null。通过反射mMessage,发现当前的Message的target为null,并且通过这个Message的when发现其已经存在很久了,这个时候我们合理怀疑产生了SyncBarrier的泄漏(但还不能完全确定,因为如果当时因为其他原因导致主线程卡死,也可能会导致这种现象),然后再发送一个同步消息和一个异步消息,如果异步消息被处理了,但是同步消息一直无法被处理,这时候就说明产生了SyncBarrier的泄漏。如果激进一些,这个时候我们甚至可以反射调用MessageQueue的removeSyncBarrier方法,手动把这个SyncBarrier移除掉,从而从错误状态中恢复。
- 无法溯源问题究竟是哪个View导致的。如果发现某个场景下该问题确实较为严重,可以考虑使用插桩或者Java hook在测试环境下debug该问题。
- View的TouchEvent中的卡顿无法监控到
- BlockCanary 基于 Looper 的性能监控
Choreographer
- doFrame函数中,Vsync信号来时会标记start_time(即intentedFrameTime),执行doFrame函数时会记录end_time(执行的doFrame的时间),这两个时间差就是 Vsync 处理时延,也就是丢帧
- doFrame计算都是前一帧的丢帧情况。此度量方式只能记录UI Thread的丢帧,会导致部分丢帧未被统计到。
- FrameInfo 来负责记录帧的绘制信息,doFrame 执行的时候,会把每一个关键节点的绘制时间记录下来
- google提供gfxinfo来记录某个app每帧的耗时落在那个区间,这部分内容会记录的文件在JankTracker.cpp文件,当每次queueBuffer()之后,都会调用finishFrame()方法对当前这一帧的耗时进行统计,不过此方式仅支持统计HW Rendering的应用。
- 丢帧的时间计算方式:FrameCompleted - IntendedVsync
- 利用Choreographer 提供的FrameCallback中的回调
- 自定义FrameCallback并实现FrameCallback接口
- 在合适的位置调用接口
- 利用FrameInfo进行监控,android 提供gfxinfo可获取丢帧信息
- adb shell dumpsys gfxinfo packageName framestats
- 利用android Choreographer类中提供的自身丢帧的计算逻辑
- 默认SKIPPED_FRAME_WARNING_LIMIT 是30,原则上应用可以hook修改此值来监控应用的场景丢帧情况
SurfaceFlinger
- 通常我们通过 Systrace 判断应用是否掉帧的时候,一般是直接看 SurfaceFlinger 部分
- SurfaceFlinger 的主线程在每个 Vsync-SF 的时候是否没有合成
- 检查发现没有可用的 Buffer 而没有合成操作
- 被其他的工作占用(比如截图、HWC 等)
- 在等 presentFence
- 在等 GPU fence
- 如果有合成操作,那么需要看 你关心的 App 的 可用 Buffer 个数是否正常:如果 App 此时可用 Buffer 为 0,那么看 App 端为何没有及时 queueBuffer(这就一般是应用自身的问题了),因为 SurfaceFlinger 合成操作触发可能是其他的进程有可用的 Buffer
- 利用 SurfaceFlinger 进行监控
- adb shell dumpsys SurfaceFlinger --latency packageName
- 利用 SurfaceFlinger PageFlip 机制进行监控
- 使用 : adb service call SurfaceFlinger 1013
- 备注:需要系统权限