湖畔镇

WebView学习笔记

WebView学习笔记

WebView学习笔记,参考最全面、最易懂的Webview使用详解

基本用法

加载内容

1
webView.loadUrl("http://www.google.com/");

加载一个网址

1
WebView.loadData(String data, String mimeType, String encoding)

加载一段网络内容,参数1是内容,参数2是内容类型,参数3是编码方式

避免内存泄漏

不在xml中定义,而是需要时创建,使用Application的Context

销毁的时候,先加载空内容,然后移除View,最后销毁

1
2
3
4
webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
rootLayout.removeView(webView);
webView.destroy();
webView = null;

WebView调用destroy()时仍绑定在Activity上,所以要先移除WebView,然后在摧毁

后退

不做处理时,按Back键会使WebView结束掉,所以需要在按键中处理

1
2
3
4
5
6
7
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}

常用类

WebSettings

对WebView进行配置管理

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
WebSettings webSettings = webView.getSettings();
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持插件
webSettings.setPluginsEnabled(true);
//设置自适应屏幕,两者合用
//将图片调整到适合WebView的大小
webSettings.setUseWideViewPort(true);
//缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
//支持缩放,默认为true
webSettings.setSupportZoom(true);
//设置可以访问文件
webSettings.setAllowFileAccess(true);

//优先使用缓存:
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

//开启DOM Storage API功能
webSettings.setDomStorageEnabled(true);
//开启Database Storage API功能
webSettings.setDatabaseEnabled(true);
//开启Application Caches功能
webSettings.setAppCacheEnabled(true);
//设置Application Caches缓存目录 每个Application只需调用一次
String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath);

如果加载的HTML里有JS在执行动画等操作,会造成资源浪费,在onStop()onStart()里分别把setJavaScriptEnabled()给设置成falsetrue即可

WebViewClient

处理各种通知/事件

1
2
3
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}

载入Url之前可以在这里获得控制进行处理,不提供时,由系统选择如何处理Url(在浏览器中打开),返回false则由系统处理,返回true 由应用处理

1
2
3
4
5
6
7
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});

以上是在本WebView显示而不打开新的浏览器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 开始载入页面
public void onPageStarted(WebView view, String url, Bitmap favicon) {
}
// 结束载入页面
public void onPageFinished(WebView view, String url) {
}
// 载入资源时
public void onLoadResource(WebView view, String url) {
}

// 拦截请求 允许应用返回数据 如果返回null 则继续请求 否则用这里返回的值
// 注意并非工作在UI线程
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
......
}
@Deprecated
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return null;
}

// 处理错误
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
......
}

WebChromeClient

辅助处理JS,图标,标题等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void onProgressChanged(WebView view, int newProgress) {}
public void onReceivedTitle(WebView view, String title) {}
public void onReceivedIcon(WebView view, Bitmap icon) {}

// 处理JS的提示框
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return false;
}
// 处理JS的确认框
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return false;
}
// 处理JS的输入框
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return false;
}

// 文件选择器
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
return false;
}

JS和Android代码的交互

Android调用JS

loadUrl()
1
mWebView.loadUrl("javascript:callJS()");

调用JavaScript中的callJS()方法

JS代码调用一定要在onPageFinished()回调之后才能调用,否则不会调用

evaluateJavascript()
1
2
3
4
5
6
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {

}
});

这个方法不会刷新页面,所以比较高效,可以处理返回值,4.4以上才可以用

JS调用Android

addJavascriptInterface()对象映射

定义映射关系类,把JS需要调用的方法用@JavascriptInterface注释,代码中用addJavascriptInterface()添加映射

Android2JS.java

1
2
3
4
5
6
public class Android2JS extends Object {
@JavascriptInterface
public void Toast(String string) {
Toast.makeText(MyApplication.sApp, string, Toast.LENGTH_SHORT).show();
}
}
1
mWebView.addJavascriptInterface(new Android2JS(), "android2js");
1
2
3
4
5
<script>
function callAndroid(){
android2js.Toast("Hello World");
}
</script>

这样就可以通过JS调用原生代码

JS拿到Android对象后,可以调用对象中的所有方法,是严重的安全漏洞

对象可以通过getClass()获得当前类Class,通过Class.forName()可以加载类,如果加载了Runtime 类,便可以执行本地命令,比如访问文件

APP通过扫描等打开外部网页,可能运行到恶意攻击代码

4.2之后可以通过加@JavascriptInterface 注释

4.2 之前通过拦截prompt()修复(不很理解,感觉跟下面的差不多)

shouldOverrideUrlLoading()方法回调拦截
1
2
3
4
5
<script>
function callAndroid(){
document.location = "js://webview?arg1=1&arg2=2";
}
</script>

JS里面的函数修改location,然后通过shouldOverrideUrlLoading()拦截URL路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
// 匹配协议/域 获得参数
if ( uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("webview")) {
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
......
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}

只能通过加载执行JS代码返回值,比较麻烦

1
mWebView.loadUrl("javascript:returnResult(" + result + ")");
拦截JS对话框alert()、confirm()、prompt()消息

跟拦截URL差不多,而且使用输入框可以方便的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Uri uri = Uri.parse(message);
if (uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("toast")) {
HashMap<String, String> params = new HashMap<>();
Set<String> keys = uri.getQueryParameterNames();
for (String key : keys) {
params.put(key, uri.getQueryParameter(key));
}
String msg = params.get("message");
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
result.confirm("done");
}
}
return true;
}

漏洞

任意代码执行漏洞

addJavascriptInterface()
searchBoxJavaBridge_

3.0以下,系统默认添加映射对象searchBoxJavaBridge_,可能被利用,用removeJavascriptInterface() 删除该方法就好了

accessibilityaccessibilityTraversal

同上

密码明文存储

WebView默认开启密码保存,并明文保存到本地数据库,通过WebSettings.setSavePassword(false)关闭密码保存

域控制不严格

A应用可以通过B应用导出的Activity让B应用加载一个恶意的file协议的URL,从而可以获取B应用的内部私有文件,从而带来数据泄露威胁

使用file域加载的JS代码能够使用进行同源策略跨域访问,从而导致隐私信息泄露

通过WebSettings.setAllowFileAccess(true)禁止访问文件,缺点是这样就不能加载本地网页,可以对使用file 协议的URL禁止使用JS

通过WebSettings.setAllowFileAccessFromFileURLs(false)禁止file协议加载的JS访问本地文件

通过WebSettings.setAllowUniversalAccessFromFileURLs(false)禁止file协议加载的JS访问其他Http源等

总之对不需要file 协议的禁用file协议,需要用的禁用JS

分享