对比 Native App 高性能低维护性和 Web App 低性能高维护怀的优劣,Hybrid App 以 WebView + Html 模式可以算是结合了两者的优点 —— 既在很大程度上提高了性能,也加强了可维护性。这也是越来越多应用选择 Hybrid 开发模式的原因。但 Hybrid 虽然在原生的基础上增加了可维护性,但性能方面较原生也是做了一些妥协。 今天要讨论的是启动问题和优化策略。
Hybrid 主要有两类性能问题:
我们今天主要探讨第1个问题。
从启动 Web 页面,到渲染完整个页面主要经历以下过程:
可以看到一个页面的初始化需要做的事情还是挺多的,这些流程也是影响用户体验的主要原因,而我们要做的优化就是减少这个过程。
提前说一下,我所做的应用是一个完全由 Native 壳子内嵌 WebView 的,几乎没有原生的业务页面,每次启动 App,启动页完了就是 WebView 所在的页面。
无论是 iOS 还是 Android,本地 WebView 初始化都要不少时间,如果可以预先初始化好 WebView 将节省不少时间。
在一个进程中首次初始化 WebView 要比第二次慢很多,这是因为 WebView 一但初始化,即使其所在 Activity destroy 掉,也将在内存长期驻留。所以我们要做的就是针对这个特性做提前预加载,从而减少实际打开 WebView 页面时的初始化时间。
我看到一些文章中说可以在应用启动时预初始化一个 WebView,然后释放掉,这样等用户走到 Web 页面去加载 WebView 时就会快很多。而我所使用的方案是:将启动页面和 Web 页面放到同一个页面中,这样在启动图加载的时候 WebView 就可以开始做一系列初始化和网络请求操作,让用户完全无感知下已经将首页加载完毕,即使请求慢一些也会在启动图完后短时间内将资源加载完成,更不可能有白屏的情况出现。当然,我这个方案不能适用于所有的场景,比如我的应用,启动页完后的主页就是 Web 页面,所以这个方法是非常有效的。有些 Hybrid 应用是以 Native 页面为主,Web 页面为辅的,这种情况就适用 App 启动时初始化一个 WebView 这种方案。
请求 JS 和 CSS 资源也是非常消耗时间的,而我们要做的就是将这些资源作为离线包存储于手机中,在请求这些资源的时候不用每次都从网络请求加载,只需要使用本地资源即可。
资源类型:
每次启动应用的时候会请求网络查看是否有更新,如果有更新会在启动页显示一个类似王者荣耀那种加载资源的进度条,待加载完成后再进入 App。
我们可以先阻塞加载图片,待 WebView 渲染完后解除阻塞,加载图片。
settings.setBlockNetworkImage(true);
合并资源,减少 HTTP 请求数,minify / gzip 压缩。
在页面中内联 DOM 片段实现页面骨架提前渲染。这个貌似在支付宝中比较常见,为了减少页面白屏等待时间,将页面放少量的 html 骨架布局显示占位,待页面全部加载完后再行替换,这样可以有效减少等待时间。
以前 Web 页面的策略是列表中没有滑动到的 item 图片资源也会加载,之后引入了 vue-lazyload 库,对图片设置懒加载,使未进入可视区域的图片不进行加载,节省网络资源和渲染开销。
优化完后很重要的一点是做性能测试:针对不同设备、不同网络环境做交叉性能测试。
主要测试方法是:
利用 Charles 工具代理手机网络来模拟不同的网络环境,做出弱网情况测试。
利用 debug 配置管理工具开启和关闭主线资源,并对比有离线资源和无离线资源两者的加载时间。
这是优化完后对页面加载时间做了测试,平均性能提升大约80%。
在启动图显示时阻塞式下载离线包,待更新成功后再加载页面,保证获得最新的版本。如果访问版本更新接口3秒内无结果,则跳过直接进入首页。
为防止离线包在不同环境重用,当切换了正式/测试环境后,我们清除了本地离线包缓存。
如果本地没有更新下来离线包,则直接做网络请求最新资源。
由于我们把主页面是打到 APK 中的,请求的时候也是通过 file 协议请求的,而其内部的 Ajax 请求是要请求服务端接口的,这就造成了跨域请求失败问题。
解决方案:
客户端 WebView 设置以下属性,以允许 file 协议的页面内部可以向其他源发起 http/https 请求。
setAllowUniversalAccessFromFileURLs(true);
在 url 中添加环境参数:
file:///data/app/main.html?env=prod
file:///data/app/main.html?env=test
API 21+
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
API 21-
public WebResourceResponse shouldInterceptRequest (WebView view, String url)
原来5.0或以下的网络请求是走另一个方法,所以在请求拦截的时候2个方法都要判断。