有赞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 headerresponse header的处理

html的内容能否正确的获取、加载,request headerresponse header必须要处理无误。

  • request header

这里最重要的是CookieUser-Agent参数,Cookie可以通过CookieManagerWebView读取; 而User-Agent通过WebSettings.getDefaultUserAgent()读取再附加上自己的ua值即可。

后台系统一般都会从Cookie中读取身份、session等信息;

有赞的前端体系中User-Agent的设置非常重要,会根据UA的值确定是跳转到PC端的网页还是H5。

  • response header

response header中包括Set-CookieCSP等重要响应头信息。response headerhtml 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 请求的过程中可以拦截到。响应头的使用又可分为两种。

  1. 针对需要缓存的 HTML 页面,一并将响应头缓存,并在取缓存时将响应头和数据一并返回给 WebView;
  2. 针对不需要缓存的 HTML 页面,直接将响应头返回给 WebView。

这样处理可以保证缓存数据也有正确的响应头。

4. 对页面重定向、要求登录的处理

页面发生重定向,有可能要在不同的域名下种一些cookie, 比如有赞之前的总域名是koudaitong.com,后来是youzan.com,在这两个域名并存的时间里,相互之间就要同步一些cookie、状态信息,以免出现走到不同模块使用了不同的域名,然后发现状态不一致而出错。

对于要求登录的页面也是,要求登录后就会跳转到登录页面,这个过程在有赞也会产生一个302跳转。

当然出现重定向的情况还有很多种,比如:短链接等。每种的情况处理起来都不太一样。

为了简单化,以及避免出现未考虑到情况造成错误。我们现阶段的html加速只对不会产生重定向的页面生效。所以在上述OkHttpClient对象上设置了followRedirects(false) ,不follow重定向。

5. 对html缓存的维护

维护html缓存,除了保存response headerhtml body以外,应该设置其过期策略,如果一个页面已经请求到加载的时间过去太久就不应该使用本地缓存了,直接请求远端的结果。

在我们的金翅管理后台,有能够增加相应控制的配置入口: image

目前设置超过一个小时的html缓存直接失效。

“缓存html链接”可以动态配置让客户端预先下载的html链接,这个适合于有活动页的场景。当然我们在金翅的客户端SDK中开放了接口供用户去配置上述所有的设置项。

6. 效果对比

再来看下我们在商品详情页和微页面使用html加速优化方案后的对比:

1) 商品详情页

  • 不使用优化

image

  • 使用优化后

image

2) 微页面/店铺首页

  • 不使用优化

image

  • 使用优化后

image

注:微页面的效果图是在一款低端的android机上的测试效果。背景中的转圈是h5中的实现,html的内容实际已经加载完成了,转圈是在加载、渲染静态图片资源。

7. 写在最后

搭建一整套的h5加速平台还是需要的工作量挺庞大的。好在我们从开始时就做了足够的分析,制定分步走的计划。每一阶段需要达到什么样子,最后做成什么样子,心里都清楚。

除了我们已经在金翅平台中实现过的以外,h5加速,还有许多方面可以做的。像是优化网络协议、优化html与js的实现、后台主动推送更新等等。

业界有许多优秀的实施方案,我们在做的过程中吸取了很多经验。

文章附录如下:

  1. 美团:WebView性能、体验分析与优化
  2. 网易:如何对Android WebView 轻量缓存优化
  3. 腾讯:VasSonic:轻量级高性能Hybrid框架
欢迎关注我们的公众号