Fair2.0专题
● 项目名称:Fair 2.0
● Github地址:https://github.com/wuba/fair
● 项目简介:Fair是为Flutter设计的动态化框架,可以通过Fair Compiler工具对Dart源文件的转化,使项目获得动态更新Widget的能力。Fair 2.0是为了解决 Fair 1.0版本的“逻辑动态化”能力不足。
本文旨在为大家提供 Fair 在实际项目中落地的完整案例,包含了使用 Fair 进行动态页面改造、复杂场景使用、接入过程中遇到的问题、Fair 接入前后的性能对比、热更新方案设计等等内容。
本文重在介绍 Fair 的落地,不会涉及过多的技术原理,如果对 Fair 原理感兴趣的同学,可以阅读以下文章:
本文目录
Fair接入和使用流程
对于 Fair 的接入方式,推荐大家使用 源码依赖 的方式,下载地址如下:
git clone https://github.com/wuba/fair.git
在 pubspec.yaml 文件里添加依赖:
假如 Fair 源码和你的项目代码在同一个目录下
另外需要特别注意的是,Fair 内部通过 fair_version 库来做版本控制,所以大家需要根据自己本机的 Flutter SDK 版本来决定 fair_version 的版本。
以我的电脑为例子,我本地使用的 Flutter SDK 版本为 2.5.0,所以我强制指定 fair_version 的版本为 flutter_2_5_0:
将一个 Widget 改造为可以动态下发的 bundle 需要三步。
第一步:改造 main 函数
需要去掉默认的 runApp(MyApp()),然后手动调用一下 WidgetsFlutterBinding.ensureInitialized(),最后用 FairApp 来包裹项目的根 Widget。
第二步:添加 @FairPatch() 注解
在需要改造的 Widget 上添加 @FairPatch() 注解。
第三步:执行 build_runner 命令
执行了 build_runner 即可生成 bundle 资源
flutter pub run build_runner build
bundle 资源的位置,位于:project -> build -> fair 下。
一个 bundle 资源包含了两个文件:一个是 js 文件,一个是 json 文件。
js 文件里面包含的是动态页面的逻辑部分
json 文件包含的是动态页面的布局 DSL
bundle 资源生成好以后,我们可以使用 FairWidget 组件对其进行加载。
在本地测试时,我们可以将 bundle 资源拷贝到 assets 目录下,然后将 assets 资源的地址通过 path 参数传递给 FairWidget:
运行效果如下:
本地测试通过以后,可以将 bundle 资源托管到自己的服务器上,然后将 path 换成一个 bundle 资源的服务器下载地址即可:
到这里简单小结一下 Fair 的使用步骤:
对于一个简单的页面可以通过以上步骤进行改造,但是实际的项目中,情况往往比较复杂,所以,我们下面继续介绍在一些复杂的业务场景下,如何使用 Fair。
Fair 提供了两个注解,可以实现动态页面之间的参数传递:
@FairWell
@FairProps
这两个注解的区别是:
@FairWell 注解传递的参数不能参与逻辑运算,而 @FairProps 传递的参数可以参与逻辑运算。
@FairWell 支持 Dart 语言的所有数据类型,而 @FairProps 只支持传递一个 Map。
来看看示例代码。
@FairWell 的使用:
@FairProps 的使用:
推荐使用 命名路由 的方式进行跳转。
首先注册路由表:
然后使用 Navigator.pushNamed 进行跳转:
对于一个本地的 Widget,如果直接在动态页面里面引用的话,Fair 无法完成 DSL 的生成,需要使用 @FairBinding 注解来对本地 Widget 组件进行标记。
标记完成后,可以运行 flutter pub run build_runner build 命令,运行成功后,会在项目的 src 目录下,生成本地 Widget 的组件映射表:AppGeneratedModule。
我们需要对 AppGeneratedModule 进行注册:
然后就可以在动态页面里使用了:
那很多人可能会有疑问了,假如我引用的是一个第三方 SDK 里的 Widget 呢?
我们依然使用的是 @FairBinding 注解,不过需要我们将三方 SDK 的 Widget 的 package 路径传递给 FairBinding:
对于页面中一些固定的逻辑方法,我们可以将其抽离出来,放到 FairDelegate 里面,来降低 Dart 与 JS 的频繁通信,达到提升性能的目标。
甚至一些页面,只需要 UI 动态化,而不需要逻辑动态化,那么完全可以使用 FairDelegate 来实现。
使用 FairDelegate 方式很简单,首先自定义一个类,让它继承 FairDelegate:
然后,需要在 FairApp 里注册模板:
我们在动态页面中,经常会用到一些第三方 SDK,比如网络请求、权限申请、拍照功能等等。
此时,我们需要使用 IFairPlugin 来桥接第三方 SDK 的能力,才能在动态页面中正常使用。
我们以权限申请库为例子。
首先,自定义一个类,继承 IFairPlugin:
重写 getRegisterMethods(),在里面注册需要暴露给 JS 侧的方法。
同样的,定义好 Plugin 后,需要在 FairApp 里进行注册:
然后就可以正常使用了。
Fair在58拍客中的落地
我们完成了三个页面的改造:发布页面、视频列表页面、视频详情页面。
并且自己设计了热更新流程,下面,我们就来详细介绍一下。
接入 Fair 前的页面:
接入 Fair 后的页面:
Fair 能做到像素级别的还原,因此接入前后,页面几乎看不出任何差异。
因为目前 Fair 的热更新平台正在开发中,还没有上线,因此,我们自己设计了一套热更新方案:
方案1:同步更新
方案2:静默更新
方案3:预加载+同步更新
方案4:预加载+静默更新
采用同步更新方案时,每次进入页面会先请求版本号,如果有更新,则下载 bundle 资源(如已有缓存则不下载),最后展示。
流程图如下:
采用静默更新方案,每次进入页面时,如有缓存则直接展示,并异步更新 bundle 资源。如无缓存,则使用同步更新。
所以,静默更新有 3 个特点:
对于标记为预加载的资源,我们会在 APP 启动的时候就下载好 bundle 资源。
比如,我们改造 58 拍客的视频列表页,它位于首页的第二个 tab,对于这样的 tab 页,我们当然是希望它在 APP 启动时就进行预加载。
预加载的方案一般不会单独使用,会选择与同步更新和静默更新一起使用。
如果我们在下载 bundle 资源的过程中,因为一些未知原因导致资源未下载完整,或者说下载了一个被恶意替换的资源,此时,去加载这样一个不合法资源的话,会发生一些未知的错误,增加 APP 的风险。
那么如何解决呢?
我们的解决方案是采用验证 校验和 的方式。
我们将 bundle 资源上传到服务器时,会通过 MD5 或 SHA-256 计算出 bundle 资源的 校验和(Checksum),并由热更新接口下发。
客户端完成 bundle 下载后,重新计算一遍得出校验和,如果与服务端下发一致,则为完整且合法的,可以执行加载。
特别注意:上图中展示的校验和是明文的形式,实际开发中,需要大家对其进行加密后再下发。这样做是为了进一步提升数据的安全性。
比如拍客里面,是对其进行了非对称加密后下发,客户端解密后再使用。
在拍客里,我们加入了一个兜底的策略,即:由 Server 端控制加载 bundle 资源还是 Flutter 原生的 Widget。
伪代码如下:
这样做的目的是,当线上发生一些未知的错误而无法纠正时,能够及时控制。
如上图所示,我们在完成改造后,加载页面发现图片变得非常模糊。
之所以会有这个问题是因为,Fair 将 DSL 转化为 Widget 时,默认取的是 assets 目录下的 1x 图,所以导致了在一些高分辨率手机下出现模糊。
解决方案是,1x 图不要使用低分辨率图片,且 Image 需要设置 width、height 和 fit 属性。
我们在代码里设置了一个颜色值:Colors.red[50]:
但是生成 bundle 资源后,发现 DSL 里并没有对于的 color 属性:
出现这个问题的原因是,Fair 暂时不能支持解析 Colors.xx[xx] 这种设置颜色的深度的语法。
推荐使用十六进制颜色值 Color(0xAARRGGBB)的写法。
接入Fair前后性能对比
开发环境:
macOS Big Sur 11.2(Apple M1)
Flutter SDK 2.5.0
测试设备:
HUAWEI nova 7,Android 11
iOS iPhoneXSMax,iOS13.3
测试方式:
非压测,模拟用户正常使用
模拟了12个事件
测试页面:
通过模拟 12 个操作事件,如滑动、点击、跳转来进行测试。
接入 Fair 后,Android 端的内存占用 增加了 22MB,iOS 端增加了 17.9MB。
接入 Fair 后,Android 端的启动时间 增加了 0.03 秒,iOS 端增加了 0.1 秒。
Fair开发体验总结
第一个感受是:保留Flutter原生开发习惯
使用 Fair 不需要学习新的技术栈,也不用适应新的语法习惯,只需要按照 Flutter 原生的写法开发即可。
第二个感受是:改造简单
将 Widget 改造为动态页面,只需要加上几个注解即可,方便、实用。
参考数据:58 拍客接入 Fair + 3 个页面改造排期:2 天
第三个感受是:bundle 资源大小控制合理
58 拍客改造了 3 个页面,这 3 个页面总的 bundle 资源大小才 8KB。
最后,欢迎大家使用 Fair,也欢迎大家为我们点亮 Star
git clone https://github.com/wuba/fair.git
如果想要加入 Fair 的交流群,可以先添加小秘书微信
陈有余:58 同城,Android 高级开发工程师