flutter卡頓監(jiān)控
1怜械、方案1
WidgetsBinding.instance!.scheduleFrameCallback(beginTimeCallback);
WidgetsBinding.instance!.addPersistentFrameCallback(drawTimeCallback!);
通過計(jì)算handleBeginFrame和handleDrawFrame之間的時(shí)間間隔計(jì)算幀率
在一秒內(nèi)計(jì)算從begin->draw調(diào)用了多少次,次數(shù)就是幀數(shù),如果幀數(shù)少于手機(jī)的渲染幀數(shù)离钝,會(huì)認(rèn)為丟失了多少幀
2拜银、方案2
SchedulerBinding.instance.addTimingsCallback
TimingsCallback 回調(diào)會(huì)返回 Engine 層的幀信息列表牌废,列表由 FrameTiming 構(gòu)成。FrameTiming 包含了每一幀的開始時(shí)間與結(jié)束時(shí)間祥绞,每過計(jì)算實(shí)際幀數(shù)與理論幀的比值即可得到幀率數(shù)據(jù)。
公式:
實(shí)際幀率= 實(shí)際幀數(shù)*理論fps/(實(shí)際幀數(shù)+丟失幀數(shù))
如何理解:
假設(shè)單位時(shí)間為1秒:實(shí)際發(fā)生50幀鸭限,丟失10幀蜕径, 實(shí)際幀率= 5060/(50+10) =50幀
假設(shè)單位時(shí)間為2秒:實(shí)際發(fā)生50幀,丟失70幀败京,實(shí)際幀率= 5060/(50+70) =25幀
公式推倒:
fps=理論總幀數(shù)/t=》t=理論總幀數(shù)/fps
實(shí)際fps=實(shí)際幀數(shù)/t = 實(shí)際幀數(shù)/( 理論總幀數(shù)/fps)= 實(shí)際幀數(shù)fps/理論總幀數(shù)=實(shí)際幀數(shù)fps/(實(shí)際總綜述+理論丟失幀數(shù))
代碼:
Fps._() {
_timingsCallback = (List<FrameTiming> timings) {
//異步計(jì)算fps
_computeFps(timings);
};
SchedulerBinding.instance!.addTimingsCallback(_timingsCallback!);
}
/// 計(jì)算fps
Future<void> _computeFps(List<FrameTiming> timings) async {
// 假設(shè)到達(dá)的幀數(shù)為1 2 3 4
// 那在lastFrames里就是 4 3 2 1兜喻,隊(duì)尾幀在隊(duì)頭
for (FrameTiming timing in timings) {
lastFrames.addFirst(timing);
}
// 只保留 maxFrames,超出則移除最早的幀
while (lastFrames.length > _maxFrames) {
lastFrames.removeLast();
}
var lastFramesSet = <FrameTiming>[];
// 獲取當(dāng)前手機(jī)的fps
if (_fpsHz == null) {
_fpsHz = await FpsPlugin.getRefreshRate;
}
//每幀消耗的時(shí)間赡麦,單位微秒
if (_frameInterval == null) {
_frameInterval =
Duration(microseconds: Duration.microsecondsPerSecond ~/ _fpsHz!);
}
for (FrameTiming timing in lastFrames) {
//lastFrames 隊(duì)頭是最后的幀朴皆,所以第一次取出來的是隊(duì)尾幀
if (lastFramesSet.isEmpty) {
lastFramesSet.add(timing);
} else {
// 幀排序如下
// frame4 frame3 frame2 frame1
var lastStart = //frame4的build開始,即frame3的rasterFinish隧甚,但中間是會(huì)有間隔的
lastFramesSet.last.timestampInMicroseconds(FramePhase.buildStart);
// 上面提到的間隔時(shí)間
var interval =
lastStart - timing.timestampInMicroseconds(FramePhase.rasterFinish);
//相鄰兩幀如果開始結(jié)束相差時(shí)間過大车荔,比如大于 frameInterval * 2,認(rèn)為是不同繪制時(shí)間段產(chǎn)生的
if (interval > (_frameInterval!.inMicroseconds * 2)) {
break; //注意這里是break戚扳,這次循環(huán)結(jié)束了忧便,雖然在同一個(gè)隊(duì)列里,但有可能相鄰的兩幀不在一個(gè)時(shí)間段帽借,所以不能放一起計(jì)算珠增,有個(gè)開源的就是沒處理這里
}
lastFramesSet.add(timing);
}
}
var drawFramesCount = lastFramesSet.length;
//公式,假設(shè)當(dāng)前手機(jī)的FPS = 60幀砍艾,1秒渲染了60次
// FPS / 60 = drawCount / (drawFramesCount + droppedCount)//drawCount????
// costCount = (drawFramesCount + droppedCount)
// FPS ≈ 60 * drawFramesCount / costCount
int? droppedCount = 0; //丟幀數(shù)
// 計(jì)算總的幀數(shù)
var costCount = lastFramesSet.map((frame) {
// 耗時(shí)超過 frameInterval 認(rèn)為是丟幀蒂教,以60hz為例
// 15ms ~/ 16ms = 0
// 16ms ~/ 16ms = 0
// 17ms ~/ 16ms = 1
// 所以只要droppedCount大于0 ,認(rèn)為當(dāng)前幀是丟幀的
int droppedCount =
(frame.totalSpan.inMicroseconds ~/ _frameInterval!.inMicroseconds);
return droppedCount +
1; //自己本身繪制的一幀脆荷,這里加一是因?yàn)檎J(rèn)為丟幀了凝垛,加1變成2或3懊悯,主要看實(shí)際消耗的時(shí)長,如果是正常幀梦皮,那就是0+1=1
}) //這里返回的其實(shí)是個(gè)list<int>
.fold(
0, //計(jì)算的初始值
(dynamic a, b) =>
a + b); //計(jì)算總的幀數(shù)炭分,fold就是list[0]+list[1]+....list[list.len-1]
//丟幀數(shù)=總幀數(shù)-繪制幀數(shù)
droppedCount = costCount - drawFramesCount;
double fps = drawFramesCount * _fpsHz! / costCount; //參考上面那四行公式
// DebugLog.instance.log(
// "computerFps _fpsHz is $_fpsHz drawFrame is $fps,dropFrameCount is $droppedCount");
lastFrames.clear();
_callBackList.forEach((callBack) {
callBack(fps, droppedCount!.toDouble());
});
}
但通過幀率數(shù)據(jù)能反映卡頓點(diǎn)嗎?
肯定是不能的,率幀是一段時(shí)間(1 秒)內(nèi)的性能表現(xiàn)剑肯,卡頓可能發(fā)生在這一段時(shí)間內(nèi)的任意時(shí)間點(diǎn)捧毛,等計(jì)算出幀率,卡頓點(diǎn)早已錯(cuò)過让网,因此使用幀率感知卡頓行不通呀忧。
3、方案3
使用Flutter Dev tools工具溃睹,在調(diào)試時(shí)候查看卡頓情況
點(diǎn)擊android studio的Flutter Dev tools工具按鈕打開調(diào)試器
進(jìn)一步打開開關(guān)而账,可以在應(yīng)用界面上看到實(shí)時(shí)幀率:
但是注意的是模擬器只可以debug,不支持profile模式:https://github.com/flutter/flutter/issues/11754
分析應(yīng)用的性能問題需要打開性能監(jiān)控圖層 (performance overlay) 來觀察 UI 和 raster 線程丸凭。在此之前福扬,要確保是在 分析模式 下運(yùn)行,而且當(dāng)前設(shè)備不是虛擬機(jī)惜犀。使用用戶可能采用的最慢設(shè)備來獲取最佳結(jié)果铛碑。
《走近科學(xué),探究阿里閑魚團(tuán)隊(duì)通過數(shù)據(jù)提升Flutter體驗(yàn)的真相》:
https://developer.aliyun.com/article/699875
《如何代碼獲取 Flutter APP 的 FPS》:
https://yrom.net/blog/2019/08/01/how-to-get-fps-in-flutter-app-codes/
《Flutter 線上卡頓檢測方案實(shí)踐(附代碼)》:
https://juejin.cn/post/7390620343014096948
《Flutter 性能分析》:
https://docs.flutter.cn/perf/ui-performance