本文首发于微信公众号「玉刚说」
前言
现在许多app都嵌入了H5页面, 然而WebView加载速度慢这个问题却一直影响着用户的体验, 所以本文就如何提高H5页面的加载速度展开讨论。
问题原因
首先我们需要知道为什么WebView的加载速度那么慢。H5页面的渲染速度其实主要取决于两个
- js解析效率
如果js文件较多、解析比较复杂, 就会导致渲染速度较慢。或者手机的硬件性能比较差的话, 也会导致渲染速度比较慢。 - 页面资源的下载
一般加载一个H5页面, 都会产生较多的网络请求, 如图片、js文件、css文件等, 需要将这些资源都下载完成之后才能完成渲染, 这样也会导致页面渲染速度变慢
对于上面的第一点, 其实主要是由前端代码和手机硬件决定的, 因为我们这里讨论的是对于app的性能优化, 暂时不考虑, 所以我们可以从第二点做文章, 主要思路就是一些资源文件都使用App本地资源, 而不需要从网络下载, 从而提高页面的打开速度。
代码实现
以加载玉刚说的renyugang.io/post/75这个页面为例。
首先将一些资源文件放在本地的assets目录, 然后重写WebViewClient的shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(WebView view, WebResourceRequest request)这两个方法, 对访问地址进行拦截, 当url地址命中本地配置的url时, 使用本地资源替代, 否则就使用网络上的资源。
YuGangShuoWebActivity:
mWebview.setWebViewClient(new WebViewClient() {
// 设置不用系统浏览器打开,直接显示在当前Webview
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// 如果命中本地资源, 使用本地资源替代
if (mDataHelper.hasLocalResource(url)) {
WebResourceResponse response =
mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
url);
if (response != null) {
return response;
}
}
return super.shouldInterceptRequest(view, url);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
String url = request.getUrl().toString();
if (mDataHelper.hasLocalResource(url)) {
WebResourceResponse response =
mDataHelper.getReplacedWebResourceResponse(getApplicationContext(),
url);
if (response != null) {
return response;
}
}
return super.shouldInterceptRequest(view, request);
}
});
复制代码
DataHelper是一个工具类, 代码如下:
public class DataHelper {
private Map<String, String> mMap;
public DataHelper() {
mMap = new HashMap<>();
initData();
}
private void initData() {
String imageDir = "images/";
String pngSuffix = ".png";
mMap.put("http://renyugang.io/wp-content/themes/twentyseventeen/style.css?ver=4.9.8",
"css/style.css");
mMap.put("http://renyugang.io/wp-content/uploads/2018/06/cropped-ryg.png",
imageDir + "cropped-ryg.png");
...
}
public boolean hasLocalResource(String url) {
return mMap.containsKey(url);
}
public WebResourceResponse getReplacedWebResourceResponse(Context context, String url) {
String localResourcePath = mMap.get(url);
if (TextUtils.isEmpty(localResourcePath)) {
return null;
}
InputStream is = null;
try {
is = context.getApplicationContext().getAssets().open(localResourcePath);
} catch (Exception e) {
e.printStackTrace();
return null;
}
String mimeType;
if (url.contains("css")) {
mimeType = "text/css";
} else if (url.contains("jpg")) {
mimeType = "image/jpeg";
} else {
mimeType = "image/png";
}
WebResourceResponse response = new WebResourceResponse(mimeType, "utf-8", is);
return response;
}
}
复制代码
我们抓包看一下修改前后的网络请求的对比。
优化前, 有n个实际发出的网络请求:
优化后, 只有一个实际发出的网络请求。并且为了和网络的资源图片做区分, 我在两张本地图片中加了“本地”的水印, 能明显看到这时候加载的是本地图片:
另外再提一点, 对于WebViewClient的shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(WebView view, WebResourceRequest request)这两个方法, 经本人亲测, 重写其中的任何一个都能生效, 后面一个shouldInterceptRequest(WebView view, WebResourceRequest request)一般是5.0以上的系统使用。我个人的建议是把这两个方法都重写了。
关于WebView的缓存
我们再看一个有意思的现象, 在不配置本地资源的时候, 我们第一次打开页面, 产生了n多个请求。但是当我们退出后再次打开这个页面(没有设置加载本地资源)的时候, 居然只发生了一次请求, 这现象与加载本地资源十分相似。