阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

Flutter 在流式场景下的架构设计与应用

  • 2021-01-29
  • 本文字数:4789 字

    阅读完需:约 16 分钟

Flutter在流式场景下的架构设计与应用

目前,闲鱼的主要业务场景都是基于流式场景构建的。在闲鱼的主要几个业务场景下存在两种类型的页面:一种是复杂交互的页面,如发布页面、商品详情页;另一种是轻交互、需要一定动态化能力满足千人千面的运营配置及快速 A/B 实验需求的页面,如首页、搜索页面、我的等页面。


在这些轻交互、动态化运营的页面场景下,有很多共通的处理逻辑:页面的布局、数据的管理、事件逻辑驱动的数据变化以及数据驱动的视图状态更新;这些工作往往大部分都是重复的工作,重复的代码逻辑。

在研发效能、交付效率方面,业务的变化往往依赖于版本发布,动辄两周的发版周期,对于需要快速投放和响应的业务来说,上线时间过长将难以接受。为了解决以上问题,在 Flutter 版本首页改版的契机下,闲鱼设计了一套流式场景下的页面搭建架构设计。

流式页面容器架构设计

在流式布局的架构设计过程中,面对实际的业务场景,通过以下几个方面解决端到端的流式页面容器设计:

1.在搭建平台侧,实现页面搭建、组件管理、协议编排等能力,与投放平台、A/B 实验平台和监控平台打通;

2.在客户端侧,采用 MVVM 模型,设计通用的事件协议,抽象通用的页面布局、数据管理及事件处理的能力,减少重复的代码开发,提升研发效率。在页面布局管理方面,与列表容器 PowerScrollView 深度结合,实现高效的页面渲染、数据驱动的页面刷新能力;

3.使用阿里巴巴集团 DinamicX 作为 DSL 实现动态模板渲染,满足投放以及运营需求;

4.在与服务端通信协议方面,闲鱼一直在实践 Flutter+FaaS 的云端一体化开发,借助 FaaS 的能力,定义一套云端一体化的事件协议,解决业务逻辑动态化的问题,减少发版依赖,进而提升交付效率。

null


在流式页面容器架构设计中,重点包括以下几个核心模块:协议层、事件中心和数据中心。下面介绍这几个模板的详细设计。

协议的设计

在页面容器协议的设计方面,在结合闲鱼业务以及阿里巴巴集团的一些技术方案后,闲鱼采用了三层协议的设计:Page、Section 和 Component。


1.Page 层协议主要包含整个页面 Sections 信息,以及下拉刷新、上拉加载更多等配置信息;

2.Section 层协议包含当前 Section 的布局信息、初始化 Event、LoadMore Event 及 Components 等信息;

3.Component 层协议与具体业务相关,对于容器来说是黑盒的,具体如何渲染会交给业务方处理;默认提供 DX 解析渲染 Handler。

null


在通信协议的设计上,全部采用事件传递的方式,包括:客户端与服务端、组件与组件、页面与组件、页面与 App 之间。这也是云端一体化的设计,理论上开发者只需要考虑事件的发送与接收,具体事件的处理在客户端还是在服务端,由对应的 Handler 决定。在云端一体化的设计下,事件的处理更加灵活,可以更方便地将逻辑后移,当业务发生变更时,减少对发版的依赖。

接下来就让我们来具体看一看事件中心的设计。

事件中心的设计

一切皆是 event

在 PowerContainer 的设计中,一切皆是事件:不论是数据的更新、消息的传递、网络请求、服务端返回的结果,还是自定义的本地处理逻辑。闲鱼抽象定义了八种通用的事件类型,整个页面容器通过事件的流动,完成页面 UI 的渲染和刷新,以及业务逻辑的表达和执行。

null


以一次网络请求为例,一次下拉刷新会获取每个 Section 的 initEvent 事件,并添加到事件中心;事件中心根据事件类型找到对应的 Handler 来处理。


如果 initEvent 配置的是 remote 请求,则交给 remoteHandler 发送网络请求,将事件传送给 FaaS 端;在 FaaS 端收到 Event 后,在 FaaS 端的事件中心分发,找到对应的 hsf 服务并获取数据,最后拼装成 Event 的方式,下发给客户端;客户端接收到之后继续让 Event 在事件中心流动起来。


