7

开源|Fair在安居拍房App中的实践

 2 years ago
source link: https://my.oschina.net/u/5359019/blog/5308403
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

开源|Fair在安居拍房App中的实践 - 58技术的个人空间 - OSCHINA - 中文开源技术交流社区

F

Fair2.0专题系列

NO.7 Fair在安居客拍房App中的实践

● 项目名称:Fair 2.0

● Github地址:https://github.com/wuba/fair

● 项目简介:Fair是为Flutter设计的动态化框架,可以通过Fair Compiler工具对Dart源文件的转化,使项目获得动态更新Widget的能力。Fair 2.0是为了解决 Fair 1.0版本的“逻辑动态化”能力不足。

Fair2.0连载公告

关于Fair 2.0详细的设计和原理介绍,请关注后续文章,我们会以连载的方式,逐步公开架构设计原理。

文章开放频率为每周2篇,分别在周二和周四,敬请关注!

《开源公告|Fair2.0逻辑动态化开源了!》

《Fair逻辑动态化架构设计与实现》

《Fair 逻辑动态化通信实现》

《Fair 逻辑语法糖设计与实现》

《Fair 下发产物-布局DSL生成原理》

《Fair 热更新设计与实现》

《Fair 下发产物-逻辑JS生成原理》

《Flutter 动态化项目评测》

随着今年政府对互联网的监管,在不少时候一个紧急需求只给1~2天整改上线,而且整改过程中需求也不是很明确,相关部门也不会给一个详细的需求文档让我们去开发,大家都是“猜测”需求的内容。在这种场景下,如果App具备动态更新的能力,会给公司减少很大的成本。面对需求不确定和紧急修改页面部分元素的能力,给予了动态化最合适的使用场景,而不只是Fix几个BUG。

Fair在58集团内的部分Flutter App中已经落地,终使集成Fair后的App获得了动态化的能力。以下文章内容主要以安居拍房App为例,介绍集成Fair的架构、业务场景所需的能力预埋,以及如何进行原生和动态化代码的维护,持续发挥Flutter的性能。

本篇文章主要介绍Fair在项目中不同场景的使用。具体的技术原理介绍,请查阅前面发布的《Fair逻辑动态化架构设计与实现》《Fair 逻辑动态化通信实现》

现有架构

安居拍房App是采用三端分离的混合开发模式,Flutter产物会以AAR或者Framework的方式集成到Android和iOS原生项目中。

安居拍摄App主要是记录房源信息、拍摄房源图片和VR的功能,如何把现有的Flutter能力,改造成动态代码可调用功能,就需要把网络、权限管理、图片选择、VR拍摄等能力提前预埋,定好通信协议,以便后续动态模块可以正常使用。扩展Fair能力前的架构,如下所示:

ef2cc0ea-83be-4e96-aa0d-eaa7a3e41993.jpg

能力预埋

如上一节所说,如何把一些平台能力提供给Fair动态调用,这部分工作需要提前规划和预埋。下面我们从完整界面路由预埋、动态和原始组合展示、已有组件和模版注册和第三方SDK能力扩展5个方面进行介绍。

2.1 完整界面

统一动态界面注册

