支付宝 App 架构的原理与实战
点击“开发者技术前线”,选择“星标?”
让一部分开发者看到未来
根据公开的 2018 年移动互联网行业分析报告,目前支付宝的月活跃用户已经超过 QQ ,成为国内第二大 App。
支付宝一开始仅仅只是一个单体应用的工具型 App,让用户可以在手机完成支付宝相关的业务查询和操作。2013 年后,支付宝逐步转型为平台型 App, 平台型 App 具有服务化、模块化、工具组件化的特点。这个时候支付宝的业务不仅仅是支付,还需要给客户提供很多生活相关的服务,例如余额宝、缴电费等。2015 年后支付宝成长为超级 App,此时支付宝里面需要支持大量复杂的业务,同时开放自己的商业能力,用自己流量助力合作伙伴,因此整个 App 面临开放、动态化、高可用的挑战,面对这些挑战,我们把它总结为以下三点:
如何应对复杂的业务协同?
如何满足业务快速迭代的需求?
如何构建面向未来的开放生态?
App 的业务越来越复杂,不仅仅是内部业务,还包含了大量外部的合作伙伴。如果采用传统的 App 开发方式很难应对日趋复杂的业务场景。
1.1 Hybrid 技术方案选型
在 Hybrid 技术方案选型方面,我们通过“开发成本、用户体验、动态性、复杂业务支持能力、研发难度”五个方面综合考量。我们筛选出 HTML5、ReactNative/Weex、Flutter 作为备选,并将原生开发作为基准线完成对比。(考虑到近期 Flutter 的热度持续走高,因此我们纳入 Flutter 一并分析。)
首先我们从业务开发成本角度来看:
原生作为最基础的开发模式,需要双端都进行开发,无疑成本是最高的;
其次是 ReactNative/Weex,即使是一次开发,同时运行在双端,但由于是 JS 转成 Native 组件渲染,实际运行起来仍然存在些许差异,导致开发者在写业务界面时,部分差异需要通过 Native 端定制开发来解决。整体而言,ReactNative/Weex 已帮助业务方大幅降低开发成本;
接下来是 Flutter,从业务开发的角度来说,Flutter 针对双端对齐真的下了大功夫。在大多数场景下,Android 端开发完毕之后能无缝跑在 iOS 端,当然这和它自研的引擎有关。只不过 Flutter 需基于 Dart 语言开发,因此对于开发者而言,部分老业务移植的工作量需考虑在内;
最后是 HTML5,带着成熟的语言,成熟的开发模式,双端几乎一样的表现等特性表明 HTML5 仍然是目前我们能落地的开发成本最低的方案。
接下来我们讨论用户体验:
首先,原生的体验毋庸置疑是最好的;
其次是自有渲染引擎的 Flutter,无论是性能还是控件的展现形式,可以说是不亚于原生的体验;
接下来便是 ReactNative/Weex 方案,通过将前端代码渲染成本地 Natvie 控件。在早期版本中,由于部分控件优化不到位导致 App 卡顿,因此用户体验的表现不足;
最后是 HTML5,完全通过浏览器内核进行渲染,借助预置资源、内核优化等技术,HTML5 可以做到接近原生的体验,但总体性能仍有差异。
接着是动态性的支持:
在本文第二章节“离线包机制+发布平台”,我们会从快速迭代的角度深度分析动态性在支撑高并发业务场景下的重要性。
首先,动态性最优的就是 HTML5 方案:可以访问在线页面,服务端即时生效,也可以通过下发资源的方式,进行动态更新;
其次是 ReactNative/Weex 方案,通过一定的定制,开发者可以将前端包热部署、热更新。不过相较于 HTML5 具备的“在线+离线”的动态性,该方案仍然存在一定差距;
接下来是原生,Android/iOS 双端均可以通过一些黑科技手段,进行动态更新,不过由于 iOS 政策禁止,因此在动态性上,原生方案暂时不推荐;
最后是 Flutter,虽然有很强大的热重载机制,不过由于 Google 的限制,线上版本 iOS 无法做到热更新,因此在动态性评估中将 Flutter 排在最后。
最后我们聊下各个方案的实现起来的研发难度:
这里我们暂时将 HTML5 放在第一位,因为做 HTML5 Hybrid 方案,离不开内核优化,内核优化就需要有一定内核研发能力,因此在开发者视角下 HTML5 研发难度最高。如果只是单纯的 HTML5 容器,研发难度就会大幅降低;
其次是 Flutter,目前在实际业务应用案例方面,国内较大体量的 App 暂时只有闲鱼团队引用了 Flutter;同时在 Flutter 的 GitHub 中仍然存在大量的 Open Issues 等待解决。而在实战开发运用过程中,Flutter 的生命周期管理,视图栈管理,原生页面切换等问题都需要开发者在前期选型过程中便要重视;
接下来是 ReactNative/Weex,由于这两个方案开源,且有大量成熟的技术社区支持,方案的研发难度对于开发者而言并不高,同时开源代码方便修改,更容易上手;
最后是原生方案,如果不考虑做热修复的话,原生方案无需做任何改动,直接使用即可;若考虑热修复方案,目前市面也有一些成熟的开源热修复方案可以直接使用。
综上所述,我们再考虑了各方的优劣之后,决定采用“HTML5 容器+内核优化”的方式来应对复杂业务的开发问题。接下来我们就介绍下容器的架构。
1.2 容器架构
最上层是原生的 HTML5 代码,这块就是大家常见的 Web 开发环境,包括 HTML、CSS、JavaScript等。
下面一层即离线包管理,这个我们在第二章节内进行详细介绍。
再往下是 HTML5 容器层,HTML5 容器作为中间层,将浏览器和支付宝底层框架有机结合起来,同时还提供各种生命周期机制、事件机制、扩展插件等内容。
在 HTML5 容器里面有个非常重要的概念: JSBridge。通过 JSBridge,HTML5 容器将支付宝框架底层以及中间件层提供的各种能力和 HTML5 前端代码进行联通,其中包括 RPC(远程过程调用,用来实现 App 和服务器通信)、支付、扫一扫等。
最下面是支付宝底层框架,提供微应用,微服务等概念。一个 HTML5 应用,也会被框架模拟成一个微应用,通过应用 ID 进行解耦。
1.2.1 JSBridge 介绍
JSBridge 是 HTML5 容器的基石,桥接了 JS 环境与 Native,实现了 Native 代码和浏览器环境的双向通信,Native 代码可以通过调用浏览器提供的接口运行JS,从而实现调用 JS 函数、传递参数到 JS 环境等;而浏览器到JS环境的通信是通过 Native 拦截浏览器的请求来实现,请求可以是网络请求或者是一些内部函数的调用。
1.2.2 H5 容器定制化扩展
HTML5 容器提供了 2 种扩展方式:
JSAPI
JSAPI 方式给 HTML5 页面增加了 Native 功能调用接口,通过实现自定义 JSAPI 类中的 Handler 方式,可以以 Native 的形式实现特定功能,例如调用 Native 加密函数。
事件
HTML5 容器在状态变化时会发送事件,通过监听 HTML5 容器特定事件,可以实现对 HTML5 容器生命周期的处理,比如修改加载进度条颜色、修改页面导航栏等。事件提供了更强的定制性,完全可以满足对 HTML5 容器的各种自定义需求
1.3 容器稳定性
上面在研发难度中,我们提及到了,HTML5 方式的研发难度是最高的,因为需要定制化内核进行性能及稳定性优化。目前支付宝采用的是阿里集团的 UC 自研内核,并针对支付宝的 HTML5 容器进行了深度优化和定制。如图所示,UC 内核和系统内核的卡顿卡死率的数据对比效果非常显著,我们可以直观地看到 Webview 稳定性的提升。
目前支付宝业务的另外一个特点就是需要快速迭代,变化的政策、突发事件都需要我们可以快速把新的业务需求触达给用户。但是对于 App 开发者有一个不容忽视的问题,就是应用商店审核。由于审核的存在,App 上开发的业务会有一个统一排期,比如说月底会有新版本,那么所有的业务进度都得考虑 App 的排期计划。
2.1 离线包机制
为了做到良好的用户体验,我们在容器中引入了离线包机制。通过离线包机制,我们将原有从线上加载的 HTML5 应用,提前下发到本地,通过读取 IO,或者是内存,进行页面的渲染,达到接近原生的用户体验。
通过发布平台,我们可以将不同的 HTML5 离线包,以单独应用的形式,进行不同维度的下发,使原来 all in 的 Native 发布模式,改为各业务线自行定制发布计划,自行制定发布标准,自行发布的并行发布形式,来满足业务的快速迭代。
2.1.1 加载机制
通过内存提前加载,定时更新,启动预加载内存等手段,我们将一个业务包需要用到的资源加载到内存,从而使启动过程尽量无感知,页面秒开无白屏。同时,我们还有 Fallback 手段,保证在包损坏或者是未下载完成时,可以通过在线页面的形式,保证业务的 100% 可用性。
2.1.2 公共资源包机制
所谓公共资源包,即所有 HTML5 离线包都可能会用到的公共资源的集合。公共资源包解决多个 HTML5 应用使用同一资源产生的冗余问题。如 React 应用使用 ReactJS 框架代码。您可以将公共资源放入全局资源包,以降低 HTML5 应用体积。
通过公共资源包机制,可有效降低各 HTML5 应用的包体积,从而使更新率提高,页面开启速度加快。
2.2 发布平台
为了满足快速迭代的需求,一个强大的发布平台也是必不可少的。发布平台的核心指标,就是将发布内容高效、精准的投放到指定的设备上,为了实现这个目标,我们做了如下的努力。
2.2.1 离线包大小管控及差量包机制
HTML5 容器离线包提供了更新机制,以单个离线包作为更新维度。因为单个离线包业务很简单,所以离线包的大小是可控的,通常小于 500KB。我们通过大量的实践,总结出来“500KB”这个值,既可以满足单个业务的内容,也可以更高效地发布到设备上。500KB,在 4G 的时代,几乎可以做到用户无感知更新,即便是 2G/3G 也可以保证一个高的到达率。
上面说的是一个 HTML5 应用的大小。实际上,我们更新的包会更小,发布平台会通过 diff 算法,计算出相同 HTML5 应用两个不同的版本的差量包,差量包通常也就在几 KB 至几十 KB 不等,可以做到更高的下载成功率,下载成功率一定程度就意味着实际到达率。
2.2.2 Fallback 机制
在一些极端网络场景下,新的业务资源包更新失败,而我们又期望用户使用的是最新的业务,这个时候 Fallback 访问机制就会发挥作用。每个离线包资源都会在发布服平台上存放一份,在刚刚说到的极端场景下,用户会访问服务器的 Fallback 地址获取资源,从而保障页面可用。
2.2.3 多维发布
另外,针对刚开发好的应用,我们可以通过发布平台的灰度发布进行发放,通过外部灰度的形式,对业务指标进行验证,达到标准后,方可正式发布,做到可灰度,可回滚。
作为超级 App,一个最主要的特征就是开放。开放就是共享 App 的流量,让外部伙伴的业务可以通过支付宝触达用户,这就面临一个质量管控的问题。支付宝需要保证这些业务是合法合规的,保障用户的财产安全。
3.1 离线包 VS 小程序
如果开发一方业务,离线包肯定是非常好的选择。不过,要是开放给第三方合作伙伴构建生态的话,纯 HTML5 页面就有一些劣势。
上图是 HTML5 离线包和小程序的细节对比。总结来说,对于开放给第三方的生态,从应用体验来讲,小程序更加统一,质量有保障;从应用安全角度来讲,小程序是访问我方发布服务器,不会直接访问第三方链接,安全可控;从研发门槛上来说,小程序是更简单的前端开发方式,同时也提供了非常丰富的组件。
3.2 小程序解析
小程序其实和离线包本质是类似的,都是一种 Hybrid 应用,但小程序是基于一个定制的 DSL 语言,不是前端的标准,但是类似。在 DSL 规则下业务进行小程序的开发,不支持直接操作 DOM,这种 DSL 规则下的自由可以有效的进行质量管控。
小程序作为一个应用,他拥有完整的生命周期。从开发到关闭,开发者都可以感受到,这点也是 HTML5 所不具备的。另外,每个小程序之间从运行时和持久化上,都是完全隔离的,而且小程序运行在特定进程中,所以和支付宝也是隔离开的。
在渲染性能上,小程序采用双线程模式将页面渲染和业务逻辑分别放在两个单独的线程中,renderer 运行在 WebView 中,负责渲染界面;小程序业务逻辑运行在单独的 worker 线程,负责事件处理、API 调用和生命周期管理。两个线程之间通过 postMessage 以及 onMessage 进行数据交换,数据可以从 worker 线程传递到 render 重新渲染界面,同时 renderer 也可以将事件传递给对应的 worker 处理。一个 worker 可以对应多个 renderer,方便页面间数据共享和交互。
在资源加载方面,小程序采用离线化方式加载,也就是说当打开小程序时,小程序离线包必须下载到本地,由于每个版本只下载一次,一方面节省了每次请求的资源开销,另一方面启动速度大大提升了。当有新的版本时,发布平台自动比对本地安装的版本和最新版本产生并下发差量包,客户端不需要下载整个包即可更新小程序至最新版。
3.3 构建生态
通过引入相同的小程序架构,使得小程序,可以作为生态进行多端互投。在支付宝中投放的小程序,可以只经过一些开放接口的适配,即可跑在基于相同小程序架构的 App 中。未来,开发者或第三方服务更多是面向小程序来开发,而 App 则是提供一个统一的架构,真正做到开放生态,用完即走的理念。
mPaaS 离线包源自于支付宝原生方案,经历了严苛的业务考验,让你直接和支付宝使用同一套框架层代码,拥有统一容器及内核,相对系统内核获取更低 Crash 率和 ANR 率,适配性强,并具备良好的、弹性的扩展能力,结合具体业务需求定制 JSAPI。
它可以帮助减少 App 白屏、解决 Hybrid App 跨平台兼容与适配,提升 App 性能并大幅优化原生开发下的包大小。
目前 mPaaS 离线包 Demo 源码已更新在 GitHub 上,欢迎 Star:
https://github.com/alipay/mpaas-demo
欢迎申请试用,提更多的优化建议和使用反馈:
http://mpaas2019.mikecrm.com/otOU1k1
扫码助手小姐姐微信,进群大厂内推&大佬技术交流
后台回复“电子书” “资料” 领取一份干货,数百面试手册等你 开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。