在处理完远端下发的事件之后,EventCenter 会发送事件结束的广播,便于业务处理相关自定义事件。

null

通用事件抽象

下面我们来具体看一看通用事件的抽象:


1.Restart 事件:指定整个 Page 或者某个 Section 的刷新事件,对于需要刷新的 Section,会将其 initEvent 事件加入事件中心。initEvent 常见的一般为一个 Remote 事件,也可以是任意其他事件。

2.LoadMore 事件:LoadMore 事件主要处理分页加载更多数据的场景。

3.Update 事件:Update 事件主要处理数据源的更新及 UI 的刷新。

4.Context 更新事件:每个 Section 都存在一个 Context 信息,代表了服务端与客户端请求的上下文信息;每个 Section 的 Rmote 事件请求,都会默认将 Context 信息发送给服务端,相应的服务端可以下发 Context 事件更新指定 Section 的 Context 信息;具体使用场景例如分页加载的 page number 等;

5.Replace 事件:Replace 事件替换 Section 信息,在 tab 切换等场景使用会使用;

6.Remote 事件:远端请求事件;

7.Native 事件:本地通用事件,如页面跳转、toast 提示、数据埋点等;

8.Custom 事件:版本预埋的业务自定义事件。

数据中心的设计

在 MVVM 架构中,数据中心承担着 ViewModel 的角色,处理 Update 事件,主要负责数据的更新及 UI 视图的刷新。对于数据的 Update 事件,闲鱼根据自身业务场景抽象了几种通用的数据更新类型:overload、patch、override 和 remove。在 UI 渲染方面,闲鱼将列表容器 PowerScrollView 与动态模板渲染 DXFlutter 相结合,实现页面渲染及数据更新后的页面刷新能力。

列表容器

PowerScrollView 是闲鱼实现的一套功能完善、高性能的列表布局容器,满足了页面容器对于瀑布流、卡片曝光、锚点定位等能力的需求。在视图渲染刷新方面,PowerScrollView 提供了列表的局部刷新能力,完美地解决了数据更新后视图的刷新问题。


在协议设计上,二级协议 Section 以及 Footer、Header 的设计与 PowerScrollView 的设计是一一对应的。二级协议 Section 定义了唯一标识 Key,在 UI 渲染中,对应到 PowerScrollView 的 SectionKey。在数据更新后,页面容器会根据 Section Key 实现视图的局部刷新能力。

null


关于 PowerScrollView 的详细设计和介绍可以参考闲鱼技术公众号的文章

动态模板渲染

DXFlutter 使用阿里巴巴集团 DinamicX 作为 DSL,在 Flutter 端实现了高效的动态模板渲染的能力。闲鱼使用 DXFlutter 实现 Component 层协议的动态模板渲染。


在介绍协议设计时提到过,Component 层协议对于页面容器来说是黑盒,那么 DX 卡片事件是如何与页面容器 PowerContainer 打通的呢?黑盒的数据又是如何更新的呢?


null

在 DSL 中,闲鱼自定义了页面容器 PowerContainer 的事件 powerEvent,通过它可以生成页面容器的通用事件类型,将 DinamicX 卡片的事件与页面容器事件中心打通。以上面代码为例,点击“删除关注列表里面的推荐卡片”的场景,只需要在 onTap 的事件中定义一个 update 类型的事件,subType 为 remove,即可实现数据的删除及删除后 UI 的渲染。


然而这里没有定义任何标识,且列表中可以存在多个相同的卡片,又是怎么知道要操作的是哪一份数据呢?


这里为每个 Component 生成一个唯一的 ComponentKey,根据 SectionKey+ComponentKey 生成卡片的唯一标识。在每一个 powerEvent 事件中,会将 Key 传入事件中心,这样就定位到任意一个 Component 的数据 model,根据事件类型更新数据 model。同时,PowerScrollView 也可以通过这个 Key,操作 UI 的局部刷新。

Section 状态管理

