有赞 webview 加速平台探索与建设(三)——html 加速
1. 概要
从之前的两篇文章当中,已经分析了我们的金翅 h5 加速平台,以及如何做静态资源的加速。这一章将主要集中在如何做 html 加速优化。
html 加速优化也是所有优化手段中,对白屏时间优化效果最为明显的!
以预取 html 内容作缓存的方式实现加速,需要解决以下问题:
- 如何在 native 端代理 html 请求?
- request header 和 response header 如何处理?
- 遇到页面重定向、要求登录如何处理?
- 如何维护 html 缓存,包括其 header 部分、body 部分?
- 在 html intercept 过程,做缓存读取、加载
实现 native 端代理请求 html 内容,还有一个好处:从WebView.loadUrl(url)
开始,实际要先加载WebView
内核后才会真正的发起 html 请求。
如果 native 端实现代理发送 html 请求,则可以将WebView
内核加载与发送请求 html,两个过程并行起来。在调用WebView.loadUrl(url)
同时或之前,发起 html 请求。这就至少节约了WebView
内核加载的这段时间(约 100ms~250ms)。
2. native 端代理 html 请求
Android 端具体的实现,我们直接看示意代码。这里使用了OkHttp
作为网络请求库。
// 定制request header
Map<String, String> requestHeader = new HashMap<>();
requestHeader.put("method", "GET");
requestHeader.put("Host", mHtmlUrl.getUri().getHost()); // mHtmlUrl就是html的链接
requestHeader.put("Accept", "text/html");
requestHeader.put("Accept-Encoding", "gzip"); // 注意这里,如果html body以gzip的方式返回,后面读取的时候要解压缩
requestHeader.put("Accept-Language", "zh-CN,zh;");
// 设置cookie和ua这两步最为重要,大部分后台服务器需要根据这两个参数获取信息、作跳转等
requestHeader.put("Cookie", "xxx");
requestHeader.put("User-Agent", "xxx");
// 构建OkHttpClient
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.followRedirects(false) // 不follow重定向
.followSslRedirects(false)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
Call call = okHttpClient.newCall(request);
Response response = call.execute();
// 获得response headers
Headers headers = response.headers();
// 获取html body的网络输入流
InputStream byteStream = mResponseBody.byteStream();
String contentEncoding = headers.get("Content-Encoding");
if ("gzip".equalsIgnoreCase(contentEncoding)) {
// 如果html body是gzip压缩,则解压
inputStream = new BufferedInputStream(new GZIPInputStream(byteStream));
} else {
inputStream = new BufferedInputStream(byteStream);
}
// 最终拿到了html body的inputStream供读取
iOS 端仍然是通过 NSURLProtocol 代替 Webview 请求 HTML。
3. request header
和response header
的处理
html 的内容能否正确的获取、加载,request header
和response header
必须要处理无误。
request header
这里最重要的是Cookie
及User-Agent
参数,Cookie
可以通过CookieManager
从WebView
读取; 而User-Agent
通过WebSettings.getDefaultUserAgent()
读取再附加上自己的 ua 值即可。
后台系统一般都会从Cookie
中读取身份、session 等信息;
有赞的前端体系中User-Agent
的设置非常重要,会根据 UA 的值确定是跳转到 PC 端的网页还是 H5。
response header
response header
中包括Set-Cookie
及CSP
等重要响应头信息。response header
和html body
的内容必须一起被加载到WebView
中。
在 Android 中,WebViewClient.shouldInterceptRequest(view, url)
返回的WebResourceResponse
提供了
void setResponseHeaders (Map<String, String> headers)
方法设置响应头(注意这个接口从 api level>=21 才提供) 。
在我的测试中,通过这个接口设置的响应头,Set-Cookie
似乎一直没有生效,所以我们还是采用了CookieManager
来手动设置:
List<String> cookies = headers.toMultimap().get("Set-Cookie"); // headers即从`OkHttp`返回response header对象
CookieSyncManager.createInstance(context); // 这句一定得调,否则在一些低端机型上有莫名崩溃
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
for (String cookie : cookies) {
cookieManager.setCookie(url, cookie);
}
// 另注:上述设置response cookie的代码不能在shouldInterceptRequest中调用,需要异步化。
// 参考:https://issuetracker.google.com/issues/36989494#c8
在 iOS 中,Request Header 可以从 NSUserDefault
中读取,另外再拼上自定义的请求头即可。而 Response Header 则在 NSURLProtocol 代替 WebView 请求的过程中可以拦截到。响应头的使用又可分为两种。
- 针对需要缓存的 HTML 页面,一并将响应头缓存,并在取缓存时将响应头和数据一并返回给 WebView;
- 针对不需要缓存的 HTML 页面,直接将响应头返回给 WebView。
这样处理可以保证缓存数据也有正确的响应头。
4. 对页面重定向、要求登录的处理
页面发生重定向,有可能要在不同的域名下种一些 cookie, 比如有赞之前的总域名是koudaitong.com
,后来是youzan.com
,在这两个域名并存的时间里,相互之间就要同步一些 cookie、状态信息,以免出现走到不同模块使用了不同的域名,然后发现状态不一致而出错。
对于要求登录的页面也是,要求登录后就会跳转到登录页面,这个过程在有赞也会产生一个 302 跳转。
当然出现重定向的情况还有很多种,比如:短链接等。每种的情况处理起来都不太一样。
为了简单化,以及避免出现未考虑到情况造成错误。我们现阶段的 html 加速只对不会产生重定向的页面生效。所以在上述OkHttpClient
对象上设置了followRedirects(false)
,不 follow 重定向。
5. 对 html 缓存的维护
维护 html 缓存,除了保存response header
及html body
以外,应该设置其过期策略,如果一个页面已经请求到加载的时间过去太久就不应该使用本地缓存了,直接请求远端的结果。
在我们的金翅管理后台,有能够增加相应控制的配置入口:
目前设置超过一个小时的 html 缓存直接失效。
“缓存 html 链接”可以动态配置让客户端预先下载的 html 链接,这个适合于有活动页的场景。当然我们在金翅的客户端 SDK 中开放了接口供用户去配置上述所有的设置项。
6. 效果对比
再来看下我们在商品详情页和微页面使用 html 加速优化方案后的对比:
1) 商品详情页
- 不使用优化
- 使用优化后
2) 微页面 / 店铺首页
- 不使用优化
- 使用优化后
注:微页面的效果图是在一款低端的 android 机上的测试效果。背景中的转圈是 h5 中的实现,html 的内容实际已经加载完成了,转圈是在加载、渲染静态图片资源。
7. 写在最后
搭建一整套的 h5 加速平台还是需要的工作量挺庞大的。好在我们从开始时就做了足够的分析,制定分步走的计划。每一阶段需要达到什么样子,最后做成什么样子,心里都清楚。
除了我们已经在金翅平台中实现过的以外,h5 加速,还有许多方面可以做的。像是优化网络协议、优化 html 与 js 的实现、后台主动推送更新等等。
业界有许多优秀的实施方案,我们在做的过程中吸取了很多经验。
文章附录如下: