写点什么

携程 Trip.com App 首页动态化探索

2019 年 9 月 28 日

携程 Trip.com App 首页动态化探索

Trip.com 是携程面向国际市场的全新品牌,受全球化战略影响,随着商业化的脚步加快,首页业务针对产品迭代速度、迭代质量、线上效果验证等方面面临新的挑战。


首页无论在哪个 App 体系下都是主要的流量入口,迭代效率和质量,以及研发成本是我们追求和要解决的问题。


在首页业务迭代中我们遇到以下几个痛点:


1)新的业务需求需要跟版本发布:在商业化越来越快的同时,有大量的产品需求需要快速上线验证其效果,目前在一个大版本需要较长时间周期的大背景下,以及 iOS AppStore 审核机制的存在,最少也需要 1 天,最慢无限期的 Review 时间。在不断缩小发布周期的大方向下,通过动态下发的方式也是我们探索的一大方向。


2)多端的研发成本:在互联网越来越快的趋势下,如何节约人力成本,解放生产力,更好更快的业务需求迭代是我们一直的诉求,多端一致性和快速 UI 搭建也是我们一大痛点。


3)业务埋点维护性和及时性:在验证产品效果的同时,会产生很多难以维护的埋点代码,在页面越来越复杂的情况下,维护成本急剧上升,当线上埋点缺失,产品和运营无法验证产品效果必须等待下个版本搭车上线。


基于以上痛点,我们推出 FoxPage Native 平台,下文将会从方案选择,框架架构,以及对于业务与技术结合思考和实践的经验分享给大家。


名字来源:FoxPage 是 IBU (国际业务部)内部一个在线可视化制作页面的平台。我们决定基于 FoxPage 搭建 Native 平台。


一、框架

1.1、技术选型及思考

首先需要明确定位以及边界,我们需要一个怎么样的框架去解决存在的痛点。


需要的:


  • 由于在首页场景使用,高性能和稳定性是最基本的要求;

  • 为了不跟随版本发布,所以动态性也是要考虑的;

  • 为了解决研发成本,多端渲染也是需要解决的问题;


不考虑的场景:


  • 不需要处理复杂的业务逻辑;

  • 不支持动画精细的交互场景;

  • 不考虑多个组件的联动性;


通过梳理场景和边界使得目标清晰。我们需要一个跨平台支持动态性并且高性能 UI 渲染框架。


站在自身需求的角度,调研业界成熟的方案得出的结论如下表。



React Native:动态性高,但是学习成本和性能(加载性能、页面性能)不理想;


Flutter:谷歌的跨平台框架,性能高,但是无动态性;


通过以上的调研,我们打算用 Native 解析 JSON + Flexbox 的方式来作为最终方案。


这么做有什么优势?


1)学习成本低:Flexbox 布局方式被开发广泛接受(内部跨平台技术栈用的多的是 RN);


2)开发成本低:JSON 和 Flexbox(Yoga)都有成熟的高性能可靠的第三库直接使用,加快框架开发速度(在一个月内将框架完成并且上线);


3)兼容性强:Flexbox 完美兼容 Web 端布局的方式,FoxPage 同时支持 Web 端的 DSL 的输出;


4)自定义 &扩展强:由于自研,没有包袱,可以在设计上以最符合我们的场景来设计框架;


1.2、架构设计

如何做好架构设计,可以先了解下 Chrome 是如何完成一个 HTML 到 UI 的输出。



那么 Flutter 渲染流程是如何呢?



通过调研沉淀下我们的渲染流程:



各个模块的职责清晰且独立:


Downloader:主要负责 DSL 更新与下载。


CacheManager:顾名思义,负责 DSL 的缓存管理。


Parse:这层主要是做 DSL 解析,负责将 JSON 数据组织成节点,供下层使用。


Layout:此层职责为将 Parse 模块解析之后的数据计算布局,生成布局元素。


Draw:此层职责为将 Parse 生成节点设置 Layout 层的布局信息输出 Native 视图树并提交系统完成渲染。


遵守职责化最小原则,每个模块被赋予的职责最小化并且彼此独立,使后续的维护,可以将影响范围限制模块内部,而不影响其他模块的稳定性,增强系统维护性。也使得对模块定制优化提供基础。