页面加载的过程中,往往需要展示一些加载状态的处理,如加载中的 Loading 动画、加载失败状态的重试按钮、没有更多内容状态的提示信息等。


在协议的设计方面,每个 Section 定义了 state,在事件中心处理 Remote 请求事件和应答事件时,更新 Section 的 state。通过注册 render handler,针对 Section 的不同状态返回加载状态 Widget。

void updateSectionState(String key, PowerSectionState state) {    final SectionData data = _dataCenter.findSectionByKey(key);      if (state == PowerSectionState.loading) {         // 从ViewCenter的config获取loadingWidget          final Widget loadingWidget = _viewCenter?.config?.loadingWidgetBuilder(this, key, data);          // ViewCenter调用replace section方法更新UI          _viewCenter.replaceSectionOfIndex(loadingWidget);        // 标记需要刷新Section        data.needRefreshWhenComplete = true;      } else if (state == PowerSectionState.error) {          ...      } else if (state == PowerSectionState.noMore) {          ...      } else if (state == PowerSectionState.complete) {        if (data.needRefreshWhenComplete ?? false) { // 判断是否需要更新Section              final int index = _dataCenter.fineSectionIndexByKey(key);              if (index != null) {                final SectionData sectionData = _dataCenter.containerModel.sections[index];                final PowerSection section = _viewCenter.initSection(sectionData);                _viewCenter.replaceSectionOfIndex(index, section);              }              data.needRefreshWhenComplete = false;        }      }}
复制代码

Section 状态变化之后,通过 PowerScrollView 提供的 replaceSection 方法,刷新 UI 视图。

Tab 容器支持

在闲鱼首页的场景,页面容器需要支持 Tab 容器的布局能力。PowerContainer 又是如何支持 Tab 容器的支持呢?


闲鱼在 Section 的协议中引入了插槽(Slot)的概念,当搭建页面时,会指定 Tab 容器的 Slot Section,默认不展示任何信息的空插槽。每一次切换 Tab 容器,通过 Replace 事件修改页面容器的 Section 信息。


void replaceSections(List<dynamic> sections) {    if (sections == null || sections.isEmpty || _dataCenter?.containerModel?.sections == null) {          return;    }    for (int i = 0; i < sections.length; i++) {          SectionData replaceData = sections[i];          assert(replaceData.slot != null);          // 寻找Section list中与Slot匹配的index          int slotIndex = _findSlot(replaceData);          // 更新dataCenter          _dataCenter.replaceSectionData(slotIndex, replaceData);           // SectionData 转换为PowerScrollView所需的PowerSection           final PowerSection sectionData = _viewCenter?.convertComponentDataToSection(replaceData);           // 更新viewCenter           _viewCenter?.replaceSectionOfIndex(slotIndex, sectionData);           //将替换Section的Restart事件发送到event center           sendEventRestart(replaceData.key);    }}
复制代码


PowerScrollView 同样也提供了 replaceSection 方法,与上文提到的 Section 状态管理相结合,完美地解决了 tab 容器的切换和加载状态管理的问题。

总结和展望

本节主要介绍了在轻交互、动态化运营场景下,如何从页面搭建、协议设计、端侧容器的实现、动态模板渲染、云端一体的事件交互等方面,设计并实现一套流式页面搭建能力,实现页面的快速搭建,提升研发效能。同时,提供业务动态化的能力,减少发版发布的依赖,提高上线的交付效率。


目前,页面容器 PowerContainer 在闲鱼首页 Flutter 版本重构中设计并落地。使用 PowerContainer 后,极大地降低了首页三个 tab 页面的重复代码,代码逻辑统一管理,降低了一半的人日工作量。在性能方面,有了 PowerScrollView 的局部刷新、Element 复用、分帧渲染、差值器等方面的优化,在流畅度方面要优于原生的体验。


没有银弹!这样一套页面容器的设计并不是为了适用所有的业务场景,更适合以展示为主、轻交互动态化运营的业务场景。云端一体的事件协议,在服务端需要事件协议的封装,这也使得与 Serverless 的场景更加适合。


闲鱼未来还会在搭建平台侧做更多的尝试,真正实现所见即所得的快速页面搭建能力。在业务逻辑动态化方面,目前更多的是通过与 FaaS 的结合、逻辑后移的方式解决。但目前仍然无法解决本地自定义事件依赖发版的问题,未来在这方面也会有更多的尝试,做到 less code 甚至是 no code 的业务开发。


本文转载自:闲鱼技术(ID:XYtech_Alibaba)

原文链接:Flutter在流式场景下的架构设计与应用

2021-01-29 13:002051

评论

发布
暂无评论
发现更多内容

元宇宙背景下——内容生产传播商业价值分析2022

易观分析

元宇宙 内容生产传播

如何使用 Serverless Devs 部署静态网站到函数计算

阿里巴巴云原生

自助洗车机洗车多少钱一次?利润有多少

共享电单车厂家

自助洗车加盟 自助洗车机洗车 自助洗车多少钱一次

TASKCTL 调度高可用架构服务与安装

TASKCTL

kettle 批量任务 调度引擎 ETL 调度任务

TASKCTL的单机与分布式部署,如何启动服务和代理节点监听

TASKCTL

批量任务 调度引擎 ETL 自动化运维 调度任务

PlatoFarm生态NFT总量恒定,激励机制让Dao成员持续贡献

小哈区块

关于 RocketMQ Summit 的延期通知

阿里巴巴云原生

走进直播间——智能自动化助力政企数字化转型

浪潮云

云计算

漏洞挖掘之Spring Cloud注入漏洞

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

快速了解日志概貌,详细解读13种日志模式解析算法

云智慧AIOps社区

算法 运维 安全 监控 日志

加盟自助洗车需要人工全天看守吗

共享电单车厂家

24小时无人自助洗车 加盟自助洗车

模块三作业

HZ

架构实战营 #架构实战营

技术干货| 如何在MongoDB中轻松使用GridFS?

MongoDB中文社区

mongodb

腾讯WeTest通过TMMi 3级认证

WeTest

中兴通讯加入星策开源社区 携手推动企业智能化转型建设

星策开源社区

人工智能 机器学习 开源社区 企业转型

生产到一半改工艺生产为另一个产品的业务方案探讨

秋去冬来春未远

生产改单 生产执行 生产拆单

线上活动| 阿里云、亚马逊云与MongoDB的大佬带你来涨知识!

MongoDB中文社区

mongodb

基于数据技术全域流量协同优化是银行零售业务关键突破点

易观分析

银行 数据技术

如何通过 Node 上传小程序代码,不会就看看吧

CRMEB

ironSource Luna 正式推出针对苹果搜索广告的自动化投放工具

Geek_2d6073

iOS编码规范

刁架构

规范 iOS编码规范

EventBridge 与 FC 一站式深度集成解析

阿里巴巴云原生

K8s 基于 EFK 的日志解决方案介绍

移动云大数据

elasticsearch Kibana Fluentd

无人洗车店生意怎么样?想加盟自助洗车

共享电单车厂家

加盟自助洗车 无人自助洗车加盟 无人洗车店生意

首届全球基础软件创新大会明天开幕!

OpenAnolis小助手

开源 操作系统 龙蜥社区 国产 基础软件创新大会

开个自助洗车要多少钱?主要费用有哪些?

共享电单车厂家

自助洗车加盟 开个自助洗车

怎么加盟6元自助洗车?一起来了解下

共享电单车厂家

自助洗车怎么加盟 6元自助洗车 加盟自助洗车 6元自助洗车项目

直播预告 | PolarDB-X 动手实践系列——用 PolarDB-X 搭建一个高可用系统

阿里云数据库开源

MySQL 数据库 阿里云 开源 PolarDB-X

花14天梳理了3月份各大厂问得最多的50道Java基础面试题

北游学Java

Java 面试

PlatoFarm生态NFT总量恒定,激励机制让Dao成员持续贡献

西柚子

Java运算符、输入、选择流程控制详细讲解

五分钟学大数据

Java 4月月更

Flutter在流式场景下的架构设计与应用_大前端_闲鱼技术_InfoQ精选文章