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

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 减少发版依赖,提升研发效能

作者:闲鱼技术——光酒

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

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

在研发效能、交付效率方面,业务的变化往往依赖于版本发布,动辄两周的发版周期,对于需要快速投放和响应的业务来说,上线时间过长将难以接受。

能否设计一种流式页面搭建能力,实现页面的快速搭建,减少重复代码,提升研发效能;提供业务动态化的能力,减少对发版发布的依赖,提高上线的交付效率?

为了解决以上问题,在Flutter版本首页改版的契机下,闲鱼设计了一套流式场景下的页面搭建架构设计。

流式页面容器架构设计

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

  1. 在搭建平台侧,实现页面搭建、组件管理、协议编排等能力,与投放平台、A/B实验平台和监控平台打通;
  2. 在客户端侧,采用MVVM模型,设计通用的事件协议,抽象通用的页面布局、数据管理及事件处理的能力,减少重复的代码开发,提升研发效率。在页面布局管理方面,与列表容器PowerScrollView深度结合,实现高效的页面渲染、数据驱动的页面刷新能力;
  3. 使用阿里巴巴集团 DinamicX作为DSL实现动态模板渲染,满足投放以及运营需求;
  4. 在与服务端通信协议方面,闲鱼一直在实践Flutter+FaaS的云端一体化开发,借助FaaS的能力,定义一套云端一体化的事件协议,解决业务逻辑动态化的问题,减少发版依赖,进而提升交付效率。

arch

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

协议的设计

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

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

protocol

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

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

事件中心的设计

一切皆是Event;

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

event

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

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

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

event remote

通用事件抽象

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

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

render

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

动态模板渲染

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

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

event

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

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
9天前
|
存储 调度 数据安全/隐私保护
鸿蒙Flutter实战:13-鸿蒙应用打包上架流程
鸿蒙应用打包上架流程包括创建应用、打包签名和上传应用。首先,在AppGallery Connect中创建项目、APP ID和元服务。接着,使用Deveco进行手动签名,生成.p12和.csr文件,并在AppGallery Connect中上传CSR文件获取证书。最后,配置签名并打包生成.app文件,上传至应用市场。常见问题包括检查签名配置文件是否正确。参考资料:[应用/服务签名](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-signing-V5)。
34 3
鸿蒙Flutter实战:13-鸿蒙应用打包上架流程
|
4天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
5天前
|
网络协议 数据挖掘 5G
适用于金融和交易应用的低延迟网络:技术、架构与应用
适用于金融和交易应用的低延迟网络:技术、架构与应用
26 5
|
6天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
3天前
|
监控 持续交付 Docker
Docker 容器化部署在微服务架构中的应用有哪些?
Docker 容器化部署在微服务架构中的应用有哪些?
|
3天前
|
监控 持续交付 Docker
Docker容器化部署在微服务架构中的应用
Docker容器化部署在微服务架构中的应用
|
11天前
|
机器学习/深度学习 人工智能 自然语言处理
医疗行业的语音识别技术解析:AI多模态能力平台的应用与架构
AI多模态能力平台通过语音识别技术,实现实时转录医患对话,自动生成结构化数据,提高医疗效率。平台具备强大的环境降噪、语音分离及自然语言处理能力,支持与医院系统无缝集成,广泛应用于门诊记录、多学科会诊和急诊场景,显著提升工作效率和数据准确性。
|
11天前
|
JavaScript 持续交付 Docker
解锁新技能:Docker容器化部署在微服务架构中的应用
【10月更文挑战第29天】在数字化转型中,微服务架构因灵活性和可扩展性成为企业首选。Docker容器化技术为微服务的部署和管理带来革命性变化。本文探讨Docker在微服务架构中的应用,包括隔离性、可移植性、扩展性、版本控制等方面,并提供代码示例。
48 1
|
13天前
|
存储 Dart
Flutter&鸿蒙next 实现一个计算器应用
本文介绍了如何使用 Flutter 创建一个简单的计算器应用,包括基本的加减乘除运算。文章详细讲解了界面布局、计算逻辑和状态管理的实现步骤,通过具体的代码示例展示了如何构建计算器界面、处理用户输入和显示计算结果。
60 0
|
Dart 开发者
【Flutter】Flutter 应用生命周期 ( 前台状态 resumed | 后台状态 paused | 非活动状态 inactive | 组件分离状态 detached )(二)
【Flutter】Flutter 应用生命周期 ( 前台状态 resumed | 后台状态 paused | 非活动状态 inactive | 组件分离状态 detached )(二)
406 0
【Flutter】Flutter 应用生命周期 ( 前台状态 resumed | 后台状态 paused | 非活动状态 inactive | 组件分离状态 detached )(二)