下文会继续针对框架内部原理做深入介绍。


1.3、DSL 的定义

数据绑定


想象一下,在我们日常开发中,往往是数据对应一个 UI 元素的显示,需要有一定的绑定数据机制。


{    "parentId":"cfb87570-82d8-11e9-811f-0906d1cca8d4",    "name": "image",    "props": {        "url":"{{product.imageUrl}}",    },    "extendId": "",    "conditions": [],    "type": "trip-app.image",}
复制代码


变量的格式为{{变量名}},如以上示例:我们的 image 组件中 url 的属性被设置为 product 对象属性中的 imageUrl 的值。


{    "parentId":"cfb87570-82d8-11e9-811f-0906d1cca8d4",    "name": "image",    "props": {        "url":"{{products[0].imageUrl}}",    },    "extendId": "",    "conditions": [],    "type":"trip-app.image",}
复制代码


数组取值格式为{{数组[Index]}},如上,我们可以通过此方法获取 products 数组中的第一个元素的图片。


事件


在组件触发事件的时侯,我们希望能做一些自定义的事情,如跳转页面,怎么定义呢?


{    "onClick": {        "name":"router_call",        "props": {            "value": {            "plugin":"router",            "method":"openURL",            "args": {                "url":"{{products.deeplink}}"            }            }        },        "type":"function.call",    }}
复制代码


以上示例表示在点击事件中通过 router 中的 openURL 打开了一个新的页面。


条件判断


在某些条件成立才渲染的场景下,我们也提供了条件判断,示例:


{    "props": {        "hidden": {            "name":"render-activity",            "type": 1,            "props": {                "items": [                    {                        "key":"{{data.showActivities}}",                        "operation":"eq",                        "value":"1"                    }                ]            }        }    },    "type":"trip-app.image",}
复制代码


如上示例: image 组件是否隐藏通过{{data.showActivities}} =="1"来控制。


埋点机制


我们还定义了动态埋点的一些规范,如示例:


{    "name": "image",    "props": {        "$traceData": {            "onClick": {                "eventName":"home.click.deals.item",                "data": {                    "url":"{{data.operatingActivities.0.deeplink}}",                    "type":"operating_activities"                }            }        }    },    "type":"trip-app.image"}
复制代码


在 image 组件中声明了点击事件,并且把需要的参数,通过 data 字段一并上传服务端。


1.4、布局

我们的目标是为了解决动态性和多端一致性,那么具备一个完备的布局能力是一个基础要求。通过调研,有以下 3 种方案可选。



1)自定义:完全自定义一套规则,实现成本高,布局效率取决于实现程度,所以这边是“中”,因为是自定义,所有通用性是三者最差的,几乎独家专属。


2)Web CSS:实现一套 Web CSS 样式集,可想而知,如果实现这样的一套系统代价是极高的,为了兼容众多的 CSS 样式,布局效率必然会下降,但是此方案通用性也是最佳,多端共享。


3)Flexbox:弹性盒子布局,从 Web CSS 子集发展而来,在 RN 已得到充分证明它的适用性,由于 Yoga 的存在,让我们在实现成本上得到下降。


Yoga 是 Facebook 基于 Flexbox 的跨平台布局引擎开源库,被用于 RN,Weex 等项目中,也证明了其高性能和可靠性。


在实际使用过程中,Yoga 在 Android 中发现了一些问题,不过我们通过定制源码完美解决,并且在实际体验下来 Yoga 完美的胜任了布局的任务。


一些布局示例:


 "props": {    "$layoutStyle": {        "position":"absolute",        "bottom": "12",        "flexDirection":"column",        "marginLeft": "8",        "width": "100%",        "paddingRight":"16"    }}
复制代码


单位定义


width、height 等实际数字单位定义中,我们定义了如下的数字单位。



1.5、DSL 解析

解析层,要做的事情比较简单,为了提高性能,并且对于相同的 DSL 模板,只会做一次解析,之后便会把结果做一个缓存,以下的流程图代表着解析流程。



1.6、视图构建

视图构建相对简单,通过解析层解析之后,每个视图组件都会 ViewNode 节点一一对应视图在虚拟视图树中的状态,包括了视图布局属性,视图属性等元素信息。


