WebView相关知识点

摘要

1.简单介绍WebView的缓存,详细参考H5 缓存机制浅析 移动端 Web 加载性能优化

2.新闻中使用WebView涉及到的点。

3.Android WebView的Js对象注入漏洞解决方案

WebView的缓存

目前缓存有以下6种,我们只介绍两种。

Dom Storage(Web Storage)存储机制

DOM 存储被设计为用来提供一个更大存储量、更安全、更便捷的存储方法,从而可以代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法。

  1. Dom Storage 是通过存储字符串的 Key/Value 对来提供的,并提供 5MB (不同浏览器可能不同,分 HOST)的存储空间(Cookies 才 4KB)。

  2. Dom Storage 存储的数据在本地,不像 Cookies,每次请求一次页面,Cookies 都会发送给服务器。

简单介绍一下Dom Storage,它包含两种机制:

  • sessionStorage 为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。

  • localStorage 同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。

在Android端想要使用Dom Storage只要进行如下设置:

1
2
3
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDomStorageEnabled(true);

有上述那么多缓存机制,为何新闻正文的图片加载,还是让Native APP做缓存?

点此查看各版本浏览器对存储容量的描述详情

通过查看存储的容量描述很清楚的看到,存储容量就特别少,并不足够支撑新闻页面的图片存储。

Indexed Database 存储机制

IndexedDB 是一种灵活且功能强大的数据存储机制,它集合了 Dom Storage 和 Web SQL Database 的优点,用于存储大块或复杂结构的数据,提供更大的存储空间,使用起来也比较简单。可以作为 Web SQL Database 的替代。不太适合静态文件的缓存。

  1. 以key-value 的方式存取对象,可以是任何类型值或对象,包括二进制。
  2. 可以对对象任何属性生成索引,方便查询。
  3. 较大的存储空间,默认推荐250MB(分 HOST),比 Dom Storage 的5MB 要大的多。
  4. 通过数据库的事务(tranction)机制进行数据操作,保证数据一致性。
  5. 异步的 API 调用,避免造成等待而影响体验。

Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。

1
2
3
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

新闻WebView相关

在点击正文滑动到任何位置,退出正文页再进入时,发现可以停留在上次滑动停止的位置。

一开始我认为是缓存的主要作用,但我还是太天真。其实在正文页面onDestroyView()时,我们有调用

1
int scrollY = webView1.getScrollY();

获取WebView现在滚动的位置,记录下来。

在正文页面渲染,比如WebView实现的OnPreDrawListener#onPreDraw()方法中调用

1
webView.scrollTo(webView.getScrollX(), scrollY);

使WebView定位到退出之前的位置。

注意:

WebView的实际内容的高度通过:

1
int height = (int) (webView1.getContentHeight() * webView1.getScale());

有一些数据内容是异步回调取回的,有可能获取的scrollY大于当前WebView实际的内容高度height,那么应该考虑不定位,而是scrollY=0的位置开始展示。

计算当前屏幕展示的图片

在WebView的WebViewClient#onPageFinished()方法中加载图片,调用js方法

1
2
3
4
5
6
function updateImgCount(count) {
if (!count || count <= 0) {
return;
}
imgContentCount = count;
}

保存图片数量imgContentCount,并且调用requestImgLoading(true)方法强制加载图片。

在WebView滚动时,通过js监听WebView的滚动:

1
2
3
window.onscroll = function() {
requestImgLoading(false);
}

其实用WebView#onScrollChanged()方法监听滚动,测试快速滑动时,回调也还算及时,我觉得两种方案都可以。但由于是通过js方法计算当前屏幕内需要加载的图片,所以还是使用js监听滚动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
function requestImgLoading(force) {
if (!window.news) {
return;
}
// 图片数为0则返回
if (imgContentCount == 0) {
return;
}
if (!imgContents) {
imgContents = new Array();
}
var screenHeight = 3 * window.screen.height;
var screenHeight = screenHeight / window.devicePixelRatio;
var start = -1;
var end = -1;
// 循环遍历图片节点
for (var i = 0; i < imgContentCount; i++) {
if (!imgContents[i]) {
imgContents[i] = document.getElementById("imgContent_" + i);
}
if (!imgContents[i]) {
break;
}
// getBoundingClientRect()方法返回元素的大小及其相对于视口的位置,返回值为DOMRect;
// DOMRect除了 width 和 height 外的属性都是相对于视口的左上角位置而言的;
var r = imgContents[i].getBoundingClientRect();
// r.top < screenHeight认为在屏幕内
if (r.bottom > 0 && r.top < screenHeight) {
start = i;
if (r.bottom >= screenHeight) {
end = i;
}
break;
}
}
if (start >= 0 && end == -1) {
for (var i = start; i < imgContentCount; i++) {
if (!imgContents[i]) {
imgContents[i] = document.getElementById("imgContent_" + i);
}
if (!imgContents[i]) {
break;
}
end = i;
var r = imgContents[i].getBoundingClientRect();
if (r.bottom >= screenHeight) {
break;
}
}
}
if (start >= 0 && end == -1) {
end = start;
}
if (force || lastRequestLoadImgStart != start || lastRequestLoadImgEnd != end) {
lastRequestLoadImgStart = start;
lastRequestLoadImgEnd = end;
if (start != -1 && end != -1) {
var allLoaded = true;
for (var i = start; i <= end; i++) {
allLoaded = true == imgContents[i].loaded;
if (!allLoaded) {
break;
}
}
if (!allLoaded) {
window.news.requestImgLoading(start, end);
}
}
}
}

通过js计算出当前屏幕展示的图片节点,在调用Native方法去发送图片请求,拿到请求后的本地路径通过position设置给图片节点。

参考

  1. Android WebView:性能优化不得不说的事

  2. H5 缓存机制浅析 移动端 Web 加载性能优化

  3. Web Storage API