提升 Hybrid 体验:饿了么双十一 PHA 框架技术实践

2,340 阅读13分钟

作者:逍菲、崖松、子伦

饿了么端 618、国庆、双11、双12等大促会场基本上会标配底部导航,在之前普通H5容器中底部导航是前端实现,每次点击会场底部导航的tab,都会重新启动一个活动页面覆盖在上面,即使之前打开过的tab也都要重新创建和加载,体验不佳,且H5也不能很好的结合Native能力做进一步的体验和性能优化。

经过调研发现手淘PHA框架可解决上述痛点问题,PHA容器底部TabBar为Native渲染,tab点击时底部bar不会重建,tab对应的webview在整个PHA容器中也可以平滑过渡、无缝切换,无需另起容器。且加载过的tab活动页面Webview会常驻内存,当再次访问时会直接切换至前台,更接近native体验。

在去年 618、国庆、双11和双12大促中,结合饿了么业务特性又陆续落地了一些特色优化手段,带来了更好的性能体验和业务成果。

双十一上线效果

效果视频查看请点击:饿了么双十一 PHA 会场实践

老容器和pha容器对比。其中左侧为老容器会场,右侧为pha容器会场。

PHA 简介

什么是 PHA,PHA 全称 Progressive Hybrid App,是提升 Hybrid 体验的一种新框架,提供了一些 Native 同层组件以及渐进式增强策略来创建 Hybrid APP 应用,让这些应用具有与 Native 相同的用户体验。

  • PHA 使用了 Web Application Manifest的配置并且对配置进行了功能扩展
  • 每个 PHA 应用都会启动一个 App Worker,Worker 是独立于当前页面运行在客户端里的一段 JS 脚本。可在 Worker 中定制业务逻辑,如基于 LBS 请求底部Tab展示的数据列表
  • 应用下可以有多个页面,每个页面的默认渲染引擎是 WebView
  • 每个页面中 PHA 提供了像下拉刷新、页头等 UI 能力,都可以通过在 Manifest 中定制
  • 针对应用 PHA 还提供了 Tab 容器、Swiper容器、启动屏等 UI 能力和预请求、离线缓存等性能优化能力,可通过在 Manifest 中配置实现

pha架构图

饿了么接入方案

本地生活跟淘宝等业务主要的区别为前者强依赖LBS属性,包括底部 Tab、商品、品牌等数据的召回。因此需要在用户打开PHA框架时,执行定位并加载对应的底部 Tab、顶部横滑数据后,动态组装出对应的manifest.json 数据来渲染PHA,整体架构图如下:

架构图

B端链路

墨斗平台依赖天马源码页面服务来创建会场框架,沿用墨斗数据搭建来配置底部 Tab 和 顶部Swiper 的数据,实现定投,主要流程如下:

注:“墨斗”是一个模块搭建平台,“天马”可以提供基础搭建服务。

数据搭建

数据搭建是墨斗中提供的数据多排期定投能力,配置定投并发布后可得到当前配置的唯一资源位ID, 业务可通过异步接口获得配置数据。

会场框架工具

新增会场框架工具,工具提供了会场框架创建、发布及编辑能力,创建并发布框架后即可得到框架投放链接。

C端链路

C端链路由客户端结合前端实现,主要包含以下3部分:

1、客户端PHA框架:集成了 Tabs 容器、Swiper容器、启动屏、Appworker、预加载等框架核心能力

2、源码模版页:作为PHA框架的入口,同时提供 PHA 正常加载入口 (native.xtpl) 和降级入口 (web.xtpl)

3、AppWorker 服务:能在框架渲染前有机会根据业务诉求定制 manifest,包括头部横滑容器的 UI 定制,事件处理、埋点等

整体加载链路图

客户端接入改造

路由层

改造饿了么路由层,增加pha入口,并在入口处增加native的降级策略、灰度控制等逻辑。

适配器:native导航头适配

由于剥离了手淘的ui库,饿了么需要自行实现导航栏的: 显示、隐藏、各类主题、滚动变色、title设置、logo设置、事件回调、降级跳转等,此外双端也提供了一些导航头、状态栏等私有jsapi供前端进行调用。

iOS端通过PHANavigationBarProtocol实现对应方法,Android端以系统Toolbar为基础,定制饿了么的TranslucentToolbar,以Fragment的形式嵌入到PHA容器Activity中。

下图是native端导航头,title支持设置文字、加载图片,导航栏主题支持透明、白色:

文字&图片导航栏