以下为 iOS 代码示例:


/** 视图节点,映射 FoxPage 中的组件 */@interfaceFPViewNode : NSObject/** 视图属性 */@property(nonatomic, strong) FPViewAttribute *attribute; /** 子视图 */@property(nonatomic, copy) NSArray<FPViewNode *> *children; /** 绑定关系的值 */@property(nonatomic, copy, readonly) NSArray <NSDictionary *> *bindValues; /** 绑定关系的值 */@property(nonatomic, strong) NSMutableArray<NSString *> *conditions; /** 真正的视图引用 */@property(nonatomic, weak) UIView<FPViewComponentProtocol> *view; /** 添加的父视图 */@property(nonatomic, weak) UIView<FPViewComponentProtocol> *superView; @end
复制代码


在 ViewNode 树准备好之后,我们会将树传递到渲染层中进行渲染操作,在渲染层中,根据 ViewNode 节点的类型,通过代理的方式,从注册的组件之中创建出视图实例,配合 Yoga 布局属性,转换到 Native 视图的映射,由系统完成最终的渲染。


1.7、数据更新

在解析渲染完成之后,关于数据流是怎么处理的呢?以下是处理流程图:



为了优化性能,我们针对 UI 元素有变化的部分做 dirty 处理,会触发 Layout 和 Draw 模块重计算和重绘。


1.8、动态更新

动态更新能力是重要的一环,在云端更新了页面布局或者样式之后,App 需要即时拉取到最新的 DSL 模板。以下是流程中的时序图:



需要注意几点:


1)App 打包需要把线上目前可用的 DSL 模板打包进 App 中,避免第一次打开 App DSL 模板未下载的时候的空窗口现象;


2)版本升级需要做好数据隔离和清除;


3)DSL 最新版本下发,需要做好 backup 与异常校验;


通过动态更新机制,改变了我们发布需要跟随版本的痛点,有问题,修复之后可以直接下发到用户的 App。


1.9、可视化页面搭建平台

看到这里的看官,心中肯定会有疑问?你们提供变量、布局、事件、埋点追踪,条件,手动来写这很模板复杂度肯定是很高的,不是普通人可以胜任的吧。


是的,我们也意识到了此问题,所以配套了一套可视化的编辑界面。如下面示例图:



左边是可视化编辑页面,右侧为实际在 App 场景的使用效果,可以看出还原度还是很高的。


属性编辑界面:



二、页面工程化的转变

通过动态化的转变之后,首页的业务需求开发的工程模式与研发流程也由此发生变化。


在旧模式下,研发人员更加关注业务需求如何实现,首页的业务需求如何在已有的框架体系之内跑起来。新模式下,研发人员更注重的是,业务组件如何设计,如何完成的一个高质量的业务组件。


将研发人员关注的复杂度从面降为点,使得首页的各个业务模块之间的独立性更高,以及更加稳定。模块之间的复用性提升,如其他业务部门也想用广告组件,他们只需要在其页面做些简单的配置。


在组件生态不断补充的未来,各个业务线之间共享彼此的组件模块,想完成一个新的业务或者产品,只需要像乐高积木一样堆砌即可。


三、构建业务运营闭环

在提供技术基础的条件下,我们继续思考技术和业务之间的关系,如何将业务价值最大化,UI 搭建可以通过平台搭建,是不是可以把产品运营同学也一起参与进来,构建一个业务运营闭环。



1)产品运营同学提出需求;


2)研发人员介入需求开发,开发组件;


3)组件搭建业务上线之后,一站式追踪线上业务价值;


4)根据平台的数据来实时进行运营策略,如修改页面模块,下线模块,添加模块等等;


5)然后反推产品同学提出更合理的产品需求;


如何优化链路,科学的运营体系构建运营业务闭环也是重中之中,并且未来会持续不断在此方向上探索。


四、总结

FoxPage Native 平台上线短短 2 个月,承载了 30+ 次的业务调整,5+ 次临时调整埋点的需求,这是之前做不到的。并且合理系统的设计也增加了框架稳定性,上线至今无任何异常发生。在保证的高质量的交付的同时,也大大减少我们研发成本,如一个复杂的展示模块开发,从原来的双端 4 人日降低到双端 1 人日。


