作者:光酒

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

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

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

流式页面容器架构设计

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

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

2、在客户端侧,采纳MVVM模型,设计通用的事件协定,形象通用的页面布局、数据管理及事件处理的能力,缩小反复的代码开发,晋升研发效率。在页面布局治理方面,与列表容器PowerScrollView深度联合,实现高效的页面渲染、数据驱动的页面刷新能力;

3.应用阿里巴巴团体 DinamicX作为DSL实现动静模板渲染,满足投放以及经营需要;

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

在流式页面容器架构设计中,重点包含以下几个外围模块:协定层、事件核心和数据中心。上面介绍这几个模板的具体设计。

协定的设计

在页面容器协定的设计方面,在联合闲鱼业务以及阿里巴巴团体的一些技术计划后,闲鱼采纳了三层协定的设计:Page、Section和Component。

  1. Page层协定次要蕴含整个页面Sections信息,以及下拉刷新、上拉加载更多等配置信息;
  2. Section层协定蕴含以后Section的布局信息、初始化Event、LoadMore Event及Components等信息;
  3. Component层协定与具体业务相干,对于容器来说是黑盒的,具体如何渲染会交给业务方解决;默认提供DX解析渲染Handler。

在通信协议的设计上,全副采纳事件传递的形式,包含:客户端与服务端、组件与组件、页面与组件、页面与App之间。这也是云端一体化的设计,实践上开发者只须要思考事件的发送与接管,具体事件的解决在客户端还是在服务端,由对应的Handler决定。在云端一体化的设计下,事件的解决更加灵便,能够更不便地将逻辑后移,当业务产生变更时,缩小对发版的依赖。

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

事件核心的设计

所有皆是event;

在PowerContainer的设计中,所有皆是事件:不论是数据的更新、音讯的传递、网络申请、服务端返回的后果,还是自定义的本地解决逻辑。闲鱼形象定义了八种通用的事件类型,整个页面容器通过事件的流动,实现页面UI的渲染和刷新,以及业务逻辑的表白和执行。

以一次网络申请为例,一次下拉刷新会获取每个Section的initEvent事件,并增加到事件核心;事件核心依据事件类型找到对应的Handler来解决。

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

在解决完远端下发的事件之后,EventCenter会发送事件完结的播送,便于业务解决相干自定义事件。

通用事件形象

上面咱们来具体看一看通用事件的形象:

  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实现视图的部分刷新能力。

动静模板渲染

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

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

在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的业务开发。

关注咱们,每周 3 篇挪动技术实际&干货给你思考!