关于flutter:Flutter-在流式场景下的架构设计与应用

9次阅读

共计 5789 个字符,预计需要花费 15 分钟才能阅读完成。

作者:光酒

目前,闲鱼的次要业务场景都是基于流式场景构建的。在闲鱼的次要几个业务场景下存在两种类型的页面:一种是简单交互的页面,如发布页面、商品详情页;另一种是轻交互、须要肯定动态化能力满足千人千面的经营配置及疾速 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 篇挪动技术实际 & 干货给你思考!

正文完
 0