WebView 优化

WebView 初始化过程

普通用户的角度

  1. 交互无反馈
  2. 达到新的页面,页面白屏
  3. 页面基本框架出现,但是没有数据;页面处于 loading 状态
  4. 出现所需的数据

程序上

img

优化点

  • 首次初始化事件:客户端冷启动后,第一次打开 WebView,从开始创建 WebView 到开始建立网络连接之间的时间
  • 二次初始化时间:再打开过 WebView 后,退出 WebView,再重新打开 WebView,从开始创建 WebView 到建立网络连接之间的时间
  • 初始化完毕后:WebView 加载冗余内容导致的缓慢。如一些复杂的 css 文件,js 文件,png 图片等

1.WebView 初始化时间

一般我们测量一个网页打开的快慢为标准的,都是以网络连接开始作为起点的。但是 WebView 中用户体验到的打开时间需要再增加 70~700ms。这是因为 WebView 需要进行一系列的初始化操作。这也就是为什么在 WebView 中会感觉慢。

  • 在浏览器中,我们输入地址时(甚至之前),浏览器就可以开始加载页面。
  • 而在客户端中,客户端需要先花费时间初始化 WebView 完成后,才开始加载。而这段时间,由于 WebView 还不存在,所有后续的过程是完全阻塞的。

优化方法:

由于这段过程发生在 native 的代码中,单纯靠前端代码是无法优化的;大部分的方案都是前端和客户端协同完成。

1.1 全局 WebView

方法:

  • 在客户端刚启动时,就初始化一个全局的 WebView 待用,并隐藏
  • 当用户访问了 WebView 时,直接使用这个 WebView 加载对应网页,并展示

这种方法可以比较有效的减少 WebView 在 App 中首次打开时间。当用户访问页面时,不需要初始化 WebView 的时间。

缺点:

  • 额外的内存消耗
  • 页面间跳转需要清空上一个页面的痕迹,更容易内存泄漏

1.2 客户端代理数据请求

  • 在客户端初始化 WebView 的同时,直接由 native 开始网络请求数据。
  • 当页面初始化完成后,向 native 获取其代理请求的数据

这种方法虽然不能减少 WebView 初始化时间,但数据请求和 WebView 初始化可以并行进行,总体的页面加载时间就缩短了。

2.与服务器连接的建立

在页面请求的数据返回之前,主要有以下过程耗费时间:

  • DNS
  • connection
  • 服务器处理

优化方案:

DNS 采用和客户端 API 相同的域名,当我们初次打开 App 时:

  • 客户端首次打开都会请求hflldr.voicecloud.cn,其 DNS 将会被系统缓存
  • 然而当打开 WebView 的时候,由于请求了不同的域名,需要重新获取xiaofeiddubao.com的 IP

如果 WebView 的域名与 App 的 API 域名统一,则可以让 WebView 的 DNS 时间全部达到 1.3ms 的量级。静态资源同理,最好和客户端的资源域名保持一致。

3.页面加载和渲染

页面加载有时会是一大部分时间花在图片资源的请求上,因此好的解决方案就是延迟这些图片的加载,先加载并展示非图片的内容。

解决方案:

WebView 有一个 setting 配置方法:setBlockNetworkImage(boolean),该方法的作用是是否屏蔽图片的加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon){
super.onPageStarted(view, url, favicon);
mCommonWv.getSettings().setBlockNetworkImage(true);
}

@Override
public void onPageFinished(WebView view, String url){
super.onPageFinished(view, url);
mCommonWv.getSettings().setBlockNetworkImage(false);
if(!mCommonWv.getSettings().getLoadsImagesAutomatically()){
//设置 webView 加载图片资源
mCommonWv.getSettings().setBlockNetworkImage(false);
mCommonWv.getSettings().setLoadsImagesAutomatically(true);
}
}

4. 使用缓存

1
2
3
4
5
6
7
8
9
10
webSetting.setJavaScriptEnabled(true);
webSetting.setDomStorageEnabled(true);//开启DOM形式存储
webSetting.setDatabaseEnabled(true);//开启数据库形式存储

String appCacheDir = this.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
webSetting.setAppCachePath(appCacheDir);
webSetting.setAppCacheEnabled(true);
webSetting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//缓存模式
webSettig.setAllowFileAccess(true);
webSetting.setAppCacheMaxSize(size);//设置缓存文件大小,但现在已不再提倡这个方法

LOAD_CACHE_ONLY:不使用网络,只读取本地缓存数据
LOAD_DEFAULT:根据 cache-control 决定是否从网络上取数据
LOAD_CACHE_NORMAL:API level 17 中已经废弃,从 API level 11 开始作用同 LOAD_DEFAULT 模式
LOAD_NO_CACHE:不使用缓存,只从网络获取数据
LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者 no-cache,都使用缓存中的数据

5.前端支持

除此之外,前端页面也要控制好资源的压缩,css、js的加载顺序等。