离线Hybrid容器如何做到接近100%秒开?

avatar

一、前言

行业趋势和业界情况

当前移动端和前端的结合越来越紧密,边界越来越不明显,近几年随着设备性能、网络环境等因素得到大幅提升,制约H5发展的性能低和体验差的两个问题得到了明显改善,同时H5灵活发布、研发成本低等优势,促进H5在Native中的使用越来越多。去年(2020年)阿里手淘的双十一大促场景也从Weex全面拥抱PHA(Progressive Hybrid App)验证了H5在复杂场景下的可行性。

普惠四轮出行业务诉求

当下普惠的业务(顺风车、打车、车服、快送等)在飞速发展,面对快速的迭代诉求,不能靠一味的追加人力解决问题,需要衍生出更合理的架构,来保障高效快速的迭代,而跨端技术和容器化,则是本次架构升级的重点。

Hybrid容器现状&升级的目标

原有H5容器只有简单的JSbridge通道,受限于H5渲染的性能低、交互体验差、稳定性低、安全性不高等因素,不能满足普惠大范围使用的诉求,因此围绕提升性能和体验,提升稳定性和安全性几个方面,我们开启了Hybrid容器技术升级,目标是“保障Hybrid技术在普惠四轮出行业务中可大范围稳定使用”。

本文主要脉络

本文会从发现问题、解决方案,到上线效果介绍整个实践的过程和思考,主要脉络如下:

  • 目前Hybrid容器遇到的问题
  • 问题分析及解决方案
  • 每个点的详细方案和线上效果
  • 整体优化后的线上效果
  • 后续规划和进一步探索

二、Hybrid容器遇到的问题

image.png

问题1. 运维监控告警缺失

网络错误、白屏率等指标的监控数据不准,因当前监控的数据是由前端团队在H5页面内的,仅有进入到H5页面内,才能感知到H5页面的运行情况,而无法感知到未进入H5页面时发生的异常,导致异常数据和基数数据都不完整。

内存OOM、容器内Crash、网络差导致无法进入页面等容器内发生的异常没有数据监控,所以当线上发生容器内异常时,开发和运营同学很难发现,只能靠人工反馈的途径感知信息(客服反馈、身边同事/家人反馈等),出行行业司乘设备再行驶过程中网络情况复杂性,以及设备的多样性导致上面的问题愈加明显。

问题2. 性能低

在此之前Hybrid容器曾经上线过一版的离线方案,但是性能数据上表现偏差,当前首屏平均耗时2800ms,1s打开率仅12%,2s打开率59%。

问题3. 稳定性差

线上故障频发(普惠车服务20200910离线包白屏故障),导致业务方将离线包的优化下线。 页面逻辑复杂,即使下线离线包后,在线的H5页面白屏率仍达到了 7%。 系统兼容性差,尤其是Android机型的碎片化,很多ROM使用的非系统自带WebView或者对其做了很多魔改操作,导致兼容性偏差。

问题4. 交互体验差

Loading 样式丑陋,且Loading过程中是白屏状态,一直被设计产品团队吐槽。 打开一个正常页面,页面的导航Title会出现几次闪烁,交互细节略显粗糙。

三、问题分析及解决方案

针对Hybrid容器当前遇到的一系列问题,每一点分析下来都可以做一些事情来补齐缺陷,大幅优化现状。

针对运维监控告警缺失

对用户的直观体感而言,统计渲染时长的统计应该是从客户端的点击跳转时间,到H5页面可视LCP(The Largest Contentful Paint)的时间,包含客户端页面跳转、WebView启动等时间。 为了使体验的衡量更贴近用户真实的体感,我们联合前端进行全链路埋点,将客户端消耗的时间与H5的耗时串联起来,数据统一回流到前端的扁鹊系统,由扁鹊系统完成整个性能数据的分析和呈现,这样数据口径得到统一的同时结果也更加符合用户的真实体感。 扁鹊上的性能报告是以时间轴进行呈现,如下图

image.png

根据图示可大致将耗时分为三个阶段

第一阶段:容器的准备阶段,该阶段主要做创建容器、页面跳转、容器的LoadRequest初始化操作。该阶段大致耗时为300ms,分析得知这段耗时大多都花费在初始化上,我们通过容器复用和容器预载对这段耗时进行优化。

