2024-12-26 flutter卡頓監(jiān)控

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í)際幀率= 50
60/(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)試器

image.png

進(jìn)一步打開開關(guān)而账,可以在應(yīng)用界面上看到實(shí)時(shí)幀率:


image.png

但是注意的是模擬器只可以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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虽界,一起剝皮案震驚了整個(gè)濱河市汽烦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莉御,老刑警劉巖撇吞,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異礁叔,居然都是意外死亡牍颈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門琅关,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煮岁,“玉大人,你說我怎么就攤上這事涣易』” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵新症,是天一觀的道長步氏。 經(jīng)常有香客問我,道長徒爹,這世上最難降的妖魔是什么荚醒? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任芋类,我火速辦了婚禮,結(jié)果婚禮上界阁,老公的妹妹穿的比我還像新娘梗肝。我一直安慰自己,他們只是感情好铺董,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禀晓,像睡著了一般精续。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粹懒,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天重付,我揣著相機(jī)與錄音,去河邊找鬼凫乖。 笑死确垫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帽芽。 我是一名探鬼主播删掀,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼导街!你這毒婦竟也來了披泪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤搬瑰,失蹤者是張志新(化名)和其女友劉穎款票,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泽论,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艾少,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翼悴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缚够。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抄瓦,靈堂內(nèi)的尸體忽然破棺而出潮瓶,到底是詐尸還是另有隱情,我是刑警寧澤钙姊,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布毯辅,位于F島的核電站,受9級特大地震影響煞额,放射性物質(zhì)發(fā)生泄漏思恐。R本人自食惡果不足惜沾谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胀莹。 院中可真熱鬧基跑,春花似錦、人聲如沸描焰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荆秦。三九已至篱竭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間步绸,已是汗流浹背掺逼。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓤介,地道東北人吕喘。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像刑桑,于是被迫代替她去往敵國和親氯质。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容