闲鱼客户端的flutter页面已经服务上亿级用户,这个时候Flutter页面的用户体验尤为重要,完善Flutter性能稳定性监控体系,能够及早发现线上性能问题,也能够做为用户体验提高的衡量标准。那么Flutter的性能到底如何?是否像官方宣传的那么丝滑?Native的性能指标是否能够用来检测Flutter页面?下面给你们分享咱们在实践中总结出来的Flutter的性能稳定性监控方案。android
过分的丢帧从视觉上会出现卡顿现象,体如今用户滑动操做不流畅;页面加载耗时过长容易中断操做流程;Flutter部分exception会致使发生异常代码后面的逻辑没有走到从而形成逻辑bug甚至白屏。这些问题很容易考验用户耐心,引发用户反感。ios
因此咱们制定如下三个指标做为线上Flutter性能稳定性标准:算法
最终目标是让这些数据指标驱动Flutter用户体验升级。weex
咱们先大概了解下屏幕渲染流程:CPU先把UI对象转变GPU能够识别的信息存储进displaylist列表,GPU执行绘图指令来执行displaylist,取出相应的图元信息,进行栅格化渲染,显示到屏幕上,这样一个循环的过程实现屏幕刷新。并发
闲鱼客户端采用的Native、Flutter混合技术方案,Native页面FPS监控采用集团高可用方案,Flutter页面是否能够直接采用这套方案监控?async
广泛的FPS检测方案Android端采用的是Choreographer.FrameCallBack,IOS采用的是CADisplayLink注册的回调,原理是相似的,在每次发出Vsync信号,而且CPU开始计算的时候执行到对应的回调,这个时候表示屏幕开始一次刷新,计算固定时间内屏幕渲染次数来获得fps。(这种方式只能检测到CPU卡顿,对于GPU的卡顿是没法监控到的)。因为这两种方法都是在主线程作检测处理,而flutter的屏幕绘制是在UI TaskRunner中进行,真正的渲染操做是在GPU TaskRunner中,关于详细的Flutter线程问题能够参考闲鱼以前的文章:深刻理解Flutter引擎线程模式。工具
这里咱们得出结论:Native的FPS检测方法并不适用于Flutter。源码分析
Flutter官方给咱们提供了 Performance Overlay (具体参考 Flutter performance profiling) 做为检测帧率工具,能否直接拿来用?性能
上图显示了Performance Overlay模式下的帧率统计,能够看到,Flutter分开计算GPU 和UI TaskRunner。UI Task Runner被Flutter Engine用于执行Dart root isolate代码,GPU Task Runner被用于执行设备GPU的相关调用。经过对flutter engine源码分析,UI frame time是执行window.onBeginFrame所花费的总时间。GPU frame time是处理CPU命令转换为GPU命令并发送给GPU所花费的时间。spa
这种方式只能在debug和profile模式下开启,没有办法做为线上版本的fps统计。可是咱们能够经过这种方式得到启发,经过监听Flutter页面刷新回调方法handleBeginFrame()、handleDrawFrame()来计算实际FPS。
注册WidgetsFlutterBinding监听页面刷新回调handleBeginFrame()、handleDrawFrame()
handleBeginFrame: Called by the engine to prepare the framework to produce a new frame. handleDrawFrame: Called by the engine to produce a new frame.
经过计算handleBeginFrame和handleDrawFrame之间的时间间隔计算帧率,主要流程以下图:
到这里,咱们完成Flutter中页面帧率的统计,这种方式统计的是UI TaskRunner中的CPU操做耗时,GPU操做在Flutter引擎内部实现,要修改引擎来监控完整的渲染耗时,咱们目前大部分的场景没有复杂到gpu卡顿,问题主要仍是集中在CPU,因此说能够反应出大部分问题。从线上数据来看,release模式下Flutter的流畅度仍是蛮不错的,ios的主要页面均值基本维持在50fps以上,android相对ios略低。这里须要注意的是帧率的均值fps在反复滑动过程当中会有一个稀释效果,致使一些卡顿问题没有暴露出来,因此除了fps均值,须要综合掉帧范围、卡顿秒数、滑动时长等数据才能反应出页面流畅度状况。
集团内部高可用方案统计Native页面加载时长是经过容器初始化后开启定时器在容器layout的时候检查屏幕渲染程度,计算可见组件的屏幕覆盖率,知足条件水平>60%,垂直>80%以上认为知足页面填充程度,再检查主线程心跳判断是否加载完成
再来看看weex页面加载流程和统计数据的定义
Weex的页面刷新稳定定义:屏幕内view渲染完成且view树稳定的时间
具体实现:当屏幕内发生view的add/rm操做时,认为是可交互点,记录数据。直到没有再发生为止。
在概念上Flutter和weex的首屏时长和可交互时长并不彻底一致,Flutter之因此选择从路由跳转开始计算时长主要是由于这种计算方式更贴近用户体验,能够获取更多的问题信息,好比路由跳转的时长问题等。
Flutter的可交互时长end点采用的算法与native一致,可见组件知足页面填充程度而且完成心跳检查的状况下任务可交互,另外对于一些比较空的页面,组件面积小,没法达到水平>60%,垂直>80%的条件,就用交互前最后一次Frame刷新时间点做为end点。
具体流程以下图:
因为debug模式采用的JIT编译,debug模式下体验加载时长偏长,可是release模式下的AOT编译时长明显缩短不少,总体页面加载时长仍是要优于weex。
Flutter部分exception/error 会致使代码后面的逻辑没有走到形成页面或逻辑bug,因此flutter的exception须要做为稳定性的标准之一
FlutterException率 = exception发生次数 / flutter页面PV
分子:exception发生次数(已过滤掉白名单)
Flutter内部assert、try-catch和一些异常逻辑的地方会统一调用FlutterError.onError
经过重定向FlutterError.onError到本身的方法中监测exception发生次数,并上报exception信息
分母:flutter页面PV
具体实现以下:
Future<Null> main() async { FlutterError.onError = (FlutterErrorDetails details) async { Zone.current.handleUncaughtError(details.exception, details.stack); }; runZoned<Future<Null>>(() async { runApp(new HomeApp()); }, onError: (error, stackTrace) async { await _reportError(error, stackTrace); }); }
其中,FlutterError.onError只会捕获Flutter framework层的error和exception,官方建议将这个方法按照本身的exception捕获上报需求定制。在实践过程当中,咱们遇到不少不会对用户体验产生任何影响的exception会被频繁触发,这类没有改善意义的exception能够添加白名单过滤上报。
有了线上exception的监控,能够及早发现隐患,获取问题堆栈信息,方便定位bug,提示总体稳定性
到这里,咱们完成Flutter页面滑动流畅度、页面加载时长和Exception率的统计,对于Flutter的性能有一个具体的数字化标准,对之后的用户体验提高和性能问题排查提供基础。目前闲鱼客户端的商品详情页和主发布页已经全量Flutter化,感兴趣的同窗能够体验下这两个页面和其余页面的性能差别,最后欢迎你们提供反馈和建议。
本文做者:闲鱼技术-三莅
本文为云栖社区原创内容,未经容许不得转载。