當我們說 流暢度 的時候,我們說的是什麼?不同的人對流暢性(卡頓掉幀)有不同的理解,對卡頓閾值也有不同的感知,所以有必要在開始這個系列文章之前,先把涉及到的內容說清楚,防止出現不同的理解,也方便大家帶着問題去看這幾篇問題,下面是一些基本的說明
Systrace 流暢性實戰目前包括下面三篇
Systrace (Perfetto) 工具的基本使用如果還不是很熟悉,那麼需要優先去補一下 Systrace 基礎知識系列[4]
Systrace 作為分析卡頓問題的第一手工具,給開發者提供了一個從手機全局角度去看問題的方式,通過 Systrace 工具進行分析,我們可以大致確定卡頓問題的原因:是系統導致的還是應用自身的問題
當然 Systrace 作為一個工具,再進行深入的分析的時候就會有點力不從心,需要配合 TraceView + 源碼來進一步定位和解決問題,最後再使用 Systrace 進行驗證
所以本文更多地是講如何發現和分析卡頓問題,至於如何解決,就需要後續自己尋找合適的解決方案了,比如對比競品的 Systrace 表現、優化代碼邏輯、優化系統調度、優化布局等
案例說明個人在使用小米 10 Pro 的時候,在桌面滑動這個最常用的場景裡面,總會有一種卡頓的感覺,10 Pro 是 90Hz 的屏幕,FPS 也是 90,所以一旦出現卡頓,就會有很明顯的感覺(個人對這個也比較敏感)。之前沒怎麼關注,在升級 12.5 之後,這個問題還是沒有解決,所以我想看看到底是怎麼回事
抓了 Systrace 之後分析發現,這個卡頓場景是一個非常好的案例,所以把這個例子拿出來作為流暢度的一個實戰分享
建議大家下載附件中的 Systrace,對照文章食用最佳
分析卡頓問題,我們一般的流程如下
按照這個流程分析之後,需要再反過來看各個進程,把各個線索聯繫起來,推斷最有可能的原因
從 Input 事件開始這次抓的 Systrace 我只滑動了一次,所以比較好定位,滑動的 input 事件由一個 Input Down 事件 + 若干個 Input Move 事件 + 一個 Input Up 事件組成
在 Systrace 中,SystemServer 中的 InputDispatcher 和 InputReader 線程都有體現,我們這裡主要看在 App 主線程中的體現

如上圖,App 主線程上的 deliverInputEvent 標識了應用處理 input 事件的過程,input up 之後,就進入了 Fling 階段,這部分的基礎知識可以查看下面這兩篇文章
由於這次卡頓主要是鬆手之後才出現的,所以我們主要看 Input Up 之後的這段

主線程上面的 Frame 有顏色進行標註,一般有綠、黃、紅三種顏色,上面的 Systrace 裡面,沒有紅色的幀,只有綠色和黃色。那麼黃色就一定是卡頓麼?紅色就一定是卡頓麼?其實不一定,單單通過主線程,我們並不能確定是否卡頓,這個在下面會講
從主線程我們沒法確定是否發生了卡頓,我們找出了三個可疑的點,接下來我們看一下 RenderThread
分析渲染線程放大第一個可疑點,可以看到,這一幀總耗時在 19ms, RenderThread 耗時 16ms,且 RenderThread 的 cpu 狀態都是 running(綠色),那麼這一幀這麼耗時的原因大概率是下面兩個原因導致的:

由於只是可疑點,所以我們先不去看 cpu 相關的,先查看 SurfaceFlinger 進程,確定這裡有卡頓發生
分析 SurfaceFlinger對於 Systrace 中 SurfaceFlinger 部分解讀不熟悉的可以先預習一下這篇文章 Android Systrace 基礎知識(5) -SurfaceFlinger 解讀[7]
這裡我們主要看兩個點
判斷是否卡頓的標準如下

