小米電池休眠自動解除打開后蓋 小米電池休眠自動解除( 四 )


小米電池休眠自動解除打開后蓋  小米電池休眠自動解除

文章插圖
卡頓場景上面兩次測試都接近理想情況,即整個 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] 得到 。

推薦閱讀