1 Android WebView 存在什么性能问题?
H5 页面加载速度慢
- 渲染速度慢
- 页面资源加载缓慢
耗费流量
每次使用 H5页面时,用户都需要重新加载 Android WebView的H5 页面
每加载一个 H5页面,都会产生较多网络请求
每一个请求都串行的,这么多请求串起来,这导致消耗的流量也会越多
2 解决方案
本文从3个角度入手
1.WebView自带机制
2.资源预加载
3.资源拦截
WebView自带(前端H5的缓存机制)
这里的缓存是什么?
缓存即离线储存,在H5网页加载后会储存在缓存区域,在无网络连接时也可以访问
android WebView的本质
在android中嵌入H5就是WebView本质,WebView自带的缓存机制其实就是H5页面的缓存机制
目前WebView暂时不支持File System缓存机制。
缓存机制:如何将加载过得网页数据保存到本地
缓存模式:加载网页时如何读取之前保存到本地的网页缓存。
3 五种缓存机制与缓存模式
3.1 缓存机制
- 浏览器 缓存机制
- Application Cache 缓存机制
- Dom Storage 缓存机制
- Web SQL Database 缓存机制
- Indexed Database 缓存机制
浏览器缓存机制(标准实现)
特点:
支持Http协议层,缓存文件需要首次加载后才产生,浏览器缓存的存储空间有限,缓存有被清除的可能,缓存文件没有校验,无需做操作,浏览器内核机制,一般都是标准实现。
应用场景:
静态资源文件的存储,如JS、CSS
、字体、图片等。Application Cache 缓存机制
特点:
方便构建Web App的缓存,专门为Web App离线使用二开发的缓存机制,
应用场景:
同浏览器缓存机制,但AppCache 是对 浏览器缓存机制 的补充,不是替代。
WebSettings mWebSettings = getSettings();
String cachePath = getContext().getFilesDir().getAbsolutePath()+"cache/";
mWebSettings.setAppCachePath(cachePath); // 1. 设置缓存路径
mWebSettings.setAppCacheMaxSize(20*1024*1024); // 2. 设置缓存大小
mWebSettings.setAppCacheEnabled(true); // 3. 开启Application Cache存储机制
- Dom Storage 缓存机制
特点:
通过存储字符串的K-V对来提供,存储空间较大(HTML5 的建议是每个网站提供给 Storage 的空间是 5MB),存储安全便捷。Dom Storage 存储的数据在本地,不需要经常和服务器进行交互
应用场景:
存储临时、简单的数据
代替 将 不需要让服务器知道的信息 存储到 cookies 的这种传统方法
Dom Storage 机制类似于 Android 的 SharedPreference机制
settings.setDomStorageEnabled(true);// 开启DOM storage
- Web SQL Database 缓存机制
特点:基于SQL Database的缓存机制,充分利用数据库的优势,可方便对数据库进行增删改查
应用场景
存储适合数据库的结构化数据
// 通过设置WebView的settings实现
WebSettings settings = getSettings();
String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
settings.setDatabasePath(cacheDirPath);// 设置缓存路径
settings.setDatabaseEnabled(true); // 开启 数据库存储机制
特别说明
根据官方说明,Web SQL Database存储机制不再推荐使用(不再维护)
取而代之的是 IndexedDB缓存机制
- IndexedDB 缓存机制
特点
属于NoSQL数据库,通过存储字符串的K-V对来提供,存储空间大,使用灵活
应用场景
存储复杂、数据量大的结构化数据。
具体实现
// 只需设置支持JS就自动打开IndexedDB存储机制
// Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关。
settings.setJavaScriptEnabled(true);
3.2 缓存模式
缓存模式是一种 当加载 H5网页时 该如何读取之前保存到本地缓存从而进行使用的方式
- Android WebView 自带的缓存模式有4种:
- LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
- LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
- LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
- LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用
//WebView.getSettings().setCacheMode(LOAD_XXX);
if (NetworkUtils.isConnected()) {
mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
} else {
mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}
4 资源预加载
- 预加载WebView对象
- 预加载H5资源
5 自身构建缓存
5.1 实现步骤
- 事先将更新频率较低、常用 & 固定的H5静态资源 文件(如JS、CSS文件、图片等) 放到本地
- 拦截H5页面的资源网络请求 并进行检测
- 如果检测到本地具有相同的静态资源 就 直接从本地读取进行替换而不发送该资源的网络请求 到 服务器获取
5.2 具体实现
mWebview.setWebViewClient(new WebViewClient() {
// 重写 WebViewClient 的 shouldInterceptRequest ()
// API 21 以下用shouldInterceptRequest(WebView view, String url)
// API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
// 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
// 图片的资源文件名为:logo.gif
if (url.contains("logo.gif")) {
InputStream is = null; // 步骤2:创建一个输入流
try {
// 步骤3:获得需要替换的资源(存放在assets文件夹里)
is =getContext().getApplicationContext().getAssets().open("images/abc.png");
// a. 先在app/src/main下创建一个assets文件夹
// b. 在assets文件夹里再创建一个images文件夹
// c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片)
} catch (IOException e) {
e.printStackTrace();
}
// 步骤4:替换资源
WebResourceResponse response = new WebResourceResponse("image/png",
"utf-8", is);
// 参数1:http请求里该图片的Content-Type,此处图片为image/png
// 参数2:编码类型
// 参数3:存放着替换资源的输入流(上面创建的那个)
return response;
}
return super.shouldInterceptRequest(view, url);
}
// API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
// 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
if (request.getUrl().toString().contains("logo.gif")) {
// 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
// 图片的资源文件名为:logo.gif
InputStream is = null; // 步骤2:创建一个输入流
try {
// 步骤3:获得需要替换的资源(存放在assets文件夹里)
is = getContext().getApplicationContext().getAssets().open("images/abc.png");
// a. 先在app/src/main下创建一个assets文件夹
// b. 在assets文件夹里再创建一个images文件夹
// c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片
} catch (IOException e) {
e.printStackTrace();
}
// 步骤4:替换资源
WebResourceResponse response = new WebResourceResponse("image/png",
"utf-8", is);
// 参数1:http请求里该图片的Content-Type,此处图片为image/png
// 参数2:编码类型
// 参数3:存放着替换资源的输入流(上面创建的那个)
return response;
}
return super.shouldInterceptRequest(view, request);
}
});