下图是light和dark风格的状态栏:

light&dark状态栏

适配器:图片加载适配

饿了么iOS端由于自己维护了图片库,需要对图片加载进行适配,实现PHAImageLoaderProtocol及其图片加载相关方法。

淘系依赖剥离

饿了么对于包大小有严格的控制,对于新引入的二三方库审核严格。饿了么端接入pha较早,早期版本中存在额外的手淘依赖,且有些功能饿了么暂时未用到,或已有类似的实现方案,因此需要对部分手淘依赖库进行剥离,对不需要的功能进行剪裁。

安卓剥离的直接依赖库约20个,如:直播库、公共资源库、ui库、compat库、启动框架库、atlas等,总大小约:7.2M+,涉及文件改动个数:100+

注:目前安卓最新的官方2.0版本已剥离了这些依赖,大大的赞!iOS端在一接入pha时依赖已基本剥离,无需大的改造。

前端接入改造

框架服务

1、源码模版:作为PHA框架的入口,同时提供 PHA 正常加载入口 (native.xtpl) 和降级入口 (web.xtpl)

  • 正常入口:基于平台创建会场框架时录入的数据生成manifest 半成品,结果中包含 AppWorker 地址,创建框架时的配置信息等,客户端执行 AppWorker 文件后,启动PHA框架渲染
  • 降级入口:为不支持PHA的低版本或PHA创建失败时提供统一的降级处理,用普通H5容器打开投放链接中的 downgradeUrl 地址实现降级,保证业务可用

2、AppWorker 服务:能在框架渲染前有机会根据业务诉求定制 manifest,主要提供如下能力

  • 定位、请求底部bar、顶部swiper数据
  • 头部横滑容器的 UI 定制、事件处理
  • 和PHA的事件、消息处理
  • 构建最终的 manifest 文件
  • 启动框架渲染
  • 埋点等

会场链路

1、页面solution、基础util、componet 改造

2、多个模块改造

  • 透明头模块:之前透明头是H5结合Native来实现的,业务定制能力相对较弱,为了解决这个题,针对PHA容器实现了纯H5版的透明头模块
  • 底部导航模块、搜索模块、分享模块:兼容PHA 和非PHA容器

埋点改造

PHA框架提供一套埋点API,客户端和前端需根据自己的埋点方案进行适配改造,统计和上报口径跟非PHA下会场埋点保持一致,主要有以下两点:

  1. 需在每次切换页面时上报PV,包括已经浏览过的页面,如Tab A 切到Tab B 再切到Tab A 时,在最后的Tab A 页面需要触发PV上报
  2. 底部 Tab 或顶部 Swiper 容器切换不同 Item 时上报点击埋点

性能优化

饿了么端pha容器加载流程主要为下图四个阶段,针对不同阶段可进行对应的性能优化手段。

  • pha容器阶段:半成品manifest和worker可进行预取或缓存优化;完整manifest数据请求组装阶段可进行经纬度的预取和接口的预请求;
  • webview阶段:webview可进行预初始化,主文档加载可以用模版化方式进行优化;
  • 业务阶段:针对业务js、css资源可进行资源离线和缓存,对常用js资源可进行内置;对业务接口数据可进行预请求;预热和预渲染。

官方PHA框架优化手段

pha框架本身提供了manifest预请求和缓存、html模板化、离线资源存取、内置Js预渲染等优化手段。

以iOS端双十一会场为例,使用manifest和worker预取,优化容器创建时间200ms以上。

饿了么端特色性能优化

PHA webview预热

打开pha首屏页面和切换tab时需要执行容器创建和主文档加载,有较长时间的白屏,体验不佳。预渲染页面无疑是性能效果最佳手段,但一方面会造成资源浪费,另一方面也会带来很大的内存压力,不能随意滥用,后续若能和端智能更好结合或许才能更好的发挥它的价值。

饿了么端对首屏外的tab采用了预热方案(预热页面最大个数可配置),来消除tab切换时的白屏时间。预热和预渲染的不同点为:预热在离屏阶段拿到主文档后不发起首屏接口请求。在首屏页面渲染完成后,将剩下的底部和顶部item对应的webview都预热并缓存好,当用户点击对应tab时再消费webview进行上屏操作,具体实现复用pha/preload子模块链路并进行了一些改造,参考了TSchedule的预渲染逻辑。主要流程如下图所示:

优化前后链路对比:

下面具体展示预热效果(左侧为未开启预热,右侧为开启预热状态):

