支付宝移动端动态化方案实践
摘要: 支付宝从最开始的工具型应用,逐渐发展成平台型应用,一直到现在,已经成为了一个超级 App。本文将带领大家进一步了解支付宝在动态化方案的探索以及 Nebula 框架。
小蚂蚁说:
此前分享的《模块化与解耦式开发在蚂蚁金服mPaaS深度实践探讨》(想要了解更多相关内容,欢迎关注公众号:mPaaS )已经对支付宝在移动端开发架构的设计思路有了初步了解。本文将结合在 iWeb 武汉站的分享,带领大家进一步了解 mPaaS 在移动端动态化方案设计。
分享内容将分为以下三个方面:
- 支付宝动态化方案的探索;
- Nebula 框架浅析;
- mPaaS 科技赋能
支付宝动态化方案的探索
任何一种技术方案都是在业务的发展和架构的演化中逐渐探索出来的,因此我们来看一下支付宝架构的演进:
支付宝从最开始的工具型应用,逐渐发展成平台型应用,一直到现在,已经成为了一个超级 App,它拥有多应用的生态,更加开放和动态化,并且保持着高可用,高性能,高灵敏的强大特性。随着 App 的逐渐庞大,整个应用的架构也在进行不断的调整,来适应各种特性。现在的支付宝客户端的架构如图所示:
整体架构分为五层:容器层、组件层、框架层、服务层、应用层。
客户端整体采用统一的框架开发,模块化的开发模式,完全插件式的容器,支持模块独立发布,方便大规模团队的并行开发。
在这样的框架结构中,同样包括了我们的动态化方案。支付宝中的动态化方案主要有两种框架:Nebula 和小程序。
这两种方案不仅解决了需求迭代速度和发版周期之间的矛盾、跨平台开发、实时发布等一些普适问题,而且有效地保证了发布质量,对线上问题进行紧急止血,同时也有助于建立良好的开放生态。
Nebula 是支付宝移动端 Hybrid 解决方案,它提供了良好的外部扩展功能,拥有功能插件化、事件机制、JSApi 定制和 H5App 推送更新管理能力。
主要功能包括:
- 接入 H5App 后台,方便管理离线或者在线 H5App,实现 H5 应用的推送、更新。
- 加载 H5 页面,并按照 Session 的概念进行管理各个页面。
- Android 使用 UCWebView,拥有解决系统级 Webview Crash 的能力,内存管理更合理,网络加载提升更快,兼容性更好。彻底告别了在Android下兼容不同 Webview 的问题。
- 支持自定义网络库,自定义网络通道;支持自定义键盘,自定义 Native View替换 H5 标签。
- 提供丰富的内置 JSAPI,实现譬如页面 push、pop,标题设置等功能;支持用户自定义 JSAPI 和插件功能,扩展业务需求。
- 自带埋点功能,接入 H5 监控平台,能够实时看到页面加载的性能、错误报警和监控。
还有一种动态化方案就是支付宝小程序:
支付宝小程序是一种全新的开发模式,融合了 H5 的易开发性、跨平台性、Native 性能,让开发者可以快速开发高性能的页面,提供优异的用户体验。通过使用支付宝小程序,开发者为支付宝开发了大量优质的小程序,丰富了支付宝生态能力。小程序开放给开发者更多的 JSAPI 和 OpenAPI 能力,通过小程序可以为用户提供多样化便捷服务。
Nebula框架浅析
Nebula 的架构如图所示,从上至下依次为 H5 应用层、服务层、原生框架层:
H5 应用层:基于 HTML 和 JavaScript 技术开发,在 H5 容器上运行的手机应用,它拥有跨平台的特性,配合离线包的使用可以完成实时热修复的功能。
服务层:为开发者提供了高阶语言的 API 来使用手机系统资源,包括:
- 视窗系统,开发者可以使用它来创造应用 UI,包括文字,图片,按键及定制 View
- 资源管理,通过它开发者可以方便的访问如多语言文字,图片和布局等非代码资源
- 应用生命周期管理,它决定应用在手机系统里的开始,结束以及强制关闭的时机
- H5 容器
原生框架层:是手机系统的基础层,它提供了标准 API 来让高阶语言(比如 Java 和 Object-C)使用底层的硬件,并包含了许多为硬件访问的专有软件库。当上层调用某个框架 API 来访问硬件时,手机系统将加载相应的软件库。
整个 Nebula 框架的核心部分就是 H5 容器,下面看一下 H5 容器的结构:
H5Service,H5Session 和 H5Page都是从 H5CoreNode 类扩展而来,以 H5Service 为根节点,它与其他类一同形成了树状结构构成了页面流程和导航。在一般情况下,H5Pages 是 H5Session 的子节点,而 H5Sessions 是 H5Service 的子节点,在任何情况下只有一个 H5Service 根节点存在。
H5Service:是 Nebula 里维护 H5 应用全局状态的基础类, 在 H5 应用的生命周期内只有一个 H5Service 的单例全局实例,H5Service 可以进行的操作包括:
- 创建且打开一个新的 Web activity
- 创建且开启一个新的 Web page
- 从共享空间存储和读取数据
- 注册插件和 Provider
- 监听应用的生命周期
H5Session:一个 H5Session 是由一叠 H5Pages 组成的完整业务流程。例如一个收银台的流程包括:一个购物车的小结页面,一个结账方式的选择页面,和最后一个结账确认页面。所有的页面都可以独立存在运作, H5Session 在其中的作用是把这些页面组织起立,按照业务逻辑把它们按序排列来完成业务。当你使用 H5Service 创建且开启一个新的 Web page 时,如果当前没有 H5Session 的话,一个新的 H5Session 实例将被创建,并为后续创建的 Web page 共享。你可以从 H5Session 中移除页面直到页面叠为空,也可以使用 H5Session 所提供的方法来获取首页,以及监听该 H5Session 的生命周期。
H5Page:是用户看得见,摸得着的页面,也是应用生命周期中最重要的一部分。你可以通过 URL 来加载内容,用 H5Param 来定制页面的外观和行为,甚至可以通过获取 H5Page 的视图层次,把 H5Page 视图和其他原生 UI 部件一起内嵌到同一个布局中。
下面是 H5 容器的几个重要组成部分:
- API 管理器主要管理 JS API:Nebula 中已经提供许多内置的 JS API 供开发者使用,比如操控 UI,显示对话框和 Toast,以及使用网络 RCP 服务等。
- 插件管理器主要管理 Plugin:如果现有的 JS API 无法满足你的业务需求,你也可以选择创造一个新的插件。你只需把原生代码打包在插件中,在管理器里注册该插件,便可在 Javascript 层使用新的 JS API 了
JS Bridge 是连接原生层和 Javascript 的沟通桥梁:它将 Javascript 代码转译成能在系统框架运行的字节码,同时也把原生层的数据结构转成 Javascript 对象使其能在 Javascript 层处理。这里 Nebula 针对JS Bridge 做了一些优化:
- 在 Android 里,js 调用 native 的通讯是通过 console.log 的方式,这个和其他容器实现不一样,其他容器一般通过 prompt 的方式来实现的,但是使用 prompt 的方式,有两个弊端:
使用 prompt 会阻断整个浏览器的进程,如果 native 处理时间一长,会导致页面假死。
- prompt 是在 UI 层面上会弹出一个模态窗口,在 native 没有对其进行捕获处理的话,会造成一个问题。一旦这个页面放在非此容器的环境下,就会出现一个很诡异的 prompt 弹窗。在支付宝内,曾经出现过这个问题,天猫页面在支付宝 app 里的时候,由于容器机制不同,页面中 bridge 脚本没有判断环境,导致页面中 js 调用 API 的时候,在页面上出现了 prompt 的模态对话框,严重影响了用户体验,但是如果使用 console.log 的话,就不会出现这个问题。console 的方式避免了不兼容环境的体验问题和同时也避免了页面的假死。
- jsbridge 注入的时机,由于业务逻辑要依赖 bridge,所以业务的所有逻辑都会在 bridge ready 之后才会触发,而 bridge js 本身运行是要一定的时间的,因此注入的时机对于性能的影响显得非常的重要。但由于 H5 页面的生命周期和容器的生命周期是相互独立的,因此在 H5 生命周期的哪个阶段注入这段 bridgejs,对于性能的影响就显得异常重要。
现在在支付宝内使用的方式为监听 H5 生活周期的事件,比如说当 Webview 设置 title 结束之后,Android 会放出一个 onReceivedTitle、shouldInterceptRequest 等事件,iOS 会尝试在 webViewDidStartLoad 事件,在监听到这些事件之后,立即注入 bridgejs,让其在 H5 生命周期尽早运行。通过这种方式的注入,经过测试,最早能在页面加载开始后, 50ms 以内就能成功注入 bridgejs。
- Event 机制:Nebula 提供了一套事件机制来管理事件在 H5Page,H5Session 和 H5Service 之间的流通顺序。一个 H5Event 可以在 H5Page, H5Session 或 H5Service 任何一层发生,事件派遣分为两步完成事件拦截。
这个步骤中事件派遣的顺序为 H5Service -> H5Session or H5Page。
事件可以在任何节点被拦截 (如果 interceptEvent() 返回 true ),也可以在任何节点被处理 (如果 handleEvent() 返回 true ):如果事件在派遣过程中被拦截或处理,该事件将被视为已被消费且不再继续流通。如果在派遣过程后事件依旧没有被拦截或处理,会有错误抛给呼叫方处理。
仅仅使用传统的 H5 技术展示在线页面,很容易受到网络环境影响,因而降低 H5 页面的性能。
在 Neblua 中我们使用离线包技术来解决这个问题。离线包是将包括 HTML、Javascript、CSS 等页面内静态资源打包到一个压缩包内,它的目录结构如图所示:
使用离线包可以使容器内的 H5 应用具有接近 Native 的体验,主要优势如下:
- 减少网络环境对 H5 应用的影响:通过下载离线包到本地,然后在客户端打开,把打开H5页面的操作从网络 IO,变成磁盘 IO。直接从本地加载离线包,不仅最大程度地摆脱网络环境对 H5 页面的影响,而且增强了用户体验。
- 提升用户打开 H5 应用的体验:通过离线包的方式把页面内静态资源嵌入到应用中并发布,当用户第一次开启应用的时候,就无需依赖网络环境下载该资源,而是马上开始使用该应用。
- 实现动态更新:在推出新版本或是紧急发布的时候,您可以把修改的资源放入离线包,通过更新配置让应用自动下载更新。因此,您无需通过应用商店审核,就能让用户及早接收更新。
下面介绍一下离线包的渲染过程当 H5 容器发出资源请求时,其访问本地资源或线上资源所使用的 URL 是一致的。H5 容器会先截获该请求,截获请求后,发生如下情况:
- 如果本地有资源可以满足该请求的话,H5 容器会使用本地资源。
- 如果没有可以满足请求的本地资源,H5 容器会使用线上资源。因此,无论资源是在本地或者是线上,WebView 都是无感知的。
离线包的下载依赖用户当前的网络。一般情况下,只有在连接 WIFI 的情况下才会在后台下载离线包。如果用户处于移动网络下,不会在后台下载离线包。如果当前用户点击 APP,离线包没有下载好,用户就要等待离线包下载好才能用。
为了解决离线包不可用的场景,fallback 技术应运而生。每个离线包发布的时候,都会同步在 CDN 发布一个对应的线上版本,目录结构和离线包结构一致。fallback 地址会随离线包信息下发到本地。在离线包没有下载好的场景下,客户端会拦截页面请求,转向对应的 CDN 地址, 实现在线页面和离线页面随时切换。
那么本地资源如何寻址呢,我们设计了独特的虚拟域名机制,仅对离线应用有效。当页面保存在客户端之后,WebView 如果要访问的话,是通过 file schema 来从本地加载访问的。然而,用户就能在地址栏里直接看到 file 的路径,这就会导致以下问题:
- 用户体验问题:当用户看到了 file 地址,会对暴露的地址产生不安全感和不自在。
- 安全性问题:由于 file 协议上直接带上了本地路径,任何用户都可以看到这个文件所在的文件路径,会存在一定的安全隐患。基于如上问题的考虑,采用虚拟域名的机制而不直接使用 file 路径来访问。虚拟域名是一个符合 URL Scheme 规范的 HTTPS 域名地址,例如 https://xxxxxxx.h5app.example...
Nebula 里面的 H5 容器和离线包,在传统的 Hybrid 框架的基础上进行了极致的优化,使整个 H5 应用具有如下特点:
- 对网络链路强依赖的弱化
- 增强对设备能力的支持
- 增强的用户体验
在性能方面,Nebula 在支付宝中经过了亿级用户的考验,crash 和 anr 以及其他稳定性指标有保障。Android 平台基于 UCWebview 深度定制,crash 率和 anr 量远低于系统webview,拥有解决系统 Webview 问题的能力。图中展示的就是在 Android 端,UCWebview 和系统 Webview 之间崩溃率和 ANR 率的对比,稳定性的优势显而易见。
最后看一下小程序与 Nebula支付宝小程序复用 Nebula 容器技术栈,重构了开发方式,对外暴露有限个 jsapi 接口,让 app 开发更简单,更加便捷利用支付宝的能力,进而发布、推广、运营。小程序本质上是也是一个 H5 App 离线包,但是有一些自己的特点。
- 小程序是为第三方 App 服务的,运行在独立进程,它的稳定和闪退不会影响到主 App,也支持二方 App 运行在主进程。
- 小程序是支持保活的,极大的提升二次打开的体验。
mPaaS技术架构与助力
Nebula 有这么大优势,现在不仅在蚂蚁金服内部使用,也能够提供给外部来使用。
首先什么是 mPaaS 呢,mPaaS 全称是 Mobile Platform as a Service 。是蚂蚁金服独创的移动研发平台,它源于支付宝 App 近 10 年的移动技术实践和思考,为移动开发、测试、运营及运维提供云到端的一站式解决方案,能有效降低技术门槛、减少研发成本、提升开发效率,协助生态伙伴快速搭建稳定高质量的移动 App。
在 mPaaS 中,我们将 Nebula 的 H5 容器、jsapi 、离线包、小程序这些模块作为一个单独的组件来进行输出,在客户端中进行配置。任何一个 App 通过 mPaaS 插件,添加对应的模块,集成这些功能,只需要这样简单的操作,就可以让你的应用具有和支付宝一样强大的动态化能力。
同时 mPaaS 提供的小程序模块,允许用户把运行在支付宝上的小程序,无缝的迁移到自己的 App 中,做到【跨平台跨应用】开发,提高代码的复用能力Nebula 组件化输出,配合 mPaaS 提供的 MDS (移动发布服务) 来实现动态更新。
mPaaS 提供的 MDS 服务,能够让每次发布更新就像发邮件一样简单。
MDS 具有智能灰度发布的能力,可以通过内部灰度,外部灰度多重验证,保证在正式发布之前,发布的产品质量有充分的保证,同时提供多种升级策略,包括指定人群地域、机型,系统版本,网络环境等多种规则。对于离线包来说,更新离线包的下载对网络环境要求较高,包的大小越大,更新的成功率越低,在 mPaaS 中,我们采用增量差分的更新能力,减少数据冗余及设备带宽,在移动网络条件下优势明显。同时保证更新功能的高可用性,升级接口可用率达 99.99%,在线分钟级触达。
下面是 Nebula 的生态基础,首先在集团内部,我们已经支持了不少产品,同时通过 mPaaS ,我们也与外部客户合作,将我们的技术能力输出给他们,典型的几个案例,包括 12306 客户端,广发发现精彩客户端,上海地铁,苏州银行等。
尤其是 12306,使用 mPaaS 改版之后,客户端整体的体验更加优越。12306 整个客户端绝大部分都是使用的 H5 技术,他们就是使用 Nebula H5 容器 配合离线包来实现,无论是页面打开速度,还是UI事件响应,体验几乎接近 native。在更新发布方面,12306 的 app 包很少更新,以 AppStore 上的发布记录来看,今年只提交了两个版本,基本上都是通过动态化的方式完成业务的迭代发布。
总结下来,蚂蚁金服 mPaaS 中就是通过「Nebula H5容器 + 离线包 + 小程序 + MDS」这样的方式来实现移动端的动态化方案。
本文作者:平生栗子
本文为云栖社区原创内容,未经允许不得转载。