Android 渲染(三):掉帧监控

Android 渲染(一):刷新机制

Android 渲染(二):Choreographer、SurfaceFlinger、HWComposer

Android 渲染(三):掉帧监控


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该问题。
  • 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
    • 备注:需要系统权限
相关推荐
Mico181 小时前
MySQL 8.0.35 基于GTID 主从复制安装增强半同步复制
android·mysql·adb
Kapaseker1 小时前
你是不是还没用过 select?实战 Kotlin select
android·kotlin
__Witheart__2 小时前
适用于Android内核boot.img生成流程
android·rockchip
木易 士心2 小时前
Jetpack Compose 深度解析:初始组合与重组的底层奥秘
android
GitLqr11 小时前
Flutter 无障碍开发实战:玩转 Semantics 解决视障用户使用痛点
android·flutter·dart
雨白15 小时前
掌握 NestedScrolling 嵌套滑动:手写仿知乎折叠主页
android
Xzaveir16 小时前
别把所有“认证”都塞进 AuthService:实名、一键登录与号码身份的领域拆分
android·人工智能
BerrySen17817 小时前
KMP全栈开发:从Android到AI Agent的技术演进与实践
android·人工智能
AFinalStone18 小时前
Android 7系统休眠唤醒(一)电源管理架构全景图
android·powermanager·电源管理