在首页动态化的探索中,遇到了很多的挑战,也有很多收获,后续有很多的功能和需求还需要继续优化和完善,并且需要考虑更多的场景支持。我们相信这是一个好的开头。


作者介绍


马增翼,高级软件工程师,目前负责 Trip.com App 公共业务研发和迭代,专注跨平台开发技术研究。热爱和拥抱开源社区。


本文转载自公众号携程技术中心(ID:ctriptech)


原文链接


https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&mid=2697268865&idx=1&sn=72f6a42a305622aba8709d0a17cc844c&chksm=8376f1b5b40178a3173488ea451d114aa4f12f988d0687a433ad3c9dee5fcf14d52cf03c18ec&scene=27#wechat_redirect


2019 年 9 月 28 日 08:002488

评论

发布
暂无评论
发现更多内容

阿里团队教科书:SpringBoot全优笔记,面面俱到太全了

Java架构师迁哥

数字货币期权交易系统开发|数字货币期权交易APP软件开发

开發I852946OIIO

系统开发

秒合约交易系统开发|秒合约交易APP软件开发

开發I852946OIIO

系统开发

查漏补缺!驱动核心源码详解和Binder超系统学习资源,挥泪整理面经

欢喜学安卓

android 程序员 面试 移动开发

翻译:《实用的Python编程》02_00_Overview

codists

Python

IDEA 文档插件 DocView 版本更新:支持编辑文档注释

程序员小航

IDEA idea插件 文档生成 Doc View

全网最全人工智能专业术语表(中英文对照)

澳鹏Appen

人工智能 数据 科技互联网 人工智能大数据 专业术语

Fluid — 云原生环境下的高效“数据物流系统”

阿里巴巴云原生

人工智能 云计算 容器 云原生 存储

安全之路其修远兮,吾将上下而求索

马士兵-苹果老师

谈谈信息安全

Machine Gun

网络安全 信息安全 渗透测试 WEB安全

架构学习模块一作业

架构实战营

锁仓挖矿系统开发|锁仓挖矿APP软件开发

开發I852946OIIO

系统开发

二十八分钟,带你用gitlab向企业微信发出灵魂拷问

📿

Java gitlab gitlab ci

Linux ln 命令

一个大红包

4月日更

北京天源迪科上线迪科商旅App

DT极客

阿里巴巴开源容器镜像加速技术

阿里巴巴云原生

Serverless 容器 云原生 k8s 存储

工作中的设计模式 —— 原型模式

程序员小航

Java 设计模式

跟单交易系统开发|跟单交易APP软件开发

开發I852946OIIO

系统开发

Knative 基于流量的灰度发布和自动弹性实践

阿里巴巴云原生

Serverless 容器 开发者 云原生 k8s

史上最全的Java面试题库宝典,Github上标星200k,太香了!

Java架构之路

Java 程序员 架构 面试 编程语言

架构实战营模块一作业

冷大大

作业 架构实战营 模块一

apk优化,Android高级工程师必看系列,在线面试指南

欢喜学安卓

android 程序员 面试 移动开发

聪明人的训练(六)

Changing Lin

4月日更

5G时代,企业信息安全将更加严峻

Machine Gun

运维 5G网络安全 网络安全 信息安全 WEB安全

阿里大牛最新整理!金三银四面试题库+Java全栈笔记限时开源

Crud的程序员

Java 编程 程序员 架构 面试

七进七出,终获阿里32k*16offer,这就是我悲惨的面试经历~

Java架构师迁哥

音频应用类开源 Demo 大盘点

anyRTC开发者

ios android 音视频 WebRTC RTC

项目优化-代码拆分

Darren

android 组件化 代码优化

字节首推Leetcode刷题速成笔记太香了!肝完再也不慌大厂面试死磕算法了!

Java王路飞

Java 程序员 面试 算法 字节

阿里的 RocketMQ 如何让双十一峰值之下 0 故障?

阿里巴巴云原生

容器 运维 云原生 k8s 消息中间件

Flink集成Iceberg在同程艺龙的实践

Apache Flink

flink

4月17日 HarmonyOS 开发者日·上海站

4月17日 HarmonyOS 开发者日·上海站

携程 Trip.com App 首页动态化探索-InfoQ