效果视频请点击查看:饿了么双十一 PHA 会场实践

业务接口预加载

会场页面的首屏接口耗时较长,部分甚至达2s以上,为了缩短页面加载耗时,对首屏接口进行预请求。考虑到饿了么端之前已通过TSchedule支持了接口预请求的能力和包大小问题,因此决定不引入pha的prefetch模块,而是借助饿了么现有能力对pha进行data prefetch的适配化改造。

由客户端同学提前发布orange(开关配置发布平台)预请求配置,pha会场在首屏路由、底部tab切换、顶部pageheader切换时提前触发接口预请求,也提供jsapi供前端自行调用预请求,优化效果明显,目前已经是饿了么端会场业务必备优化项。

(1)、首屏webview加载前网络数据异步预请求

(2)、启动前路由阶段网络数据异步预请求

(3)、tab切换webview加载前网络数据异步预请求

(4)、通过jsbridge在webview加载前网络数据异步预请求

完整manifest数据请求优化

本地生活业务多对定位有强依赖,对于会场业务目前只能先返回半成品manifest文件,在worker中调用端上jsapi拿到经纬度后发起接口请求,获取完整manifest相关数据进行组装;对于非会场的单页面业务进行接口请求时也大都需要经纬度信息。

经纬度预取

方式一: 将经纬度信息注入半成品manifest(针对需要pha worker对半成品manifest文件进行处理的业务场景)

在回传给前端的manifest半成品json中,追加上首页缓存的经纬度数据,省去一次jsbridge调用耗时。优化前后流程对比如下图所示:

追加的经纬度信息模板:

方式二: 在入口链接处传入经纬度(针对无需pha worker的场景)

大多单页面业务在一开始就能获取到完整的manifest数据,不需要worker进行额外处理,也就无法直接通过方式一获得注入的经纬度信息,因此我们采用了另一个方案,给pha入口链接(manifest url)拼接经纬度参数,并通过pha参数透传的能力将其拼接到H5内页的链接上。具体流程如下:

完整manifest数据接口预请求

在worker获取经纬度后,会使用页面id、资源id和经纬度发起一个接口请求,获取完整manifest所需数据(pages、tab等),数据返回后拼接成完整manifest数据,然后调用jsapi通知端上更新manifest,进行tab和页面的加载,此接口耗时大概200ms左右。

通过在路由阶段预请求此接口来进行优化,由于接口所需的页面id和资源id针对同一大促会场投放期间基本不会改变,因此提前发布的预加载配置将这两个id直接设为固定值,而经纬度在预请求发起时动态获取。

此功能上线之后经过两个版本的对比,首屏耗时整体减少约150ms。

稳定性

降级

PHA框架渲染前的任何一步出错可能导致框架初始化失败,为实现同一个链接投放到饿了么新老版本,并保证业务在各种异常场景下可用,需做到无缝降级到H5容器,流程如下:

预案

在整个双十一大促开始前,针对pha会场可能出现的各种异常情况进行了预案的录入和演练,例如:会场降级,预热关闭、接口预加载关闭、manifest预取关闭等,在安全团队和测试团队同学的共同支持保障下,pha容器会场在大促期间运行稳定,未出现任何重大故障。

监控和报表

在dp2监控平台构建了manifest命中率监控、离线资源命中率监控、降级监控、性能监控、预热命中率监控、首屏耗时监控、容器内webview加载耗时监控、白屏监控等相关报表。

此外手淘目前在建设容器大盘,ios端已经初步进行了接入,期待大盘后续例如告警,二方业务数据展示等建设。

总结和展望

通过pha框架的接入及一系列的优化手段,双端优化首屏耗时减少650ms左右,消除了切换tab时的白屏时间,提升了饿了么端大促会场的用户体验,同时也促进了业务数据的提升。

据运营侧数据统计,去年双十一主会场底部导航Tab的曝光点击率,相比国庆大促正式期提升超过50%。自618大促正式期试点PHA以来,新底部导航点击率持续上升。

后续前端需解决拼链接的问题作为常态化的产品能力支持各业务场景使用,端侧也将进一步探索其他的优化方案,比如: Tabbar直出渲染、pha容器Fragment化、quickjs引入等。此外除会场业务,饿了么端超会部分业务也进行了pha投放,取得了一些性能上的收益。

期望未来饿了么所做的优化点能给大家一些启发与思考,服务更多合适业务,进一步提升用户体验。

关注【阿里巴巴移动技术】微信公众号,每周 3 篇移动技术实践&干货给你思考!