第二阶段:HTML资源下载阶段,该阶段主要做域名解析->创建链接->下载HTML文档操作。该阶段耗时约为850ms,主要用于HTML资源下载,因此我们可以提前将H5应用中的资源以离线包的形式下载到本地,当用户进入H5页面时,拦截加载该资源的请求,直接从本地读取资源,这样就可以做到使用耗时较短的本地IO操作代替耗时较长的网络请求操作,可缩短大量耗时。

第三阶段:H5页面处理阶段,拿到HTML文档后,就进入了H5页面,随即H5进行js执行、页面的渲染、执行Onload等操作。该阶段耗时约为1070ms,主要做下载css js资源、执行js、发起网络请求、页面渲染等操作。所以该阶段的优化可以使用离线包、请求预加载、图片资源优化技术,尽量将该阶段的网络依赖前置掉。

修正性能数据

image.png 如图:用户打开一个H5页面时,对页面加载的时长直观体感是从用户操作,到H5页面可视LCP(The Largest Contentful Paint)渲染完成,这期间包含创建容器、客户端页面跳转、请求页面资源、H5页面渲染等事件。而Native统计不到H5渲染完成的时间,H5统计不到请求页面资源之前的时间,导致衡量性能的数据不准确。 为了得到更贴近用户真实体感的性能数据,我们联合前端团队进行全链路埋点,将客户端消耗的时间与H5的耗时串联起来,数据统一回流到扁鹊系统,由扁鹊系统做多维度呈现。

细化方案:

H5获取客户端点击按钮时间和进入页面时间 客户端添加名称为 getNativePerformanceTime 的 JS bridge 方法 H5在需要获取性能时间的时候调用bridge方法,调用时将callback的JS方法名传给客户端 客户端读到callback的JS方法名,调用该JS方法,并将性能时间(WebViewInitTime、PageStartTime)放入方法参数中,回传给H5

{
classMap: 'UBTBridge',
method: 'getNativePerformanceTime',
callbackId: '1',
params: {
callbackName: 'tianQiApmEasyBikeWebviewCallback',
    }
}
// JS 方法:tianQiApmEasyBikeWebviewCallback 参数格式
window.tianQiApmEasyBikeWebviewCallback({
"code" : 0,
"data" : {
"nativePageRenderStartTime" : "1605598621744",
"nativePageNavigationStartTime" : "1605598621743"
},
"callbackId" : "1"
});

产出结果:

下图是修正数据后的扁鹊性能统计图,其中黄色和青色部分为容器创建和容器loadRequest初始化耗时。

image.png

扁鹊页面数据解释

iOS-EasyBike-offline为iOS离线数据,iOS-EasyBike-online为iOS在线数据。 Android-EasyBike-offline 为Android离线数据, Android-EasyBike-online为Android在线数据。 扁鹊统计中各时段的耗时分析 图中每段时间都按不同颜色分割开,每段时间做的事情和耗时多长,都会清晰呈现出来。

稳定性监控告警

Hybrid容器的稳定性会受到容器实现逻辑、打开的H5页面实现逻辑和系统资源使用情况(内存、CPU等)的影响,这些影响最终都有可能导致App Crash或H5页面白屏,因此我们对Hybrid容器稳定性的监控会包含Crash异常和白屏率两个指标。 另外容器层可以很容易的感知JS Bridge调用错误、网络错误、内存告警错误场景,对这3个具体场景的错误也进行了监控。

细化方案:

针对容器侧的异常我们重新梳理细化了异常点位,再公司谛听系统(App稳定性监控平台)里面快速聚合分析后进行预警。 白屏监控技术方案和落地过程过程较为复杂,单独讲 JS Bridge调用错误、网络错误、内存告警 的场景监控,需要收集数据,添加监控图表,添加告警 在发生具体错误的地方添加埋点,来收集数据 使用Argus系统配置自定义指标,将统计的数据呈现出来 在Argus系统的自定义指标中添加告警策略,在异常时触发告警

产出结果:

  1. 监控图表:

image.png 2. 告警信息:

image.png

白屏监控告警

前端团队已经有监控可以查看容器内H5页面的白屏率,但因其统计白屏率的方法受限(6秒没有打开出现文本或者图片的dom节点后则判定为白屏),这种方式有很明显的弊端:css设置的背景图片无法检测,图片加载失败的场景无法检测,检测无法覆盖到容器异常、资源加载异常等导致的白屏情况,导致统计的白屏率比真实的线上情况偏低。 对此,我们升级了白屏检测的方案,通过客户端截屏,针对白色像素颜色占比进行分析,当白色像素占比达到一定的比例,被判定为白屏状态,基于此判定技术进行白屏监控。目前已在车服务首页页面应用,且拿到了较为准确的线上白屏情况的数据。

