先记录一些使用技巧:
WebView有3个子类,可联合他们一起使用
WebSettings:对webView进行管理和配置
WebViewClient:处理通知和请求事件
WebChromeClient:处理js的对话框等
1、加载开始的时候显示loading,加载完成关闭loading
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
});
//支持js
webSettings.setJavaScriptEnabled(true);
//允许js弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//支持插件
webSettings.setPluginsEnabled(true);
//设置编码格式
webSettings.setDefaultTextEncodingName(“utf-8”);
//缓存相关
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//离线加载
if (NetStatusUtil.isConnected(getApplicationContext())) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}
//https请求,默认不能处理https请求,显示的是空白
webView.setWebViewClient(new WebViewClient() {
@Override public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
handler.proceed();
// handler.cancel();
// handler.handleMessage(null); } });
//onReceivedSslError为webView处理ssl证书设置
//其中handler.proceed();表示等待证书响应
//handler.cancel();表示挂起连接,为默认方式
//handler.handleMessage(null);可做其他处理
交互
一、native调js,都需要在子线程中执行。
1、Android调用js无参无返回值方法:
首先要在js代码中添加被调用的方法名比如:
function sayHello() {
alert("我是无参无返回toast")
}
然后在android代码中:格式如下
webview.loadUrl(“javascript:sayHello()”);
2、Android调用js有参无返回值其实和第一种一样,将参数写在方法里面就行了。
3、Android调用js有参有返回值(API4.4以前)
在4.4之前没有一个方法是直接调js代码然后可以返回值的。所以4.4之前的思路是通过调js方法,然后js调android方法将值传回。
比如js代码:
function sumToJava(number1, number2){
window.control.onSumResult(number1 + number2)
}
native调用js方法,子线程中执行:
webview.loadUrl(“javascript:sayHello(1,2)”);
js将最终的值传给native:
@JavascriptInterface
public void onSumResult(int result) {
Toast.makeText(getApplicationContext(), "我是android调用js方法(4.4前),结果是" + result, Toast.LENGTH_LONG).show();
}
4.4之后,有了一个方法直接调用js代码并直接拿到返回值:
js代码:
function sumToJava2(number1, number2) {
return number1 + number2;
}
native代码:
@TargetApi(Build.VERSION_CODES.KITKAT)
public void Android2JsHaveParmHaveResult2(View view) {
mWebView.evaluateJavascript("sumToJava2(3,4)", new ValueCallback<String>() {
@Override
public void onReceiveValue(String Str) {
Toast.makeText(getApplicationContext(), "我是android调用js方法(4.4后),入参是3和4,js返回结果是" + Str, Toast.LENGTH_LONG).show();
}
});}
二、js调native
1、对象映射
在native代码中定义与js建立对象映射的管理类,比如JsToAndroid.java
public class JsToAndroid extends Object {
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
System.out.println(msg);
}
}
然后js代码中调用:
function callNative(){
window.JsToAndroid .hello(“你好”);
}
这样,native的hello方法就得到执行了。
2、拦截uri
这种方式需要native和js端约定好uri的格式,然后native去拦截js端的uri并解析,然后做进一步操作
js代码:
function callAndroid(){
/*约定的url协议为:js://webview?arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&arg2=222";
}
native拦截并解析:
// 复写WebViewClient类的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 步骤2:根据协议的参数,判断是否是所需要的uri
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
Uri uri = Uri.parse(url);
// 如果url的协议 = 预先约定的 js 协议
// 就解析往下解析参数
if ( uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
// 步骤3:
// 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;//拦截
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
3、native拦截js的对话框alert()、confirm()、prompt()
js代码:
function clickprompt(){
// 调用prompt()
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
然后native去拦截:
mWebView.setWebChromeClient(new WebChromeClient() {
// 拦截输入框(原理同方式2)
// 参数message:代表promt()的内容(不是url)
// 参数result:代表输入框的返回值
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根据协议的参数,判断是否是所需要的url(原理同方式2)
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
Uri uri = Uri.parse(message);
// 如果url的协议 = 预先约定的 js 协议
// 就解析往下解析参数
if ( uri.getScheme().equals("js")) {
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//
// 执行JS所需要调用的逻辑,比如打印
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//参数result:代表消息框的返回值(输入值)
result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// 通过alert()和confirm()拦截的原理相同,此处不作过多讲述
// 拦截JS的警告框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// 拦截JS的确认框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
}
);
js调Android有一些问题已经webview本身有内存泄漏的问题。
漏洞1、对象映射的方式可能会让js攻击者获取手机的信息,因为是通过对象访问的方式去调用native代码,有可能会拿到系统级的类,然后拿到sd卡中的信息。
漏洞2、webview如果设置了保存密码的话,那么密码会被保存在应用程序的数据库中,那么就有可能被别人窃取。
漏洞3、域控制不严漏洞,就是通过uri的方式交互,可能会被利用。
漏洞4、onPageFinished 函数不一定有用。
内存泄漏:
webview默认是持有Activity的引用的,所以关闭Activity的时候要确保webview被销毁。但webview销毁有一些坑爹的东西:目前最好的方案是,当要用webview的时候,最好单独开一个进程去使用webview 并且当这个进程结束时,手动调用System.exit(0)。
这是目前对于webview 内存泄露 最好的解决方案。使用此方法 所有因为webview引发的 资源无法释放等问题 全部可以解决。