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 架构模式
相关文章
|
4天前
|
JSON Dart Java
flutter开发多端平台应用的探索
flutter开发多端平台应用的探索
19 6
|
4天前
|
存储 搜索推荐 数据库
MarkLogic在微服务架构中的应用:提供服务间通信和数据共享的机制
随着微服务架构的发展,服务间通信和数据共享成为关键挑战。本文介绍MarkLogic数据库在微服务架构中的应用,阐述其多模型支持、索引搜索、事务处理及高可用性等优势,以及如何利用MarkLogic实现数据共享、服务间通信、事件驱动架构和数据分析,提升系统的可伸缩性和可靠性。
15 5
|
4天前
|
机器学习/深度学习 测试技术 数据处理
KAN专家混合模型在高性能时间序列预测中的应用:RMoK模型架构探析与Python代码实验
Kolmogorov-Arnold网络(KAN)作为一种多层感知器(MLP)的替代方案,为深度学习领域带来新可能。尽管初期测试显示KAN在时间序列预测中的表现不佳,近期提出的可逆KAN混合模型(RMoK)显著提升了其性能。RMoK结合了Wav-KAN、JacobiKAN和TaylorKAN等多种专家层,通过门控网络动态选择最适合的专家层,从而灵活应对各种时间序列模式。实验结果显示,RMoK在多个数据集上表现出色,尤其是在长期预测任务中。未来研究将进一步探索RMoK在不同领域的应用潜力及其与其他先进技术的结合。
25 4
|
4天前
|
JSON Dart Java
flutter开发多端平台应用的探索 下 (跨模块、跨语言通信之平台通道)
flutter开发多端平台应用的探索 下 (跨模块、跨语言通信之平台通道)
|
4天前
|
运维 Cloud Native Devops
云原生架构的崛起与实践云原生架构是一种通过容器化、微服务和DevOps等技术手段,帮助应用系统实现敏捷部署、弹性扩展和高效运维的技术理念。本文将探讨云原生的概念、核心技术以及其在企业中的应用实践,揭示云原生如何成为现代软件开发和运营的主流方式。##
云原生架构是现代IT领域的一场革命,它依托于容器化、微服务和DevOps等核心技术,旨在解决传统架构在应对复杂业务需求时的不足。通过采用云原生方法,企业可以实现敏捷部署、弹性扩展和高效运维,从而大幅提升开发效率和系统可靠性。本文详细阐述了云原生的核心概念、主要技术和实际应用案例,并探讨了企业在实施云原生过程中的挑战与解决方案。无论是正在转型的传统企业,还是寻求创新的互联网企业,云原生都提供了一条实现高效能、高灵活性和高可靠性的技术路径。 ##
12 3
|
11天前
|
传感器 Cloud Native 物联网
Micronaut在物联网中的应用探索:轻盈架构赋能万物互联新时代
【9月更文挑战第6天】Micronaut是一个现代、轻量级的Java框架,以其高效、易用及对云原生环境的支持,在物联网开发中展现出独特优势。它通过AOT编译技术优化应用,减少内存消耗,适合资源受限的设备。Micronaut支持反应式编程和HTTP/2,提升并发处理能力和网络传输效率。本文通过一个温度传感器数据收集服务的例子,展示了如何利用Micronaut简化物联网应用开发,使其成为该领域的理想选择。
28 3
|
14天前
|
编解码 Dart 网络协议
Flutter如何玩转超低延迟RTSP/RTMP播放,跨平台视频流体验大升级,让你的应用秒变直播神器!
【9月更文挑战第3天】Flutter作为谷歌推出的跨平台移动UI框架,凭借高性能和丰富的生态系统广受好评。本文详细介绍如何在Flutter应用中实现低延迟的跨平台RTSP/RTMP播放,并提供具体示例代码。首先介绍了如何使用`flutter_vlc_player`播放RTSP流,然后讨论了优化视频播放以降低延迟的方法,包括调整播放器配置等。通过选用合适的播放器插件并进行优化,Flutter可在视频流播放领域提供卓越的用户体验。随着生态的发展,Flutter有望成为视频流媒体开发的首选框架。
55 6
|
13天前
|
Dart 搜索推荐 API
打造个性化天气应用:从零开始的Flutter之旅
【9月更文挑战第3天】探索Flutter的强大功能,我们将一步步构建一个动态的天气应用。通过这个实践项目,你将学习到如何从无到有地设计用户界面、处理数据流和集成第三方API。本指南适合所有水平的开发者,无论你是Flutter新手还是寻求提高的资深开发者,都能在这里找到价值。让我们开始吧,创造属于你的天气小助手!
|
3月前
|
开发框架 前端开发 测试技术
Flutter开发常见问题解答
Flutter开发常见问题解答
|
4月前
|
前端开发 C++ 容器
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)