移动APP首页出于对启动速度和UI性能考虑,一般都采用原生技术实现,由不同的卡片样式组合而成,然而在实际应用中,由于业务发展的需要,首页的样式和内容总是在不停的迭代更新,这对首页的架构设计提出了挑战,为了快速满足业务发展,手机银行客户端的首页方案经历了多次技术架构的演进,主要经历了以下几个阶段:
- 固定样式阶段:页面样式固定,数据请求后刷新页面
- 内容动态化阶段:卡片样式提前内置,内容动态下发
- 样式动态化阶段:卡片样式和内容完全由服务端决定
- 逻辑动态化阶段:卡片样式、内容、逻辑均由服务端来控制
固定样式阶段
早期应用简单地将业务转移到移动端,这个时期应用能够满足客户的业务办理需求即可,与大多数应用初期技术方案类似,移动端设计好页面样式,与服务端通信获取业务数据后刷新页面。
这种方案很好地满足了业务向移动端迁移的需求,也为业务发展提供了强力的支持。但是伴随着业务的发展,该方案的一些弊端也逐渐地显现了出来。比如下面的场景此方案就无法很好地满足:
- 业务需要根据运营策略对页面进行调整,例如:年初是银行揽储的重要阶段,这时候很多负债和理财类楼层卡片及活动楼层卡片需要放在比较靠上的位置展示,有些当季不太重要的可以往下放放;还有一些楼层卡片一般是因业务办理时间比较固定,所以可以选择在相应业务办理的时间段前后展示,平常则不显示。
- 业务需要基于客户的需要显示不同的内容,例如:客户小张是一名工薪阶段,他感兴趣的内容可能偏重在理财、贷款等产品上,而客户小王是一名退休职工,他感兴趣的可能偏重理财、签到打卡、小游戏等产品上。
内容动态化阶段
为了满足业务上述需求,首页的实现方案演变为内容动态化阶段,具体地实现方案为Android /iOS端预置楼层样式,并和服务端约定好每个楼层的可配置内容数据定义,客户端根据服务端下发的楼层列表和数据来渲染对应的内容,页面整体配置较为灵活,很好地支撑了业务的发展。
但是随着客户端首页的不断改版,楼层卡片样式不断变化,通过原生预置卡片样式的方案暴露出了难以解决的问题:
- 开发效率低下 ,每次新增卡片样式,都需要Android、iOS端分别实现,再加上两端测试和维护的人力投入,开发成本大。
- 不能及时触达用户,新增的卡片样式需要跟着客户端发版,但是APP发布需要经过应用市场审核,时间上难以把控;而且即使APP已经发布,用户也可以选择不更新,因此无法覆盖所有用户。
样式动态化阶段
为了解决内容动态化的不足,满足新增卡片样式无需客户端发版的诉求,需要卡片样式也支持动态化;基本的思路就是把卡片细分为容器、图片、文字、列表、轮播等基础组件,容器和组件组成树形结构,通过搭积木的形式来实现卡片样式;
我们使用json定义了一套DSL,用于描述卡片的样式,包括文字、图片、列表等基础组件、布局信息、组件属性、点击事件处理等;
{
"type": 2000,
"version": 1,
"components": {
"type": "container",
"layout": {
// 布局信息
},
"props": {
// 组件属性
},
"action": {
// 点击事件处理
},
"children": [
// 子组件
]
}
}
动态布局基于Flexbox布局,相比于传统布局方式,Flex布局可以较为简便实现一些布局样式,比如水平排列,水平等分排列等,相比于传统布局,Flex布局便捷快速,在手机端由于兼容性非常好,所以在做移动端前端页面时,Flex布局有着非常大的优势,能够保证跨端布局的灵活和一致性。APP端我们采用facebook开源的高性能 Flexbox布局引擎yoga。
楼层卡片DSL里面定义了组件的属性信息,属性需要在页面加载时跟楼层数据绑定,通过{{字段名}}
定义需要绑定的数据字段;
{
"type": "line",
"layout": {
},
"props": {
"color": "{{data.color}}", // 定义直线的颜色
},
}
点击事件处理定义了对卡片元素点击事件的响应动作,包括跳转页面、关闭页面、弹框、toast等。
{
"action": {
"props": {
"content": "您好,欢迎来到XXX",
"gravity": "bottom"
},
"type":"toast",
}
}
通过后台管理端的卡片样式编辑器,可以对卡片样式进行可视化编辑并自动生成DSL,APP在启动的时候先从后台拉取卡片样式,然后请求页面卡片数据,卡片样式和数据完成绑定以后最终渲染展示给用户。
通过DSL来描述卡片样式和行为虽然实现了卡片样式的动态化,但是仍有一些不足:
- 只适合侧重静态展示的场景,很难实现具有复杂动效的样式。
- 通过DSL很难去描述复杂的逻辑,比如根据用户账户余额的大小展示不同的图标。
逻辑动态化阶段
通过DSL来描述卡片样式是通过牺牲一定的灵活性来实现页面动态化,适合重展示轻交互的场景,但是在首页的需求中,还是存在一些比较复杂的交互场景,比如APP根据服务端下发的卡片数据内容展示和隐藏不同的组件、点击组件的时候先发网络请求并根据响应结果执行不同的逻辑等,通过DSL很难去描述这样的逻辑,因此我们引入了一个轻量级的JavaScript引擎,在Android中使用QuickJS引擎,iOS中使用系统内置的JavasScriptcCore引擎,复杂的交互逻辑通过脚本来实现,这样在保证楼层样式和内容动态化的同时具有一定的灵活性。
我们在DSL中增加script字段,卡片展示和组件点击分别提供不同的js脚本:
{
"type": 2000,
"version": 1,
"components": {
},
"script": {
"show": "function onShow(data) {}", // 楼层展示时要执行的js脚本
"click": "function onClick(id, data) {}" // 组件点击时要执行的js脚本
}
}
当展示和点击事件触发的时候,执行对应的脚本:
jsContext.evaluate("onShow(data)");
jsContext.evaluate("onClick(componentId, data)");
未来展望
APP首页架构最理想的方案是具有高开发效率、高性能、跨端、动态化和完全灵活的交互,使用DSL+JavaScript在灵活性上还是有一些限制,目前终极的高性能跨端技术方向是使用自渲染引擎,典型的代表是Flutter,但是Flutter不具备动态化的能力,业内有尝试使用Flutter+JavaScript Engine来适配web生态,web生态天然的跨端、动态化能力和Flutter的高性能结合,是一个值得关注的方向,未来我们也将在这个方向上进行探索。