如果 SurfaceFlinger 進行了合成,而且 App 在這一個 Vsync 周期(vsync-app)進行了正常的工作,但是對應的 App 的 BufferQueue 裡面沒有可用的 Buffer,那麼這一幀也是卡了,之所以 SurfaceFlinger 會正常合成,是因為有其他的 App 提供了可用來合成的 Buffer — 卡頓出現這種情況如下圖所示(也在附件的 Systrace 裡面)
如果 SurfaceFlinger 進行了合成,而且 App 在這一個 Vsync 周期(vsync-app)進行了正常的工作,而且對應的 App 的 BufferQueue 裡面有可用的 Buffer,那麼這一幀就會正常合成,此時沒有卡頓出現 — 正常情況正常情況如下,作為對比還是貼上來方便大家對比
回到本例的第一個疑點的地方,我們通過 SurfaceFlinger 端的分析,發現這一幀確實是掉了,原因是 App 沒有準備好可用的 Buffer 供 SurfaceFlinger 來合成,那麼接下來就需要看為什麼這一幀 App 沒有可用的 Buffer 給到 SurfaceFlinger
回到渲染線程上面我們分析這一幀所對應的 MainThread + RenderThread 耗時在 19ms,且 RenderThread 耗時就在 16ms,那麼我們來看 RenderThread 的情況
出現這種情況主要是有下面兩個原因
但是桌面滑動這個場景,負載並不高,且鬆手之後並沒有多餘的操作,View 更新之類的,本身耗時比前一幀多了將近 3 倍,可以推斷不是自身負載加重導致的耗時
那麼就需要看此時的 RenderThread 的 cpu 情況:
既然在 Running 情況,我們就去 CPU Info 區域查看這一段時間這個任務的調度情況
分析 CPU 區域的信息查看 CPU (Kernel 區域,這部分的基礎知識可以查看 Android Systrace 基礎知識 - CPU Info 解讀[8] 和 Android Systrace 基礎知識 -- 分析 Systrace 預備知識[9])這兩篇文章
回到這個案例,我們可以看到 App 對應的 RenderThread 大部分跑在 cpu 2 和 cpu 0 上,也就是小核上(這個機型是高通驍龍 865,有四個小核+3 個大核+1 個超大核)
其此時對應的頻率也已經達到了小核的最高頻率(1.8Ghz)
且此時沒有 cpu boost 介入
那麼這裡我們猜想,之所以這一幀 RenderThread 如此耗時,是因為小核就算跑滿了,也沒法在這麼短的時間內完成任務
那麼接下來要驗證我們的猜想,需要進行下面兩個步驟
在用同樣的流程分析了後面幾個掉幀之後,我們發現
至此,這一次的卡頓分析我們就找到了原因:RenderThread 掉到了小核
至於 RenderThread 的任務為啥跑着跑着就掉到了小核,這個跟調度器是有關係的,大小核直接的調度跟任務的負載有關係,任務從大核掉到小核、或者從小核遷移到大核,調度器這邊都是有參數和算法來控制的,所以後續的優化可能需要從這方面去入手
在 Triple-Buffer-的作用[10] 這篇文章,講到了 Triple Buffer 幾個作用
那麼在桌面滑動卡頓這個案例裡面,Triple Buffer 發揮了什麼作用呢?結論是:有的場景沒有發揮作用,反而有副作用,導致卡頓現象更明顯,下面是分析流程
可以看文章中 Triple Buffer 緩解掉幀 的原理:
在分析小米桌面滑動卡頓這個案例的時候,我發現在有一個問題,小米桌面對應的 App 的 BufferQueue,有時候會出現可用 Buffer 從 2 →0 ,這相當於直接把一個 Buffer 給拋棄掉了,如下圖所示
這樣的話,如果在後續的桌面 Fling 過程中,又出現了一次 RenderThread 耗時,那麼就會以卡頓的形式直接體現出來,這樣也就失去了 Triple Buffer 的緩解掉幀的作用了
下圖可以看到,由於丟棄了一個 Buffer,導致再一次出現 RenderThread 耗時的時候,表現依然是無 Buffer 可用,出現掉幀
仔細看前面這段丟棄 Buffer 的邏輯,也很容易想到,這裡本身就已經丟了一幀了,還把這個耗時幀所對應的 Buffer 給丟棄了(也可能丟棄的是第二幀),不管是哪種情況,滑動時候的每一幀的內容都是計算好的(參考 List Fling 的計算過程),如果把其中一幀丟了,再加上本身 SurfaceFlinger 卡的那一下,卡頓感會非常明顯
舉個例子,以滑動為例,offset 指的是離屏幕一個左邊的距離
附件已經上傳到了 Github 上,可以自行下載:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action[14]