FairApp(      child: MaterialApp(        home: ***,        routes: {          ***          // Fair动态页面跳转          'fair_page': (context) => FairWidget(              name: _getParams(context, 'name'),              path: _getParams(context, 'path'),              data: {'fairProps': jsonEncode(_getData(context, _getParams(context, 'name')))}),        },      ),    )

如上所示,Fair的界面调用统一注册在routes里面的fair_page来跳转,根据传入的path和参数来完成对应的动态界面的展示。

统一动态界面调用

// 动态界面Navigator.pushNamed(context, 'fair_page', arguments: {                'name': '动态界面 **',                'path':                    'assets/bundle/lib_src_page_logic-page_sample_logic_page.fair.json',                'data': {"fairProps": {'pageName': '动态界面 **', '_count': 58}}              });

如上所示,跳转到动态界面我们使用Navigator.pushNamed来完成。这里有同学可能会问,一个原生界面不是早把跳转的方式固定写好了吗?这里得益于安居拍房App的Api动态路由的设计,在一个原生界面中,点击跳转的路由都是后端下发的,App根据Api返回路径完成目标界面的跳转。看到这里大家就明白了,Api路由管理除了方便A/B Test,以至于原生与H5、RN、Flutter都可以实现灵活动态切换。如果项目允许,也可以推广这种方案。

2.2 界面部分元素

与整个界面的动态化相比,界面部分元素的动态化,在实际需求场景中遇到比较多。比如需要在原生列表中增加一种类型item,Fair提供了FairWidget,方便跟原生组合显示。下面我们以在列表中预埋一个动态Item为例:

// 列表ListView.builder(                padding: EdgeInsets.only(left: 20, right: 20),                itemCount: _response.list.length,                itemBuilder: (BuildContext context, int position) {                  return getItem(_response.list[position]);                })));// item 构建Widget getItem(var item) {// 根据后端item类型,选择是动态item还是原生item    if (item.type == 'fair') {      // 动态内容      return Container(          alignment: Alignment.centerLeft,          color: Colors.white,          constraints: BoxConstraints(minHeight: 80),          child: FairWidget(            name: item.id,            path: 动态资源名,            data: {**参数**});    } else {      return Column(        // 原生内容      );    }  }

2.3 使用本地Widget组件

Fair除了在Widget文件头部增加@FairPatch()来实现整个界面的动态化转化,还提供了@FairBinding()注解来实现本地Widget注册成动态可使用的组件。

本地Widget转化

// 一个本地Widget界面,提供给界面动态时使用@FairBinding()class CardWidget extends StatelessWidget {  String text;  CardWidget({this.text});  @override  Widget build(BuildContext context) {    return Text(      text,      style: TextStyle(color: Colors.red),    );  }}

编译&注册

// flutter pub run build_runner build 后注册到FairApp中FairApp(child: MyApp(), generated: AppGeneratedModule());

动态界面中使用

@FairPatch()class CardWidgetState extends State<CardWidget> {  @override  Widget build(BuildContext context) {    return Container(      color: Colors.yellow,      child: Column(        children: [          Row(            children: [              CardWidget(text: 'card 1'),            ],          )        ],      ),    );  }}

2.4 逻辑模版使用

由于Fair对原生Flutter类型的支持有限,同时为了避免高频的Dart与JS的通信,我们一般会考虑把算法和交互流程一致的代码,做成固定模版,只把显示相关的部分做成动态的。安居拍房App首页的就是一个任务列表,而且考虑到后续列表的使用场景比较多,我们需要预埋一个逻辑模版,方便后续动态列表的生成。Fair提供了Delegate方便我们做模版扩展,例如下面的下拉刷新列表:

生产模版

class ListDelegate extends FairDelegate {  // 注册列表的构建方法  @override  Map<String, Function> bindFunction() {    var functions = super.bindFunction();    functions.addAll({      '_itemBuilder': _itemBuilder,      '_onRefresh': _onRefresh,    });    return functions;  }  // 通知JS侧 访问新的数据  Future<void> _onRefresh() async {    await runtime?.invokeMethod(pageName, '_onRefresh', null);  }  // 得益于Fair是提供的第一层Widget Tree的组合,FairWidget可以完成动态的Widget的生成  Widget _itemBuilder(context, index) {    var result = runtime?.invokeMethodSync(pageName, '_onItemByIndex', [index]);    return FairWidget(      name: itemData,      path: '***',      data: {'**'})},    );  }}

如上代码所示,像_onRefresh方法由DSL中注册到Flutter ListView,ListView构建回调会自动访问到此方法,于是我们可以使用这些回调方法做一层跟JS侧的通信,来完成界面的数据更新和Item内容的动态展示。

注册模版

FairApp(      delegate: {        'ListLoadMore': (ctx, _) => ListDelegate(),      },      child: MaterialApp(        home: ***      ),    )

如上所示,我们只需要把开发好的模版,注册到delegate中即可在DSL构建ListView的时候注册给系统。

2.5 常用第三方SDK

关于第三方或者自定义插件的使用,在FlutterApp中非常常用。安居拍房App几乎每个界面都需要使用网络,而且由于App的使用场景,拍摄和权限功能,也是必须要提前预埋,方便后续动态化界面的使用。下面我们以权限插件为例,如何扩展提供给动态场景使用。

/// Fair 定义了第三方插件扩展的标准接口,开发者只需实现接口就可以使用底层的JS标准通信,这对开发者来说是无感知的class WBPermission extends IFairPlugin {  Future<dynamic> requestPermission(map) async {    // 根据从JS侧获得的map参数做具体的内容桥接    // 源Permission的状态获取    isGranted = await Permission.photos.request().isGranted;    return Future.value();  }  @override  Map<String, Function> getRegisterMethods() {    // 注册JS可调用的方法    var functions = <String, Function>{};    functions.putIfAbsent('requestPermission', () => requestPermission);    return functions;  }}


集成Fair后的架构

集成Fair动态化SDK后,重点需要考虑对未来能力的思考,把一些平台能力扩展能动态可使用的组件。这里主要包括插件、现有Widget(添加Annotation)和一些模版等能力预埋。扩展Fair能力后的架构,如下所示:

470d25d8-5b28-4bc3-bded-028e05ba02b2.jpg

部分效果展示

4.1 界面

8ff23501-0ef5-467a-8ed7-dd6e265221ba.png

如上图所示,得益于预先扩展了网络动态化支持和动态界面跳转,通过下发的Router协议可以很方便的构建一个完整的订单反馈界面。

4.2 首页列表动态Item

850e4435-fb85-4427-bdb8-853d886ba5d4.png

如上图所示,安居拍房App已经通过Flutter原生开发了一个首页列表,Fair除了支持把整个列表重新动态化,还支持一个更灵活的Item动态化。通过动态化的Item和点击后的动态界面Router跳转,很方便实现动态Item和进入的动态详情界面的功能。

版本管理

Fair是通过Fair Compiler工具对源Dart文件进行转化,生产动态产物的。不像MXFlutter、Kraken基于JS技术栈来实现动态化。项目一旦通过JS去实现,性能的损失是不可逆的,但是Fair就是基于Dart开发,可以在处理紧急需求时,通过动态转化,在正常发版时使用源代码即可。整个版本管理流程如下:

7245c329-a23f-4c4d-a69d-0c96d8e38370.jpg

性能数据

最后我们提供一下安居拍房App集成的一些性能数据,这个也是很多开发者关心的话题。后续Fair团队会提供,Fair与MXFlutter和Kraken的数据对比,敬请关后续的《Flutter动态化项目评测》。

6.1 测试环境和功能

Android

荣耀 v40 Android 10, 内存 8G

iOS

iPhoneXSMax,iOS 13.3, 内存 4G;

Flutter 引擎版本

1.17.3

测试界面

首页混合动态列表(如首页列表动态Item图)

6.2 包体积

Android

增大了13.2M。(我们默认使用两个常用的SO库v7a和v8a,如果只是用一个或者更多数据会有变化)

iOS

净增5.6M(arm64+armV7)

6.3 内存

因为安居拍房是混合开发的App,我们直接从集成Fair打包成AAR或者Farmework集成到原生后,通过Android Studio Profile 和Xcode Instruments 直接观察。

Android

净增20M

iOS

净增17.9M

6.4 启动时间

获取启动时间,我们并没有直接通过Dev Tools取直接获取数据,而是通过录屏截取从点击进入页面数据完整渲染之间的时间。

Android

净增0.05秒

iOS

净增0.1秒

6.5 帧率

界面加载完成后,在动态界面前后,快速滑动获取的数据,我们在Flutter环境时通过Dev Tools获取的数据。

Android

可忽略不计

iOS

可忽略不计

总结

安居拍房App 通过集成Fair获取了动态化的能力,目前项目已经上线并处理了几次小场景的动态化需求。在集成Fair后,建议大家能及时梳理出后续可能使用的动态化能力,比如常用的网络、权限、存储和图片选择等等,以免在使用时发现没有适配支持。Fair直接提供Widget级的动态化,无论在完整或者部分界面动态化使用场景都具备灵活性,建议大家使用。

有奖互动

Perseverance Prevails

关于  Fair在安居客拍房App中的实践  有任何疑问或想法 欢迎在评论区留言 我们将选取5~10条留言送出58精美周边——58帮帮手机支架 同时项目成员将从中挑选出1~3条精华留言
精华留言将额外获得 ¥109元天猫精灵硬糖智能蓝牙音响

点击阅读原文链接直接进入Github

请为我们点亮star吧!

本文分享自微信公众号 - 58技术(architects_58)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK