Tetris Native 揭秘|有道词典动态化运营引擎
Tetris Native 是有道词典端侧动态渲染引擎,目前已作为多个业务的运营投放容器,支持跨端 UI 动态化发布及多种样式,助力有道词典流量变现。《Tetris Native 揭秘》系列文章将详细介绍 Tetris Native 的设计理念和详细落地方案。
在介绍 Tetris Native 之前,先给 Tetris 打个广告。Tetris 是 H5 低代码平台,一直以来都被有道词典广泛使用。该平台已经在我们的会员、课程、社区等业务投放页中得到应用,显著提升了运营和市场投放页面的开发迭代效率。转正请注明来源:「申国骏」
Tetris 页面展示
Tetris Native 是一套动态化投放技术方案,旨在提升运营和广告投放效率。与 Tetris 不同的是,Tetris Native 在客户端本地进行渲染,它属于端侧的服务驱动 UI(Server-Driven UI)技术。服务端下发的数据包括图片、文本、视频和卡片等 UI 组件的样式布局及内容。通过服务驱动 UI 的方式,我们能够在客户端不需要更新版本的情况下,新增或调整现有页面的 UI 组件内容和布局,实现快速的运营开发迭代。
我们先来一睹为快,下面是有道词典线上投放的动态化运营版面展示。
案例一:有道词典头部动态版面运营位
案例二:有道词典底部动态版面运营位
案例三:线上其他动态化版面展示
如上述案例所示,Tetris Native 已在有道词典中大量使用。就使用方式而言,针对不同的业务场景,可以有三种使用方式。
三种使用方式
第一种方式:服务端下发带有业务数据的页面描述 DSL,客户端进行渲染。优势:在云端合成业务数据和页面样式描述 DSL,客户端渲染 DSL JSON。样式修改或新增只需更改投放后台,无需客户端发版。适合样式需要频繁修改的业务,但要求投放后台和服务高度配合,后台需处理业务数据的同时考虑端侧布局样式构建。目前主要用于首页快速运营迭代的地方。
第二种方式:服务端下发纯业务数据,客户端构建页面渲染 DSL 后进行渲染。优势:服务端仅下发业务数据,无需额外改动。客户端结合业务数据生成页面描述 DSL 并进行渲染。与第一种开发模式相比,投放后台和服务端无感知,不过依赖客户端发版。客户端可通过构建 DSL JSON 实现双端样式一致,重用已有 UI 逻辑,降低业务开发难度。适合日常样式改动较少的页面。
第三种方式:服务端下发纯业务数据和绘制 DSL 渲染 JS 脚本,端侧执行 JS 脚本生成页面渲染 DSL 后进行渲染。优势:不需改动现有服务端业务数据逻辑,与第二种方式类似对投放后台和服务端无感知,但不依赖客户端发版。客户端通过执行 JS 脚本生成 DSL JSON 来实现动态修改样式需求,无需侵入现有服务端和投放系统逻辑。适用于业务数据变化不大的情况下修改样式,不过这对端侧渲染性能有一定影响。目前尚未在线上使用。
以上介绍了动态渲染引擎 Tetris Native 的三种使用方式,以及它们的优缺点和适用业务场景。接下来,我们将回顾设计动态渲染引擎的初衷,解决的业务困境,需求分析和技术选型过程,以及介绍目前 Tetris Native 的整体开发进度。
需求分析
在使用动态化投放方案之前,我们面临的主要问题是无法快速进行投放试验。由于不同的投放样式需要进行端侧开发和发布,整个流程较为耗时。尤其是在首页设计了近 20 种不同的投放样式时,按照传统的开发流程,需要在安卓和 iOS 端各开发 20 种样式。而且,如果日后需要对这些样式进行修改或新增,还需要进行额外的发布。显然,传统的开发方式需要转变为更加灵活的方式。总结来说,我们面临的问题主要有两个方面:一是传统开发方式无法支持运营快速进行投放试验;二是研发效率和上线流程妨碍了快速迭代的实现。
运营投放具有以下特点:主要以卡片组合为投放样式,每个卡片承载不同的业务投放。一个投放版面由多种业务卡片组合而成,页面包含多个投放版面。单个业务素材会在不同样式的卡片中出现。
素材、卡片、版面、页面之间的关系
针对上述的业务需求,因为运营投放的样式多为卡片的组合,我们可以考虑开发一套以卡片为基础的动态运营投放系统。由于我们业务的落地页基本支持 deeplink 的跳转方式,因此我们仅需要处理卡片的 deeplink 跳转以及对应的展示和点击统计逻辑即可。
对于端侧,可以总结有以下的技术需求:
支持动态发布(服务端下发卡片和素材内容及布局)
支持多种卡片展示样式(平铺、横竖滚动、横竖列表等)
支持多种卡片内容格式(文本、图片、动图、视频等)
渲染速度快
服务端下发数据尽量少保证传输速度快
对卡片展示和点击进行自动上报
对于投放后台,有以下技术需求:
支持创建素材并绑定素材跳转的 deeplink 落地页
支持编辑投放的版面样式,并绑定对应的素材
生成素材内容和样式描述 DSL,加入需要统计上报字段
支持编辑投放的生效规则和投放策略
在明确了上述的技术需求之后,我们对业内不同的技术方案进行评估,考虑其可行性、适用性和性能等因素。这样的分析对比有助于我们选择最适合我们需求的技术方案,并为后续的系统设计和开发提供指导。
技术选型
就实现客户端无需发版即可实现 UI 动态渲染而言,有多种技术选择。首先,可以采用像 React Native 或 WEEX 这类类似 Web 开发的方式。这种方法允许在移动端使用前端框架如 Vue 进行 UI 渲染,提供了丰富的 UI 控件,并具备成熟的社区支持。然而,与原生控件相比,性能略有损耗,而且存在一些交互差异。
另一种选择是使用 Flutter 动态化,例如 58 的 Fair、腾讯的 mxflutter 以及阿里的 karken。这些方法支持前端开发方式,并通过底层调用 Flutter 实现跨平台动态渲染。然而,这些方案在成熟度和开发维护成本方面尚有待改进。
还有一种方法是使用 Webview 来解决 UI 动态修改而无需发版的问题,但 Webview 的性能较差,不适合在高频用户路径中直接使用。
最后,可以考虑采用服务驱动 UI(Server-Driven UI)的方案,如阿里的 Tangram 和优酷的 Gaia-X。这些方法通过 DSL 描述来指导客户端构建原生 UI,需要对支持的 UI 控件进行 DSL 定义,然后在客户端进行视图构建、布局和渲染。虽然这些方法目前支持的 UI 控件较少,但由于使用原生控件,性能较好。
考虑到我们项目的需求,主要以卡片组合形式为主,并且对于 UI 交互要求不太复杂。同时,我们对于性能和开发效率有着较高的要求。基于这些考虑,我们决定采用服务驱动 UI 的技术方案来推进项目。接下来,我们将对服务端驱动 UI 技术方案进行详细的业内方案对比。
在服务端驱动用户界面(Server Driver UI)的技术方案中,我们需要仔细考虑两个关键方面,即渲染引擎和领域特定语言(DSL)的选择。这两个方面的决策将直接影响我们后续的开发方案。因此,我们需要对这些方面进行深入的分析和评估,以确保最终选择的方案能够满足我们的需求并具备高度的可扩展性和灵活性。渲染引擎的选择涉及到考虑性能、跨平台支持、可定制性以及对现有技术栈的集成能力等因素。而在选择 DSL 语言时,我们需要评估其表达能力、易用性、可维护性以及对业务逻辑的良好支持等特性。通过深入研究和比较,我们将能够制定出一个既具备技术优势又能够满足业务需求的综合解决方案。
基于 Flex 布局框架
开源的跨平台 Flex 布局引擎,例如facebook/yoga以及Tencent/Taitank,提供了底层的跨平台渲染布局引擎。利用这些引擎可以实现跨平台的动态渲染框架。使用这种技术栈比较有代表性的动态化框架有美团的MTFlexbox以及知乎的Morph。这些框架的整体架构逻辑如下所示。
基于 flex 跨端框架
在这个框架下,由于底层依赖于 Flex 的跨端渲染引擎,因此所有 UI 布局的描述都必须采用 Flex 布局方式。Flex 跨端布局引擎具有成熟可靠的优势,且 Flex 布局描述通用易懂。然而,与常规约束布局相比,将样式转化为 Flex 布局描述需要额外工作,并且不可避免地增加了 View 的层级嵌套,增加了整体 DSL 描述的复杂度和数据传输量。同时,facebook/yoga以及Tencent/Taitank有别于我们原生开发的 View 体系,在布局问题的维护和调试方面较为困难。
基于声明式 UI 框架
随着 Jetpack Compose 和 SwiftUI 等声明式 UI 框架的广泛应用和不断发展,将 DSL 转换为声明式 UI 框架成为一种可行的选择。特别是 Compose Multiplatform 的出现,理论上可以将 DSL 转换为适用于多平台的声明式 UI。然而,目前引入 Jetpack Compose 和 SwiftUI 并没有明显的收益,声明式 UI 的引入会增加应用程序的包体积并影响启动速度。因此,我们目前没有采用这一方案。尽管一些技术论坛上存在相关的尝试和分享,例如:《Jetpack Compose Enables JSON Defined View Layout》,但目前业内还没有一个完整的基于声明式 UI 的动态化框架。声明式 UI 框架是未来的发展趋势,我们将继续关注,并在认为基于声明式 UI 的动态化方案具有额外优势,如性能和维护性等方面时考虑进行切换。
基于原生 UI 框架
基于原生 UI 框架是指我们将 DSL 转换绑定到安卓和 iOS 的原生 View 中,并通过原生布局的方式或者引入对应平台的 Flex 布局对 View 进行渲染。采用这种方式虽然要求各端编写相应的代码来创建和渲染 DSL 对应的 View,不过这种方式有以下几个好处:
更接近熟悉的原生开发,无需额外学习成本
性能与原生一样快
在 View 的扩展和维护方面相对容易
可与现有的原生控件页面很好集成,具有良好的互操作性。
基于上述考虑,我们选择了基于原生的 View 框架作为底层布局和渲染的基础。业内有两个具有影响力的基于原生 UI 框架的动态化项目,分别是天猫的 Tangram(alibaba/Tangram-Android、alibaba/Tangram-iOS)以及优酷的alibaba/GaiaX。我们分别对这两个项目进行详细的分析。
Tangram
Tangram 是阿里生态中淘宝 &天猫开源的动态化渲染方案。该项目的主要代码贡献发生在 2017 年至 2019 年期间,目前已停止维护。Tangram 实现了基于列表的多样式卡片式布局,支持一拖 N、悬浮和吸顶效果,以及轮播等卡片样式的动态化布局。它包含了 JSON 样式解析器、数据解析绑定器、VirtualView 渲染框架、VirtualLayout(LazyScroolView for iOS)布局框架、预设组件卡片库,以及布局预览、曝光点击处理等辅助工具。整体流程可参考下图:
Tangram 工作流程
Tangram 的实现思路给我们提供了有价值的启示和帮助,但它整体效果并不完全满足我们自身的业务需求。此外 Tangram 已处于停止维护状态,其代码逻辑庞大且复杂。因此,在 Tetris Native 中,我们只是借鉴了 Tangram 一些 View 生成的流程和思路,并没有基于它进行开发。
GaiaX
GaiaX 是阿里生态中优酷开源的跨端动态化模板引擎,配套有卡片编辑器,文档较全且在活跃迭代。目前,网易云音乐也采用该框架来实现首页的动态化。整体思路与我们的目标相似,即通过将 DSL 转换为相应的原生业务卡片 UI 控件。其实现的整体思路和方案与 Tetris Native 类似,也是将 DSL 转换为虚拟节点树,再转换为原生 View 树。其中 DSL 包含描述 UI 控件层级关系的 Layout JSON、描述样式的 CSS 和描述数据的 Data JSON 三部分。GaiaX 整体框架实现流程如下:
GaiaX 工作流程
在启动 Tetris Native 项目时,GaiaX 还没有进入公众视野,因此我们没有基于 GaiaX 来开发动态化模板引擎。相比 Tetris Native,GaiaX 虽然有配套的卡片编辑器,但是无法和我们自己的投放系统进行结合(参考云音乐自研的投放系统)。此外,GaiaX 缺乏我们业务需要的一些特性(如视频、不规则圆角、Flex 布局等),同时 GaiaX 样式与数据分离增加了数据构造的复杂度和数据传输量,没有带来实质优势。因此我们将参考 GaiaX 的实现方式,吸取其中比较好的部分(如事件和 JS 引擎的引入),以完善和迭代 Tetris Native。
Tetris Native
总体而言,跨端服务驱动 UI 框架的技术选型涉及以下三个方面的方案选择。首先,需要选择 DSL 层的格式,即 XML、JSON 还是 YAML,并决定是否需要进行样式数据分离。其次,需要选择 Layout 层的布局引擎,是跨端的 Flex 布局引擎、声明式 UI 框架,还是原生的布局方式。最后,在 View 层需要选择使用原生 View 还是声明式 UI 控件。根据我们团队的技术条件、现有项目架构和业务需求,在 Tetris Native 中我们做出以下技术选型:
首先,明确我们的目标是根据业务需求来抽离共性,而不是构建类似 RN 或其他类 Web 开发引擎,不自嗨
DSL 层,使用 JSON 作为数据传输格式,简洁明了,减少数据传输量
View 层,基于基础原生组件构建,考虑与现有项目的互操作性和可维护性,暂时不引入声明式 UI 组件
Layout 层,使用原生相对布局和流式布局,避免引入难以维护的三方渲染引擎
技术架构和流程概览如下图所示:
Tetris Native 技术架构图
Tetris Native 工作流程
详细设计方案和落地问题处理将在下一篇文章中进行详细介绍,敬请期待。
DSL 设计
对于动态模板引擎而言,DSL 的设计是尤为关键的。我们遵循以下三个原则来设计 DSL:
直观:字段定义应该能够直观地传达其含义。
简洁:在保持直观性的前提下,尽量保持简洁。
通用:尽量使用已有的安卓和 iOS 原生参数,避免引入新的概念。
以下是 Tetris Native 支持的能力 DSL 一览。
运营投放逻辑
从运营的角度来看,模板动态化的端侧实现仅仅是整体框架的一部分。以下是我们整体动态化运营投放、测试和服务上线的介绍。
首先,产品和运营团队会编写需要试验的所有投放样式的需求文档。设计师会根据产品需求文档设计试验投放样式的 UI 稿。根据 UI 稿,我们会生成动态版面的 JSON 描述。然后,在投放后台中,我们需要新增这些试验版面的投放流程。运营团队会上传图文等素材,并指定素材对应的业务落地页,生成不同样式的卡片。接着,运营团队会进入需要试验的版面,进行素材投放的编辑,并确定每个版面所需的素材组合。
在设置好试验版面的素材内容后,运营团队会在投放后台系统中设置这些试验版面的投放策略。当投放策略设置完成,我们可以预览投放的页面,并将其发布到测试环境。测试团队会在测试环境中对新增样式进行测试,当测试通过后,我们就可以将其发布到线上,向用户展示。
在用户浏览和点击投放试验卡片的过程中,我们会根据页面描述 JSON 中的统计字段自动收集和上报日志。最后,运营团队可以通过我们的日志报表查看不同版面和素材对用户点击率的影响,以便决定使用点击率更高的版面和素材。下图展示了投放流程和投放系统的测试及上线流程。
运营投放整体流程
通过引入 Tetris Native 动态化运营,我们成功降低了客户端开发和迭代所需的开发成本和时间投入,从而提高了运营投放的效率。此外,我们还实现了规范的自动日志上报、广告获取和自动展示上报等功能,消除了过去产品和开发团队之间繁琐的日志对接和测试验收流程,进一步加速了整体运营投放的效率。
总结展望
Tetris Native 经历了多个线上版本的迭代,从想法的萌生到正式立项,再发展到了目前相对稳定的阶段。在迭代过程中,我们不断提炼业务逻辑的共性,并持续优化整体框架的设计和性能。以下是 Tetris Native 整体开发和迭代的过程:
Tetris Native 开发历程
Tetris Native 接下来的 Roadmap 会继续结合业务产品需求,继续往以下这些方面进行迭代优化
探索 DSL 编辑优化的方法,将 DSL 以类似于 API 的方式进行发布,降低使用门槛
将库中原生 View 构建逻辑进行抽离,支持调用方复用和快速构建
探索 AI 在这里提升效率的潜力和应用
以上介绍了网易有道动态化运营引擎 Tetris Native 的初衷以及迭代开发过程,同时详细阐述了有道词典在动态化运营投放方面的整体逻辑,并展示了引入 Tetris Native 后的效率提升,希望能为大家的提供一些思路和带来收益。
下面让我们为参与 Tetris Native 开发的团队成员们鼓掌!
服务端:谢江钊、前端:滕洁、Android 端:申国骏、iOS 端:林俊健
版权声明: 本文为 InfoQ 作者【申国骏】的原创文章。
原文链接:【http://xie.infoq.cn/article/56339484306c3a41c40e525d4】。文章转载请联系作者。
评论