Flutter 学习与性能优化总结

Posted 懂你的大海

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 学习与性能优化总结相关的知识,希望对你有一定的参考价值。

作者:小肥羊冲冲冲android

前言

有幸负责的模块使用Flutter编写,在三个月的开发过程中,在原有Demo自学基础上又学到了很多,谨以此篇文章做一个Flutter阶段性的学习和总结,以便于往后的学习过程中温故而知新,那么我们正篇开始。

前世今生

新事物的诞生往往是有一定原因存在的,移动端在这条路上有几个阶段,从Android Native 到 WebView 阶段,为了获得不发版本就可以获得实时动态化的效果,双端使用JSBridge实现了与原生Native底层能力的对接。   Native容器阶段,主要有React Native为代表,虽然RN依赖于原生渲染,性能好与H5,但是还存在一些问题,比如:javascript和原生通信,部分场景存在通讯瓶颈,容易导致卡顿、不同移动平台,空间需要单独维护,原生版本更新后,社区控件更新较慢等问题。

Flutter阶段,Flutter抛弃了原生系统控件和 Webview,使用自研高性能渲染引擎来绘制Widget,预先(AOT)编译,运行时直接执行Native(arm)代码,Dart代码执行(在UI TaskRunner),图片下载(IO TaskRunner),真正的渲染(GPU TaskRunner),同平台的通信等(Platform TaskRunner即Native概念下的主线程)是互相隔离的。针对布局等的优化:布局计算时单次树走动即可完成;Relayout Boundary机制:如果Child 的size是固定的,那么不会因为Child的Relayout导致Parent ReLayout等布局优化,都让Flutter脱颖而出。

所以,App引入Flutter,可以有效提高开发效率,避免双端不一致的现象,缩小开发成本。但是Flutter毕竟不是原生,在代码写法上有很多需要注意的地方,文章第三章会主要总结本人在开发和工作中遇到的问题,相比于H5,flutter在动态化方面,支持的并不好。

1 基础知识

1.1 运行模式

Debug:Debug模式可以在真机和模拟器上同时运行:会进入所有断点,包括debugging信息、debugger aids(比如observatory)和服务扩展。优化了快速develop/run循环,但是没有优化执行速度、二进制大小和部署。命令flutter run就是以这种模式运行的。

Release : Release模式只能在真机上运行,不能在模拟器上运行:会关闭所有断言和debugging信息,关闭所有debugger工具。优化了快速启动、快速执行和减小包体积。禁用所有的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。

Profile:Profile模式只能在真机上运行,不能在模拟器上运行:和Release模式类似,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(比如可以使用DevTools)。flutter run --profile 可进入该模式。

Test :headless test模式只能在桌面上运行:和Debug模式不同的是headless的而且你能在桌面运行。命令flutter test就是以这种模式运行的。

1.2 架构设计

Framework : 使用dart实现,包括Material Design风格的Widget,Cupertino(针对ios)风格的Widgets,文本/图片/按钮等基础Widgets,渲染,动画,手势等。

Engine : C++实现,主要包括:Skia,Dart和Text。 Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。

Embedder : 嵌入层,即把Flutter嵌入到各个平台上去,这里做的主要工作包括渲染Surface设置,线程设置,以及插件等。 从这里可以看出,Flutter的平台相关层很低,平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

1.3 渲染流程

Widget:Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成;不过,由于Element是通过Widget生成,所以它们之间有对应关系,我们可以宽泛地认为Widget树就是指UI控件树或UI渲染树。

Element : 一个Widget对象可以对应多个Element对象。这很好理解,根据同一份配置(Widget),可以创建多个实例(Element)。

从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。

举个栗子?

以下例子取自 闲鱼技术博客 闲鱼Flutter复杂业务优化

Container(
    color: Colors.blue,
    child: Row(
        children: [
    Image.asset('image'),
    Text('text'),
    ],
  ),
);

依据上图 来说就是 UI 刷新的时候,Framework 通知 Engine,Engine 会等到下个 Vsync 信号到达的时候,会通知 Framework 进行 animate, build,layout,paint,最后生成 layer 提交给 Engine。Engine 会把 layer 进行组合,生成纹理,最后通过 Open Gl 接口提交数据给 GPU, GPU 经过处理后在显示器上面显示

在基础能力和业务开发完成后,性能优化和代码检查也是非常重要的,所以后边的文章主要围绕 性能检测和优化 等细节的总结。

3 性能检测工具

结合 Flutter 性能分析 链接 和Flutter 性能视图 等官方文档,整合调试工具说明,并添加自我理解

3.1 Performance Overlay

开启方式比较多,通过 DevTools TimeLines 或者 run Flutter Inspector 都可以进入

点击show CPU GPU 图后

● 竖轴表示耗时,沿竖轴的黑线是时间线 (间隔单位为 16ms) ● 横轴则表示帧,垂直的绿色条代表的是当前帧 (如果为红色则代表处理耗时点) ● 上图显示GPU线程消耗的时间 (帧为红色:Widget太多 场景复杂 渲染耗时) ● 下图显示UI线程消耗的时间 (帧为红色:Dart代码有问题 查看build中是否做太多耗时操作)

也可以在代码中开启 PerformanceOverlay 控件

@override
  Widget build(BuildContext context) 
    Widget app = MaterialApp(
    	// true 展示PerformanceOverlay
      showPerformanceOverlay: showPerformanceOverlay,
      title: 'appname',
      theme: ThemeData(
        appBarTheme: AppBarTheme(brightness: Brightness.light),
        textTheme: textTheme
      ),
      home: _buildHome(context),
      builder: (_, widget) 
        return MediaQuery(
          data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
          child: widget
        );
      ,
    );

    return WillPopScope(
      child: app,
      onWillPop: dealWillPop,
    );
  

showPerformanceOverlay 为MaterialApp自带属性,如果没有使用MaterialApp,可以在代码中通过PerformanceOverlay.allEnabled(checkerboardOffscreenLayers: true); 开启

3.2 DevTools TimeLine

使用 DevTools 可以在Performance Overlay大粒度卡顿范围内深度挖掘 由于Observatory和DevTools功能类似,且正逐渐被DevTools替代,这里主要罗列DevTools的使用方式。

● 顶部部分列表类似于PerformanceOverlay ,深色代表GPU耗时,浅红色代表UI层耗时(图层过多) ● 中间部分为帧事件图表,分位 UI 和 GPU两部分组成 ● 底部部分为CPU图表,主要有三种不同呈现方式来展示CPU调用堆栈信息

我们也可以通过添加一些系统api ,更方便我们定位事件图表具体执行位置 ● debugProfileBuildsEnabled - 向 Timeline 事件中添加 build 信息 ● debugProfilePaintsEnabled - 向 timeline 事件中添加 paint 信息 ● debugPrintRebuildDirtyWidgets - 记录每帧重建的 widget

和Android Profiler 类似,帧事件横轴代表事件顺序,纵轴 代表方法调用堆栈,我们可以通过事件宽度来判断那些事件是比较耗时的。

3.3 Widget rebuild stats

Android Studio 中 View > Tool Windows > Flutter Performance 打开性能工具窗口,在 Widget rebuild stats 中勾选 Track widget rebuilds 来查看 widget 的重建信息。重建信息包括 Widget 名字、源码位置、上一帧中重建次数、当前页面中重建次数。此外,Widget 名字前面还会显示一个小图标。 ● 黄色旋转圆圈 - 重建次数过多 ● 灰色圆圈 - 未重建 ● 灰色旋转圆圈 其他情况

4 性能优化

上一章,对于检测工具大概的总结了使用方式和看法,下边我们就根据工具,来定位界面的问题,并总结出我的优化方式。

4.1 列表优化

在商品2.0中列表占比是比较多得,首页列表的交互逻辑更为复杂,如何让用户用起来如丝般顺滑,需要进行一些性能的打磨。

问题: 界面滑动的时候 会有轻微掉帧现象,检测 fps长期飘红(小于 60fps)

(图一 Flutter Performance 截图)

(图二 DevTools Fps ui GPU 截图)

方案:

更换PowerScrollView 后列表 滑动FPS和部分DevTools截图 PowerScrollView :概念链接

PowerScrollView 优化点:

  1. 针对PowerDataManager 数据可以做到增量更新。
  2. 添加缓存数组,监控_childElements方法来对element 进行缓存,当滚动超出 viewport 的显示以及预加载范围或者数据源发生变化,会通过调用 collectGarbage 方法回收不需要的 elements)
  3. cell 层面引入了 placeholder 的机制,快速滑动场景优化build工作量
  4. 更多样的瀑布流 、列表样式可供选择等等优化

引入后效果,

(图一 Flutter performance 截图)

图中红色部分主要处于手指第一次按下,并且执行了顶部商品工具栏动画的原因

(图二 DevTools FPS UI GPU 截图)

上图可以看到 gpu占比较多,列表滑动起始部分还是有卡顿部分,主要是由于首页商品工具栏过度动画 和 首页复杂的UI展示引起的,后续可以通过

4.2 优化 ClipPath 和 ClipRPath

在刚开始调试初期,使用 Timeline 查看渲染线程性能消耗,可以发现有多个 ClipRectLayer 和 ClipRRectLayer过程,对比商品首页界面后,猜测是在Flutter壳工程中使用自带Image展示图片的时候,对于大图没有进行裁剪显示,部分圆角Widget也需要优化,同时修复 radius 为0也会设置 ClipRRect 的问题。

4.3 常用优化方法

  1. 尽量将setState放在叶子节点,好处是build时影响范围极小,局部刷新
  2. 使用ListView.builder()而不是直接使用ListView()来构建列表 (官方推荐)
  3. 对于频繁更新的控件,使用RepaintBoundary隔离,让其拥有一个独立的paint区域
  4. 使用const来修饰永远不需要变更的控件,如果宽高固定,推荐固定宽高,避免重复计算
  5. 按需使用StateLessWidget,并非全部用StateFulWidget
  6. 使用Visibility控件,避免布局树频繁切换
  7. 针对于使用Fish-redux,state 对象中的视图数据真正发生变化的时候,新建 state 对象
  8. 使用图片替换半透明效果,减少saveLayer 或者clipPath 的使用。

最后

不知不觉已经写到了最后,对于Flutter的学习和使用,一直都在路上,文章中不免有些纰漏,还望大佬们海涵并指出,后续会继续深耕Flutter 动态化方案和Flutter性能优化,继续努力学习,继续满血冲冲冲!

以上是关于Flutter 学习与性能优化总结的主要内容,如果未能解决你的问题,请参考以下文章

(精品网课资源分享)动脑学院VIP50W年薪安卓Android架构性能优化UI/NDK/Flutter

常见性能优化策略的总结(转)

Flutter 学习 布局类Widget

前端性能优化术语

Flutter性能优化实践

PLSQL优化基础和性能优化 (学习总结)