详细方案:

  1. 检测白屏方案流程图

image.png 2. 动态化的相关配置:白屏检测算法中有几个重要的参数,可以通过动态化配置这些参数来保障线上检测的准确率。相关参数如下:

截屏做图片检测是耗内存、耗CPU的操作,我们要获取白屏率,仅需要抽样即可,因此将抽样率做成可配置的参数 为了尽量避免白屏检测对内存和CPU的消耗,我们在计算之前,将原图片进行了压缩,但如果压缩量太大,不能保障计算结果的准确性,压缩量太小,又会大量消耗内存和CPU,我们添加了一个控制图片压缩量的配置,动态的调整,来保证计算结果准确的同时,消耗尽量小的内存和CPU资源。 像素容差,很多页面用的不是纯白的背景色,而是类似#F9F9F9这样的颜色,我们配置了一个容差值,当占比最大的颜色在这个容差内,则认定为白色。 白色像素点的比例达到某个值,判定为白屏,这个数值可配置。

不足之处&改进方案:像素检测的方式也存在一定弊端

  1. 无法精准匹配,无法适应复杂业务场景(前端有自定义Preload页面,业务自定义背景 或者打底图等)像素分析不具有通用性向和业务弹性

  2. 异常错误埋点:因白屏场景较多(网络异常、业务异常、资源异常、OOM、环境启动异常等)同时部分错误回调时机不准确,我们只能监控覆盖主要异常错误,无法获取全部异常情况,导致遗漏部分数据 未来我们希望利用AI算法覆盖更多的场景类型,目前AI图像识别白屏检测已经开发中,很快会上线。未来我们希望利用AI算法覆盖更多的场景类型,目前AI图像识别白屏检测已经开发中,很快会上线。 下图为目前业务中存在的集中白屏&加载态截图。

image.png

针对性能的优化

根据H5的加载过程我们采取分而治之的策略,针对每个阶段分别进行了优化。

阶段一:容器加载优化(复用&预载)

众所周知WebView容器本身是一个内存消耗大户,初始化过程在安卓系统平均耗时146ms,iOS系统381ms,我们借鉴了线程池的概念,利用系统空闲时机,预先创建 Webview 对象池,将容器预载在内存中,需要使用 WebView 时直接从池中对象获取对象,用对象池一次创建多处复用的方式避频繁创建导致的耗时和内存抖动。 容器预加载和复用的图示如下:

image.png

容器复用&预加载 在初始化过程的优化效果

截屏2021-04-09 下午6.19.39.png

阶段二:图片加载优化

H5性能优化绕不开的一个话题是图片资源优化,特别是对于以图片为主的页面而言。通过容器Hook的方式,借助于CDN的OSS格式转换功能,我们针对页面中的图片进行了Webp的格式升级,同时配合端侧的图片加载库,打通了WebView和原生的边界,实现了图片缓存的统一,既能够减少网络请求、节省宽带资源同时有节约了数据空间。

image.png 图片优化效果回收

截屏2021-04-09 下午6.20.34.png

阶段三:H5资源优化

对于普通的H5应用,用户需要等待页面所需资源加载完成,框架初始化完成,才能开始渲染首屏页面,在这之前页面处于白屏或Loading

image.png 我们分别使用了离线包和预渲染针对H5资源加载进行了优化

H5资源优化1、离线包

离线包是利用了端侧提供的静态资源缓存方案,将HTML和Common JS 等资源,下发至客户端。 在App 通过WebView 打开h5页面时 ,拦截加载资源的网络请求,根据域名匹配对应的缓存离线资源,在匹配成功后直接从本地缓存中读取对应资源,减少了对应的请求网络、从而缩短了页面的初始化时间。

image.png 离线包文件分布:

image.png

H5资源优化2、PreRender

预渲染是针对前端单页应用首次打开的性能优化方案:

常规的前端单页应用通过js代码动态生成html/css渲染页面,等待js执行时页面空白,渲染复杂页面时首次白屏性能指标较差。 预渲染是针对前端单页应用首次打开的性能优化方案: 另外在SEO,页面数据抓取(分享卡片)等场景,预渲染的页面也要明显优于常规单页应用。 离线包和页面预载流程图:

image.png

阶段四:接口预请求

除了针对资源文件等静态数据的优化外,我们还利用App自身优势,针对动态数据获取的进行了优化, 正常打开一个页面,从用户操作开始,到页面渲染出来的耗时如图,其中页面渲染中依赖部分网络接口的返回数据,因此将网络请求前置,可节省出页面渲染的时间,示意如下图:

