Android WebView经常会有一些奇奇怪怪的问题,为了避免日后重复采坑,统一在这里总结下:
setWebContentsDebuggingEnabled
setWebContentsDebuggingEnabled()用来配置WebView是否支持远程调试,当被设置为true时,既可以用PC端的Chrome来调试手机端的WebView。具体操作如下:
WebView配置如下
1
2
3
4//KITKAT版本一下不支持这个API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}手机通过USB连接PC,在Chrome下输入”chrome://inspect”即可进入调试页,同时会显示出所有可调式的WebView页,审查元素、network等调试操作和PC上相同。
setWebChromeClient和setWebViewClient
- WebViewClient主要帮助WebView处理各种通知、请求事件的,比如:
onLoadResource,onPageStart,onPageFinish,onReceiveError,onReceivedHttpAuthRequest 等。 - WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等,比如:
onCloseWindow(关闭WebView),onCreateWindow(),onJsAlert (WebView上alert无效,需要定制WebChromeClient处理弹出),onJsPrompt,onJsConfirm,onProgressChanged,onReceivedIcon,onReceivedTitle 等等。
addjavascriptinterface
addjavascriptinterface可以把一个java对象导出,通过js来调用java对象。如果这个对象涉及到敏感操作,有可能产生安全问题。
使用实例如下:
定义一个AndroidToast,实现弹出一个toast功能,并将其导出1
2
3
4
5
6public class AndroidToast {
public void show(String str) {
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
}
WebView进行设置,开启JavaScipt,注册JavascriptInterface的方法1
2
3
4
5
6
7
8
9private void initView() {
webView = (WebView) findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDefaultTextEncodingName("UTF-8");
webView.addJavascriptInterface(new AndroidToast(), "AndroidToast");
webView.loadUrl("file:///android_asset/index.html");
}
在javascript中调用方法,通过window属性可以找到映射的对象AndroidToast,直接调用它的show方法即可。1
2
3function toastClick(){
window.AndroidToast.show('from js');
}
WebResourceResponse
拦截web返回请求,配合其他漏洞造成本地文件泄露,参考https://blog.oversecured.com/Android-Exploring-vulnerabilities-in-WebResourceResponse/
setGeolocationEnabled
setGeolocationEnabled用来配置是否允许H5定位,默认为true。通过一定的配置,可以使WebView中的H5定位时,用户不会受到任何定位提示。
参照官方开发文档,实现h5定位需要满足三个条件:
- APP本身必须具有定位权限
- APP必须实现onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback) 这个回调函数,当js调用h5定位的API时,这个函数会被调用
- 发起定位请求的站点必须为可信origin,如https,http的将不允许定位
下边这行代码用来处理定位请求,第一个参数是发起定位请求的origin,第二个 boolean 类型的参数表示是否授予网页定位权限;而第三个 boolean 类型的参数则表示是否保留这个权限状态。1
callback.invoke(origin, true, true);
具体代码如下: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前端实现
<body>
<p id="demo">点击按钮获取您当前坐标(可能需要比较长的时间获取):</p>
<button onclick="getLocation()">点我</button>
<script>
var x=document.getElementById("demo");
function getLocation()
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(showPosition);
}
else
{
x.innerHTML="该浏览器不支持获取地理位置。";
}
}
function showPosition(position)
{
x.innerHTML="纬度: " + position.coords.latitude +
"<br>经度: " + position.coords.longitude;
}
</script>
</body>
1 | Manifest文件: |
1 | WebView contentWv = (WebView) findViewById(R.id.wv_content); |
一般比较合适的做法是,在该回调函数中设置一个对话框,告知用户是否授权定位操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25wv.setWebChromeClient(new WebChromeClient(){
public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage("Allow to access location information?");
DialogInterface.OnClickListener dialogButtonOnClickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int clickedButton) {
if (DialogInterface.BUTTON_POSITIVE == clickedButton) {
callback.invoke(origin, true, true);//允许
} else if (DialogInterface.BUTTON_NEGATIVE == clickedButton) {
callback.invoke(origin, false, false);//拒绝
}
}
};
builder.setPositiveButton("Allow", dialogButtonOnClickListener);
builder.setNegativeButton("Deny", dialogButtonOnClickListener);
builder.show();
}
}
);