如何代碼獲取 Flutter APP 的 FPS


眾所周知,官方提供了好幾個辦法來讓我們在開發 Flutter app 的過程中可以使用查看 fps等性能數據,如 devtools ,具體見文檔 Debugging Flutter apps 、 Flutter performance profiling等。

但是這些工具統計到的數據充其量只能算開發過程中的“試驗室”數據,假如需要統計app 在線上在用戶手機上的運行情況,該如何在 flutter 端代碼里自己計算性能數據,比如 fps 這個值呢?

經過閱讀源碼,發現其實很簡單,給 window 對象注冊 onReportTimings 即可,去看 api文檔。

void main() { runApp(...); window.onReportTimings = _onReportTimings; } void _onReportTimings(List<FrameTiming> timings) { // TODO } 

 

代碼給你看

考慮計算 fps ,只需要保留最近 N 個 FrameTiming 來計算即可,最好用類似stack的數據結構存起來,參考了文檔,我們選用 Queue ,N 指定為 100

const maxframes = 100; // 100 幀足夠了,對於 60 fps 來說 final lastFrames = ListQueue<FrameTiming>(maxframes); void _onReportTimings(List<FrameTiming> timings) { // 把 Queue 當作堆棧用 for (FrameTiming timing in timings) { lastFrames.addFirst(timing); } // 只保留 maxframes while (lastFrames.length > maxframes) { lastFrames.removeLast(); } } 

lastFrames 的頭就是最后一幀,尾是隊伍里最開始的一幀,現在你可以計算 FPS 了:

double get fps { int frames = lastFrames.length; var start = lastFrames.last.timestampInMicroseconds(FramePhase.buildStart); var end = lastFrames.first.timestampInMicroseconds(FramePhase.rasterFinish); var duration = (end - start) / Duration.microsecondsPerMillisecond; return frames * Duration.millisecondsPerSecond / duration; } 

但,你會發現,這樣算出來和官方工具算的對不上,而且錯的離譜。

 

代碼再給你看一下

其實, window.onReportTimings 只會在有幀被繪制時才有數據回調,換句話說,你沒有和app發生交互、界面狀態沒有變化、沒有定時刷布局等等沒有新的幀產生,所以 lastFrames 里存的可能是分屬不同”繪制時間段“的幀信息。

假設最大 60 幀,每幀消耗的時間 frameInterval 為:

const frameInterval = const Duration(microseconds: Duration.microsecondsPerSecond ~/ 60); 

flutter 應該就是根據該 frameInterval 按計划繪制每一幀,即如果一幀繪制時間少於 frameInterval 則下一幀會等待一點點時間,而當一幀繪制時間超過 frameInterval 則會立即繪制(我沒看源碼猜的,如果跟你認知的不一樣,可以評論一下)。

可以認為 lastFrames 里的相鄰兩幀如果開始結束相差時間過大,比如大於 frameInterval * 2 ,是不同繪制時間段產生的。那么我們采取最后一個時間段來計算 FPS:

double get fps {
  var lastFramesSet = <FrameTiming>[]; for (FrameTiming timing in lastFrames) { if (lastFramesSet.isEmpty) { lastFramesSet.add(timing); } else { var lastStart = lastFramesSet.last.timestampInMicroseconds(FramePhase.buildStart); if (lastStart - timing.timestampInMicroseconds(FramePhase.rasterFinish) > ( frameInterval.inMicroseconds * 2)) { // draw in different set break; } lastFramesSet.add(timing); } } var frameCount = lastFramesSet.length; var duration = ( lastFramesSet.first.timestampInMicroseconds(FramePhase.rasterFinish) - lastFramesSet.last.timestampInMicroseconds(FramePhase.buildStart) ) ~/ Duration.microsecondsPerMillisecond; return frameCount * Duration.millisecondsPerSecond / duration; } 

此時又會發現,算出來的 FPS 有時會超過 60。

 

代碼給你看,真的

因為上面的 duration 其實算的不對。

前面提到,flutter 是按 frameInterval 計划繪制的,所以 duration 算出來偏小,進而導致計算結果超過 60。

換個思路,遵循之前的按計划繪制的邏輯,如果一幀繪制時長超過 frameInterval ,那么它必然會占用下一個幀的繪制計划,這個被擠占的幀即為 丟幀 ,顯然,一秒內總繪制的幀數加上總丟掉幀數應該約等於 60。

所以, FPS ≈ 60 * framesCount / (framesCount + droppedCount) = 60 * framesCount / costCount

修改之前的代碼,套入公式即為:

double get fps {
  var lastFramesSet = <FrameTiming>[]; for (FrameTiming timing in lastFrames) { if (lastFramesSet.isEmpty) { lastFramesSet.add(timing); } else { var lastStart = lastFramesSet.last.timestampInMicroseconds(FramePhase.buildStart); if (lastStart - timing.timestampInMicroseconds(FramePhase.rasterFinish) > ( _frameInterval.inMicroseconds * 2)) { // in different set break; } lastFramesSet.add(timing); } } var framesCount = lastFramesSet.length; var costCount = lastFramesSet.map((t) { // 耗時超過 frameInterval 會導致丟幀 return (t.totalSpan.inMicroseconds ~/ frameInterval.inMicroseconds) + 1; }).fold(0, (a, b)=> a + b); return framesCount * 60 / costCount; } 

好的,現在你會發現,算出來和 Flutter Performance 中看到的幾乎一模一樣。

廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com

最后還有點代碼給你看

對了,需要注意的是,Flutter 官方工具讀取的 fps 信息,也來自 onReportTimings 回調,所以需要保留原始回調調用,否則你會發現工具讀不到 fps 信息了,如:

var orginalCallback = window.onReportTimings; window.onReportTimings = (timings) { if (orginalCallback != null) orginalCallback(timings); // ... }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM