美团外卖 Flutter 性能优化与实践

如果无法正常显示,请先停止浏览器的去广告插件。
分享至:
1. Flutter 性能优化与实践 吴晓⻰ 美团技术专家
2.
3. 吴晓⻰ • 2019 年加入美团外卖技术部商家研发组 • 曾就职于百度、金山 • 现为 Flutter 动态化核心成员之一
4. 1 Flutter 性能指标采集与监控体系 2 Flutter 在客户端上关键指标优化与实践
5. Flutter 性能指标采集与监控体系 ① 框架层异常 ② 引擎层和嵌入层崩溃 ③ FPS 最优采集口径 ④ Flutter 秒开率概述及算法原理 ⑤ 大盘指标
6. ① 框架层异常 网络环境 运行环境 runZoned 渲染逻辑 JSON 解析 FlutterError.onError 手势识别
7. ① 框架层异常 多个 Zone 间通过 parent 属性形成链式结构 runZoned 怎么捕获异常? Zone 内部用 try-catch 包裹执行体,从而 将执行体内的所有异常捕获,然后将捕 获到的异常及调用栈传递给 onError 回调
8. ① 框架层异常 以RenderObjectToWidgetElement的_rebuild方法为例 与预期值不同时将抛出异常 FlutterError 抛出的异常怎么收集?
9. ① 框架层异常 业务代码产生 Framework 层抛出 重写 onError 通过 Zone 获取
10. ② 引擎层和嵌入层崩溃 复用收集方案 自动符号化 与引入的其他三方库并无二致,通过原 Native 的崩溃收 集方案采集上报 符号表和带符号的 libflutter.so 在自己手里,MTFlutter 团队 和 Crash 平台团队协作,实现 Flutter 崩溃日志自动符号化
11. ③ FPS最优采集口径 Native Flutter Android Choreographer.FrameCallBack iOS CADisplayLink
12. ③ FPS最优采集口径 采集方案 原生方案不能用 分析工具有限制 渲染引擎Skia直接和绘图相关的底 层接又对接,绕过原生渲染引擎 官方提供的调试分析工具只能在 Debug 和 Profile 模式下开启
13. ③ FPS最优采集口径 Release 模式 FPS 计算方式探索 Engine Vsync VsyncWaiter ::FireCallback Framework Shell ::OnAnimatorBeginFrame Window ::BeginFrame onBeginFrame Window onDrawFrame SchedulerBinding ._handleBeginFrame ._handleDrawFrame
14. ③ FPS最优采集口径 onReportTimings FrameTiming 提供一个包含FrameTiming的List参数 提供非常细致的时间开销 给window对象注册 onReportTimings,就可以拿到整个构建渲染过程的完整耗时
15. ④ Flutter 秒开率概述及算法原理 秒开率 页面首屏时间 < 1s 的比率 用于衡量终端服务内容呈现速度 页面实现秒开有助于电商增加营收 优化前 | 等待 优化后 | 秒开
16. ④ Flutter 秒开率概述及算法原理 界定达成FMP触底算法 可见元素布局第一次超出屏幕底部的时间 Text、Image、Button 等视图元素 FMP(First Meaning Paint):首次有效绘制, 标记主角元素渲染完成的时间点 全屏⻚面结束示例
17. ④ Flutter 秒开率概述及算法原理 界定达成FMP触底算法 交互前最后一次首屏范围内的视图树变动 Text、Image、Button 等 其他⻚面结束示例
18. ⑤ 大盘指标 FPS大盘 趋 势 秒开率大盘 对 比 FPS值 度量质量、横向对标
19. 小结 • 框架层异常(runZoned、FlutterError.onError) • 引擎层和嵌入层崩溃(崩溃日志自动符号化) • Flutter FPS 最优采集口径(FrameTiming) • Flutter 秒开率概述及算法原理(可视元素的判定、结束点的判定) • 大盘指标(度量质量、横向对标)
20. Flutter 在客户端上关键指标优化与实践 ① 基于动态下发的 Flutter 包大小优化方案 ② 性能优化工具 ③ 性能优化思路 ④ 外卖商家端商品列表性能优化实践
21. libapp.so libflutter.so flutter.jar flutter_assets 310.5KB Flutter 引擎 Flutter 引擎的 Java 代码编译产物 flutter_assets 3% 170KB 包括图片、字体、LICENSE 等静态资源 Flutter 业务 flutter.jar 6% 1.5MB libapp.so 29% Flutter 业务与框架的 Dart 代码编译产物 3.2MB Flutter 资源 Flutter 引擎的 C++ 代码编译产物 libflutter.so 62% Flutter Android 产物组成
22. 稳定性 动态性 优化收益 删减法 压缩法 支持粒度 用户体验 动态下发 扩展性 双端复用性 易维护性
23. ① 基于动态下发的 Flutter 包大小优化方案 打包处理 打包 自定义 Gradle Plugin 对 Flutter 产物处理 过滤 build.gradle 中增加 abiFilters 进行过滤,只保留单架构的 so 预处理 将无用的 LICENSE 文件移除,将 assets 中的文件打包 bundle.zip 上传 通过 Dynloader 上传插件将过滤文件从 APK 中移除,上传到 DD 系统托管
24. ① 基于动态下发的 Flutter 包大小优化方案 特殊处理 Flutter 路由拦截 自定义引擎初始化 自定义资源加载
25. ① 基于动态下发的 Flutter 包大小优化方案 自定义引擎初始化 继 承 重 写 start sSettings== null sSettings FlutterMain. startInitilization sSettings=setting System.loadLibrary(“flutter”) ① 一般在 Application 初始化时初始化 sInitalized= =false ② 在 Flutter 页面启动时确保初始化的完成 FlutterMain. sInitialized ensureInitializetionComplete FlutterJNI.nativeInit sInitialized = true Android 侧 Flutter 引擎初始化流程图 End
26. ① 基于动态下发的 Flutter 包大小优化方案 自定义资源加载 我们无法修改 PlatformAssetBundle 原有的资源加载逻辑, 但是我们可以自定义一个资源加载器对其进行替换: 在 Widget 树的顶层通过 DefaultAssetBundle 注入
27. ① 基于动态下发的 Flutter 包大小优化方案 如何将这些模块合理的加以整合呢? 平台团队实现 FlutterDynamic ,其结构图如右:
28. ① 基于动态下发的 Flutter 包大小优化方案 | Android 打包 阶段 Flutter 产物 预处理 java 代码 移除无用的 assets 多架构的 so flutter_assets zip 打包 flutter_assets 过滤多余架构的 so Apk hash 计算 DD 系统 资源与 so 的 移除和上传 瘦身apk 当 Flutter 业务的不断迭代增⻓ 时,Flutter 产物包也会随之不断 变大,最终导致需下载的产物变 运行 阶段 Flutter 路由拦截器 DynLoader 下载管理 自定义引擎初始化 大,也会对下载成功率带来压力 自定义资源加载器 其中的 DynLoader 是平台迭代工程组为公司提供了一套 so 与 assets 的动态下发框架 字体动态加载
29. ① 基于动态下发的 Flutter 包大小优化方案 | Flutter 动态化(Flap)细粒度的动态下发 彻底的动态化方案 视图逻辑一体化 现有代码一键生成可下载执行文件 趋近零成本迁移 原 Flutter 页面 98% 代码无需修改 经受线上大量验证 外卖商家 90% 页面已动态化,共 150+ 页面 配套丰富 IDE 插件语法提示,开发面板辅助,一键锁包及异常定位到代码行号等
30. ① 基于动态下发的 Flutter 包大小优化方案 | Flap 产物(Bundle)体积瘦身 随着动态化业务发展,动态化产物的体积日渐增大, 体积过大会导致下载成功率降低,下载时长超长,增加带宽成本等问题 缩小 Bundle 体积势在必行
31. ① 基于动态下发的 Flutter 包大小优化方案 | Flap 产物(Bundle)体积瘦身 原有方案: JSON 文件 压缩成行 加密 转字节流 所有文件压 缩打包 存在的问题: 单文件加密过后整体做压缩时压缩率基本只能维持在100% 结论: 压缩算法本身有提升空间 JSON 内容冗余:字段名太长、重复内容多
32. ① 基于动态下发的 Flutter 包大小优化方案 | Flap 产物(Bundle)体积瘦身 产物生成阶段 改进方案 JSON 写入 文件 生成 .flap 整个 ZIP 进 行加密 产物使用阶段 重新生 成.flap文件 Bundle 解密 转字节流 字节流转成 JSON 文件 组 JSON 文件 加密成 .flap ① 压缩前不对 JSON 进行处理,以便对 JSON 进行极限压缩; 特点 ② 对压缩后的文件进行加密,保证传输过程中产物安全; ③ 端上 SDK 将解密后的 JSON 再进行一次加密,写到沙盒中,保证本地产物安全。
33. ① 基于动态下发的 Flutter 包大小优化方案 | Flap 产物(Bundle)瘦身收益 25个 Bundle 瘦身前 瘦身后 瘦身率 mine 131KB 29KB 77.86% im_home 335KB 73KB 79.21% … … … … 25个 Bundle 3.6MB 779KB 78.36%
34. ① 基于动态下发的 Flutter 包大小优化方案 | 小结 瘦身效果 瘦身质量 100% 100% 95% 75% 75% 50% 50% 30% 25% 0% iOS 动态下发方案 99.99% 下载成功率 ⻚面加载成功率 25% 0% Android 99.99% Flutter 动态化(Flap)
35. 小Team,木法搞呀? 领导说: • 包大就大吧 • 有没有现成能用的优化方案 • 把咱们体验也搞一搞
36. 业务性能优化怎么做? 问题页面 发现问题 优化工具 优化方式 解决问题 实际操作 验证结果
37. ② 性能优化工具 | CPU 性能优化工具 帧渲染图表 Timeline view CPU 分析器 配置环境
38. ② 性能优化工具 | Skia 性能优化工具 1、 捕捉 Timeline 分析 Flutter 应用的 Skia 调用 flutter run --profile —trace-skia 图表部分的背景色覆盖了 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 前面增加的背景色指令 2、 捕捉 skp flutter screenshot --type=skia —observatory-uri=uri 捕捉 Skpicture 分析每一条绘图指令
39. ③ 性能优化思路 | 减少构建负荷 Root StatefulWidget Root StatefulWidget setState ① 降低遍历的出发点 ② 控制 build 方法的耗时 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 StatefulWidget 仅仅只有 这里需要 刷新 setState StatefulWidget 仅仅只有 这里需要 刷新
40. ③ 性能优化思路 | 提高构建效率 ① 对列表和网格列表懒加载 ② 使用 const 来修饰永远不需要变更的控件 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 ③ 优先使用 StatelessWidget ,而不是全部用 StatefulWidget ④ 使用 Visibility 控件替换 if/else ,或 return 占位控件,树结构不会发生改变
41. ③ 性能优化思路 | 提高绘制效率 Paint RenderObject 对于频繁更新的控件, Layer RepaintBoundary 使用RepaintBoundary 隔离它, 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 让它在一个独立的 paint 区域 RepaintBoundary
42. ③ 性能优化思路 | 提高GPU效率 透明度 Opacity 创建带圆⻆的矩形 应用剪切矩形 AnimatedOpacity FadeInImage borderRadius属性 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 AnimatedBuilder 动画 Clipping 依赖于动画的 Widget 的构造方法 中构建 Widget 树 动画中剪裁 Clip.antiAliasWithSaveLayer 预先构建子树 动画开始之前预先剪切图像 没有 Opacity 那么耗时,但仍然很 耗时,所以请谨慎使用
43. 业务逻辑实现复杂 大量接口和交互 动画、吸顶等效果
44. 优化前 常规优化 优化后 ⻚面组件的刷新范围过大, 导致单帧构建耗时过⻓ 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 商品列表数据解析完成后 触发的组件 build 当前数据变化无关的页面 组件的 build 记录
45. 优化前 动画优化 优化后 在弹窗完全展示的每一帧都会重新 build 弹窗的内容 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 只在首次触发了 弹窗内容的 build
46. 优化前 只是更新了单个商品的内容, 但列表的整体都重新 build 了 列表局部刷新 优化后 a. 单个 Item 构建耗时长 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 达到局部刷新 b. 刷新机制不合理 c. 累加开销大
47. ④ 外卖商家端商品列表性能优化实践 Viewport 的刷新 别 讲 代 码 当 setState 触发 ListView 的 update 时,如果满足条件,即会触发 performRebuild performRebuild 清空 childWidget 缓存 重新 build child Widget update child element 删除 修改 新增 所有子元素的 rebuild 列表刷新耗时较高 与预期不符
48. ④ 外卖商家端商品列表性能优化实践 存在问题 优化方案 修改私有变量势必要重新相关的父类实现 细分场景区分、刷新的细粒度控制等引入了新的工作量 区分 loadMore、insert、delete 等场景对缓存 map 做处理
49. ④ 外卖商家端商品列表性能优化实践 updateChild 的参数包含了 Element 类型的 child 和对应的新的 Widget 配置 newWidget,可划分为四种情况: newWidget 为空 child 为空 newWidget 为空 child 非空 newWidget 非空 child 为空 newWidget 非空 child 非空 Null Remove 新建 更新 直接返回 null 直接调用 通过 inflateWidget 更新 child 树 deactivateChild 方法 创建一个新的 Element 并返回 执行清理工作 并返回 若是不能更新child 且返回null 则通过 inflateWidget 创建一个新的 Element 并返回
50. ④ 外卖商家端商品列表性能优化实践 newWidget 非空,child 非空,则是尽可能更新 child 树,并返回, 若是不能更新 child,则通过 inflateWidget 创建一个新的 Element 并返回。 具体的可细分为三种情况: 继承 SliverChildBuilderDelegate 实现 CacheSliverChildBuilderDelegate; 接受一个 LRU map 用于缓存,并在 Selector delegate 调用 builder 方法做一层代理; 缓存 Element 创建新的 Element 首先判断是否有对应的缓存 Widget,缓存未命中时创建新 Widget 并保存。 性能较优 如果非同一个 widget,但是 Widget.canUpdate(child.widget, newWidget) 为 true ,意味着可以通过 调用 update 来用新的 widget 配置更 新 element 性能最佳 若 newWidget 和 child 本身持有的 widget 是同一个widget,则只是调用 updateSlotForChild 更新 Element 对 应的 slot 性能一般 若非前两种情况,则退化为通过 inflateWidget 创建新的 Element 并 返回
51. ④ 外卖商家端商品列表性能优化实践 其他优化 只刷新当前选中 分帧渲染 Tab 重复 build 通过 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 缓存 widget 解决 Provider 拆分 JSON 懒解析 页面不可见时 滑动时延迟刷新 不刷新 …
52. ④ 外卖商家端商品列表性能优化实践 Android 筛选维度 四个版本完成了第一阶段的优化 优化效果有大幅提升 版本趋势 低端机上流畅度提升更加明显 给用户提供更好的使用体验 收益评估 优化效果
53. 总结 睡着了……刚刚说了啥? 1. “工欲善其事必先利其器”,我们需要先找到适合做优化分析的工具; 2. 其次,优化前期需要“抓大放小”,先解决关键问题,解决那些存在的问题; 当我们真正需要解决线上性能问题的时候,我们需要先准确的采集指标, 然后通过大盘分析,找出其中的问题,除了优化外,我们还要做好防劣化 措施; 3. 优化思想是围绕最核心的关键点展开,我们可以从生活中获得启发;
54. 总结 布局优化 分帧渲染 延迟加载 数据预取 懒解析 按需加载 优秀的 Flutter 代码在减少每次帧渲染过程的更新数据 错峰加载 预构建 并行分解 缓存复用 局部刷新 …
55. 扫他
56.

Accueil - Wiki
Copyright © 2011-2024 iteam. Current version is 2.137.1. UTC+08:00, 2024-11-17 03:57
浙ICP备14020137号-1 $Carte des visiteurs$