提升 Hybrid 体验:饿了么双十一 PHA 框架技术实践
作者:逍菲、崖松、子伦
饿了么端 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 下会场埋点保持一致,主要有以下两点:
需在每次切换页面时上报 PV,包括已经浏览过的页面,如 Tab A 切到 Tab B 再切到 Tab A 时,在最后的 Tab A 页面需要触发 PV 上报
底部 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 篇移动技术实践 &干货给你思考!
版权声明: 本文为 InfoQ 作者【阿里巴巴移动技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/de42f1191fca33d9be8e7089d】。文章转载请联系作者。
评论