image.png 我们通过将WebView的请求路由给APP的网络库组件,除了复用网络库本身很多底层优化外,我们还将原有的串行变成并行,在进入H5页面之前,利用页面做跳转动画的时间,预先做H5页面中的网络请求,缩短H5的加载时间。

向H5页面注入拦截Ajax请求的js,进行Ajax请求的拦截,并将拦截的请求映射到本地预请求的接口上,将本地预请求的数据通过JS Bridge的方式回传给Ajax请求的对象。

接口预请求收益

截屏2021-04-09 下午6.24.58.png

针对交互体验优化

NavigationBar交互优化

之前Hybrid容器在进入H5页面时,title会出现闪烁,导致打开H5的体验很差,同一时间设计团队也在推进App的视觉改版,于是我们重新设计了一个高可用、易扩展的NavigationBar,新NavigationBar支持自定义背景、自定义返回和关闭按钮、右侧自定义按钮等功能。 将新的NavigationBar应用到Hybrid容器,解决Title闪烁问题的同时,也提供了Hybrid容器导航栏可定制的能力。

Loading交互层优化

针对之前页面加载过程中,UI的显示及交互样式很简陋、细节粗糙的问题,我们提供了统一的UI展示和自定义样式,如导航栏样式、页面Loading状态、进度条样式等; 目前Hybrid容器的Loading交互支持业务定制,且支持动图、静态图片和Lottie动画3种形式的资源。

弱网/无网优化

因H5页面资源已离线,图片已支持WebP,且已走缓存数据,理论上静态页面已支持弱网/无网打开页面,但是业务中使用的H5页面大部分都包含业务逻辑的,依赖API接口的返回数据,若API接口请求不能成功,H5依然无法打开和使用。 对此我们完成API接口数据的缓存,保障用户在弱网/无网环境中,可使用缓存数据打开页面,最大程度的避免业务“开天窗”。

四、优化后整体效果

1.优化后首屏耗时数据回收

截屏2021-04-09 下午6.27.08.png

2. 优化后1s打开率、2s打开率数据回收

截屏2021-04-09 下午6.27.23.png

4. 接入业务量

截止发稿时间,已接入H5页面PV总量普惠业务中占比超 80%。 包含车服务首页、个人中心页面、价格详情页等7个高PV的页面。

五、未来展望

容器后续的挑战

虽然目前容器优化取得了阶段性成果,人力资源终归是有限的,优化一期,我们主要围绕性能和稳定性入手,我们后续还有很多事情需要优化 :

  • 注入统一WebView内核,解决安卓碎片化下各种魔改系统的适配问题
  • 容器内的安全问题,鉴权拦截跳转黑白灰名单机
  • SSR ,Prefetch ,地图组件的同层渲染等深度的性能优化
  • 统一JSBridge的标准,降低业务接入维护的成本等
  • 自愈性H5链路降级服务
  • 完善研发运维平台
  • 优化离线资源下载使用率

App架构演进

当前成熟的移动端App架构都趋向容器化,提升研发效率的同时,可支持容器间的快速降级,保障稳定性,我们基于架构分层的思想,设计了哈啰App 容器化2.0的架构演进。

image.png

右侧为App内部架构,该架构主要运用分层的思想,将每一层的逻辑解耦开。整个App的业务实现由容器层承载,各页面通过统一路由进行页面跳转及数据传递,容器内事件通过协议总线层分发给对应的handler处理。下面能力层为封装的基础组件和业务组件,可供容器内业务直接使用,App底层为平台基础,供所有业务使用。 左侧为App运维支撑平台,包含发布构建、运维平台、性能度量、监控告警几个部分,职责也一目了然,为线上App的健康运行保驾护航。

流畅体验&安全&高稳定

对用户来说,稳定性和丝滑体验只是最基础要求,后续我们会在安全和性能上持续优化,带给用户真正的如丝般顺滑的体验。

作者简介

逸书(胡峰) 移动端开发专家

唐永祥 资深移动工程开发工程师

任泽强 高级移动端开发工程师

均来自普惠用车研发-业务治理团队

打个广告

我们是负责普惠四轮出行业务的大前端提效技术团队,这里有丰富业务场景和技术挑战,我们将持续建设及完善Hybrid容器的稳定性、提升用户体验。 如果您有兴趣或有身边的人在找工作(移动端前端不限),可将简历发至:hufeng071@hellobike.com,期待您的加入!