WebView是android中一个非常实用的组件,它和safai、chrome一样都是基于webkit网页渲染引擎,可以通过加载html数据的方式便捷地展现软件的界面。
在WebView的设计中,不是什么任务都由WebView类完成的,辅助的类完全其它辅助性的工作,WebViewy主要负责解析、渲染。
就是辅助WebView处理各种通知、请求事件的。
doUpdateVisitedHistory(WebView view, String url, boolean isReload) //(更新历史记录)
onFormResubmission(WebView view, Message dontResend, Message resend) //(应用程序重新请求网页数据)
onLoadResource(WebView view, String url) // 在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
onPageStarted(WebView view, String url, Bitmap favicon) //这个事件就是开始载入页面调用的,通常我们可以在这设定一个loading的页面,告诉用户程序在等待网络响应。
onPageFinished(WebView view, String url) //在页面加载结束时调用。同样道理,我们知道一个页面载入完成,于是我们可以关闭loading 条,切换程序动作。
onReceivedError(WebView view, int errorCode, String description, String failingUrl)// (报告错误信息)
onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm)//(获取返回信息授权请求)
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) //重写此方法可以让webview处理https请求。
onScaleChanged(WebView view, float oldScale, float newScale) // (WebView发生改变时调用)
onUnhandledKeyEvent(WebView view, KeyEvent event) //(Key事件未被加载时调用)
shouldOverrideKeyEvent(WebView view, KeyEvent event)//重写此方法才能够处理在浏览器中的按键事件。
shouldOverrideUrlLoading(WebView view, String url)
//在点击请求的是链接是才会调用,重写此方法返回true表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边。这个函数我们可以做很多操作,比如我们读取到某些特殊的URL,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。
是辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等
onJsConfirm()//处理确认消息框
使用确认消息框可向用户问一个“是-或-否”问题,并且用户可以选择单击“确定”按钮或者单击“取消”按钮。confirm 方法的返回值为 true 或 false。该消息框也是模式对话框:用户必须在响应该对话框(单击一个按钮)将其关闭后,才能进行下一步操作。
var truthBeTold = window.confirm(“单击“确定”继续。单击“取消”停止。”);
if (truthBeTold) {
window.alert(“欢迎访问我们的 Web 页!”);
}
else {window.alert(“再见啦!”); }
onJsAlert()//处理警告消息框
alert 方法有一个参数,即希望对用户显示的文本字符串。该字符串不是 HTML 格式。该消息框提供了一个“确定”按钮让用户关闭该消息框,并且该消息框是模式对话框,也就是说,用户必须先关闭该消息框然后才能继续进行操作。
window.alert(“欢迎!请按“确定”继续。”);
onJsPrompt()//处理提示消息框
提示消息框提供了一个文本字段,用户可以在此字段输入一个答案来响应您的提示。该消息框有一个“确定”按钮和一个“取消”按钮。如果您提供了一个辅助字符串参数,则提示消息框将在文本字段显示该辅助字符串作为默认响应。否则,默认文本为 “”。
与alert( ) 和 confirm( ) 方法类似,prompt 方法也将显示一个模式消息框。用户在继续操作之前必须先关闭该消息框
var theResponse = window.prompt(“欢迎?”,”请在此输入您的姓名。”);
webView是通过loadUrl(String url)加载界面的(也可以将其视为一个浏览器,通过http协议向服务器请求获得一个html界面)在底层它会调用WebViewProvider这个接口然后,然后把这个url传递出去,然后怎么请求获得响应的在源码中没有找到,setwebViewClient(myWebViewClient),重写WebViewClient这个类里的onPageStarted()和onPageFinished()
而js的注入就在onPageFinished()这方法中进行进行交互的
在Android开发中,能实现Js调用Java,有4种方法:
1.javascriptInterface
2.webViewClient.shouldOverrideUrlLoading()
3.webChromeClient.onconsoleMessage()
4.webChromeClient.onJspompt()
这是Android提供的Js与Native通信的官方解决方案。
首先Java代码要实现这么一个类,它的作用是提供给Js调用。
public class JavascriptInterface {
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
}
}
然后把这个类添加到WebView的JavascriptInterface中。webView.addJavascriptInterface(new JavascriptInterface(), “javascriptInterface”);
在Js代码中就能直接通过“javascriptInterface”直接调用了该Native的类的方法。
function showToast(toast) {
javascript:javascriptInterface.showToast(toast);
}
但是这个官方提供的解决方案在Android4.2之前存在严重的安全漏洞。在Android4.2之后,加入了@JavascriptInterface才得到解决。所以考虑到兼容低版本的系统,JavascriptInterface并不适合。
安全漏洞:
WebView注册一个名叫“jsInterface”的对象,然后在JS中可以访问到jsInterface这个对象,就可以调用这个对象的一些方法,最终可以调用到Java代码中,从而实现了JS与Java代码的交互。
我们一起来看看关于addJavascriptInterface方法在Android官网的描述:
简单地说,就是用addJavascriptInterface可能导致不安全,因为JS可能包含恶意代码。今天我们要说的这个漏洞就是这个,当JS包含恶意代码时,它可以干任何事情,比如访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。
解决方案
Android 4.2以上的系统
在Android 4.2以上的,google作了修正,通过在Java的远程方法上面声明一个@JavascriptInterface
1.2 WebViewClient.shouldOverrideUrlLoading()
这个方法的作用是拦截所有WebView的Url跳转。页面可以构造一个特殊格式的Url跳转,shouldOverrideUrlLoading拦截Url后判断其格式,然后Native就能执行自身的逻辑了。
public class CustomWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (isJsBridgeUrl(url)) {
// JSbridge的处理逻辑
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
1.3 WebChromeClient.onConsoleMessage()
这是Android提供给Js调试在Native代码里面打印日志信息的API,同时这也成了其中一种Js与Native代码通信的方法。
1.4 WebChromeClient.onJsPrompt()
其实除了WebChromeClient.onJsPrompt(),还有WebChromeClient.onJsAlert()和WebChromeClient.onJsConfirm()。顾名思义,这三个Js给Native代码的回调接口的作用分别是展示提示信息,展示警告信息和展示确认信息。鉴于,alert和confirm在Js的使用率很高,所以JSBridge的解决方案中都倾向于选用onJsPrompt()。
Js中调用
window.prompt(message, value)
WebChromeClient.onJsPrompt()就会受到回调。onJsPrompt()方法的message参数的值正是Js的方法window.prompt()的message的值。
public class CustomWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 处理JS 的调用逻辑
result.confirm();
return true;
}
}
1.5 Java调用Js
前文提到的4种通信方式都是Js通信Native的Java,而反过来,Java通信Js只有一种方式。那就是调用WebView.loadUrl()去执行一个预先定义好的Js方法。
view.loadUrl("javascript:showMsg()");
WebViewJavascriptBridge是移动UIView和Html交互通信的桥梁,用作者的话来说就是实现java和js的互相调用的桥梁。替代了WebView的自带的JavascriptInterface的接口,使得开发者更方便的让js和native灵活交互,使我们的开发更加灵活和安全。
Android API 4.4以前,谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息,甚至轻而易举的调用本地代码进行流氓行为,谷歌后来发现有此漏洞后,在API 4.4以后增加了防御措施,如果用js调用本地代码,开发者必须在代码申明JavascriptInterface, 列如在4.0之前我们要使得webView加载js只需如下代码:
mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");
4.4之后使用时需要在调用Java方法加入@JavascriptInterface注解,如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对客户端的进行窃取和攻击。 但是即使这样,我们很多时候需要在js调用本地java代码的时候,要做一些判断和限制,或者有的场景也会做些过滤或者对用户友好提示,甚至更复杂的Hybrid模式下,需要js和native之间进行交互通讯,拍照上传,因此原生的JavascriptInterface 就比较维护了,特此有了基于JavascriptInterface 封装的WebViewJavascriptBridge框架。
基于JsBridge的WebView加载html页面
webView.registerHandler(“clientOption”, …);这是Java层注册了一个叫”submitFromWeb”的接口方法,目的是提供给Js来调用。这个”submitFromWeb”的接口方法的回调就是BridgeHandler.handler()。webView.callHandler(“functionInJs”, …, new CallBackFunction());这是Java层主动调用Js的”functionInJs”方法。
webView = (BridgeWebView) findViewById(R.id.webView);
webView.loadUrl("http://192.168.1.2:3000/mobile");
webView.registerHandler("client", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
}
});
webView.callHandler("Loign", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
下面是前端的界面的一段代码
callHandler()方法的实现。这其中会调用到doSend()方法,这里想解释下m.setCallbackId(callbackStr)方法的作用。该方法设置的callbackId生成后不仅仅会被传到Js,而且会以key-value对的形式和responseCallback配对保存到responseCallbacks这个Map里面。它的目的,就是为了等Js把处理结果回调给Java层后,Java层能根据callbackId找到对应的responseCallback,做后续的回调处理。
private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
Message m = new Message();
if (!TextUtils.isEmpty(data)) {
m.setData(data);
}
if (responseCallback != null) {
String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
responseCallbacks.put(callbackStr, responseCallback);
m.setCallbackId(callbackStr);
}
if (!TextUtils.isEmpty(handlerName)) {
m.setHandlerName(handlerName);
}
queueMessage(m);
}
然后 message.callbackId会被取出来,实例化一个responseCallback,而它是用来Js处理完成后把结果数据回调给Java层代码的。接着会根据message.handleName(在这个分析例子中,handleName的值就是”clientOption”)在messageHandlers这个Map去获取handler,最后交给handler去处理。
webView.registerHandler("client", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
JSONTokener jsonTokener = new JSONTokener(data);
try {
JSONObject jsonObject = (JSONObject) jsonTokener.nextValue();
boolean isbidding = Boolean.parseBoolean(jsonObject.get("bidding").toString());
Toast.makeText(getApplicationContext(), "isbidding:" + isbidding, Toast.LENGTH_LONG).show();
Log.d("isbidding", "" + isbidding);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
这就是一段完整的交互过程
配置gradle
然后 在布局中加入bridgeWebView这个控件就可以了
参考(感谢各位大神的博客)
http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651231789&idx=1&sn=f11650ad0e18ddc12ece6e7559d5084c&scene=1&srcid=0513BWa7HuHjzPAeManB3w6C#rd