小米电池休眠自动解除打开后盖 小米电池休眠自动解除( 四 )


小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
卡顿场景上面两次测试都接近理想情况,即整个 Render Loop 执行几乎没有延迟与卡顿 。但是现实中应用的运行总是有着各种各样的或大或小的卡顿问题 。
为了验证更接近现实情况下,DisplayLink 和 VSync 信号之间的关系,在连续滑动的情况下笔者人为加入了一个 20ms 的微小卡顿进行测试:
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
上图中可以看到,ProMotion 屏幕很好的处理了这次卡顿,由于三缓冲机制的存在,再 Render Loop 渲染 Surface 4 卡顿期间,通过改变 VSync 间隔,系统尝试将缓冲区中的 Surface 283 与 Surface 250 延迟上屏,尽量缩短了用户看到静止画面的时长 。
随后,主线程恢复执行,可以看到 DisplayLink 的回调频率很快恢复至卡顿前的高水平 。而此时 VSync 信号由于前述卡顿减缓机制的存在频率其实有所降低 。此时二者频率并不吻合 。
这和之前播放慢速动画/慢速滑动的情况很相似,由于卡顿加上缓冲机制的存在导致短时间内系统将屏幕的刷新频率降低,但在 CPU 侧依然维持了 DisplayLink 的高速回调,满足了使用方对 preferredFrameRateRange 这一 API 的设置 。
为了进一步分析了这种机制的本质,笔者接下来会尝试逆向分析 iOS 15 中的系统库相关实现的改动 。
逆向分析DisplayLink 驱动方式的变化在 CADisplayLink 回调 *** 上设置断点,分别在 iOS 14 和 15 ProMotion 设备上运行,可以得到:
在 iOS 14 上,CADisplayLink 是通过 Source 1 mach_port 直接接受 VSync 信号驱动的
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
在 iOS 15 ProMotion 设备上,CADisplayLink 不再由 VSync 信号驱动,而是由一个 UIKit 内部的 Source0 信号驱动
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
在 15 中,CADisplayLink 之一次创建并添加至 RunLoop 的时候,会注册一个 Source 1 信号,这和 14 中行为一致 。
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
其 callout 回调地址对应符号为同样为 display_timer_callback,同样和 14 中的一致 。
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
这也可以解释为什么 15 上 VSync 信号确实会唤醒一次 RunLoop,只是这次唤醒并不一定触发 DisplayLink 的回调,这就说明 display_timer_callback 行为和 14 相比一定发生了某种变化 。
display_timer_callback逻辑的变化使用 Hopper 分析 display_timer_callback 的实现,发现 15 和 14 的实现并无区别 。使用 LLDB 进行 debug,逐步分析,观察到后续调用函数为 CA::Display::DisplayLink::callback,其关键反汇编代码如下图所示:
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
观察反汇编代码可以发现,如果 CA::display_link_will_fire_handler 这个 block 返回了 NO,则这次 VSync 信号回调不会触发后续的 CA::DisplayLink::dispatch_items 调用 。
实际上在 LLDB 中也验证了这点:
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图

小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
注意上图中的 _CFRunLoopCurrentIsMain 和上图红框代码接近,后续的 blraa 指令看起来很明显是调用了一个 block(上面的 ldr x9 [x8, #0x10] 就是把 invoke 指针从 block 结构体中取出的意思) 。tbz 指令中 w0 寄存器为 block 执行的返回值,为 0(即 NO)时跳转至 0x1848dbc08,而 0x1848dbc08 刚好在 dispatch_items 的调用之后,跳过了该调用 。
通过对上图中 blraa 指令 step in,我们发现这个 block 实际上是由 UIKitCore 注册的:
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
找到引用了该符号的 UIKit 的私有 ***__UIUpdateCycleSchedulerStart,反汇编结果也验证了这点 。
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
同时发现这个 block 的返回值固定为 0x0 。
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
而同样的 symbol 在之前的 iOS 版本上并不存在,也就是说这个应该是 iOS 15 的变动 。换安装了 iOS 15 的非 ProMotion 设备,重走上面的逆向流程发现,该设备的 CA::display_link_will_fire_handler 为 nil,未注册:
小米电池休眠自动解除打开后盖  小米电池休眠自动解除

文章插图
这里 cbz 执行了跳转,说明 x0 为 nil,而 x0 是由 ldr x0, [x8, #0x1c8] 得到 。

推荐阅读