赞
踩
JS交互与webView的工作原理浅析
webView是什么
WebView是android中一个非常实用的组件,它和safai、chrome一样都是基于webkit网页渲染引擎,可以通过加载html数据的方式便捷地展现软件的界面。
在WebView的设计中,不是什么任务都由WebView类完成的,辅助的类完全其它辅助性的工作,WebViewy主要负责解析、渲染。
WebViewClient
就是辅助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,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。
WebChormeClient
是辅助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是怎么加载网页的
webView是通过loadUrl(String url)加载界面的(也可以将其视为一个浏览器,通过http协议向服务器请求获得一个html界面)在底层它会调用WebViewProvider这个接口然后,然后把这个url传递出去,然后怎么请求获得响应的在源码中没有找到,setwebViewClient(myWebViewClient),重写WebViewClient这个类里的onPageStarted()和onPageFinished()
而js的注入就在onPageFinished()这方法中进行进行交互的
客户端是怎么和前端页面交互的
1.Js调用Java,Java调用Js
在Android开发中,能实现Js调用Java,有4种方法:
1.javascriptInterface
2.webViewClient.shouldOverrideUrlLoading()
3.webChromeClient.onconsoleMessage()
4.webChromeClient.onJspompt()
1.1 JavascriptInterface
这是Android提供的Js与Native通信的官方解决方案。
首先Java代码要实现这么一个类,它的作用是提供给Js调用。public class JavascriptInterface {
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
}
}1
2
3
4
5
6
7
然后把这个类添加到WebView的JavascriptInterface中。webView.addJavascriptInterface(new JavascriptInterface(), “javascriptInterface”);
在Js代码中就能直接通过“javascriptInterface”直接调用了该Native的类的方法。function showToast(toast) {
javascript:javascriptInterface.showToast(toast);
}1
2
3
但是这个官方提供的解决方案在Android4.2之前存在严重的安全漏洞。在Android4.2之后,加入了@JavascriptInterface才得到解决。所以考虑到兼容低版本的系统,JavascriptInterface并不适合。
安全漏洞:
WebView注册一个名叫“jsInterface”的对象,然后在JS中可以访问到jsInterface这个对象,就可以调用这个对象的一些方法,最终可以调用到Java代码中,从而实现了JS与Java代码的交互。
我们一起来看看关于addJavascriptInterface方法在Android官网的描述:This method can be used to allow JavaScript to control the host application. This is a powerful feature, but also presents a security risk for applications targeted to API level JELLY_BEAN or below, because JavaScript could use reflection to access an injected object’s public fields. Use of this method in a WebView containing untrusted content could allow an attacker to manipulate the host application in unintended ways, executing Java code with the permissions of the host application. Use extreme care when using this method in a WebView which could contain untrusted content.
JavaScript interacts with Java object on a private, background thread of this WebView. Care is therefore required to maintain thread safety.
The Java object’s fields are not accessible.
简单地说,就是用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
2
3
4
5
6
7
8
9
10
11
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)1
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
2
3
4
5
6
7
8
9
1.5 Java调用Js
前文提到的4种通信方式都是Js通信Native的Java,而反过来,Java通信Js只有一种方式。那就是调用WebView.loadUrl()去执行一个预先定义好的Js方法。view.loadUrl("javascript:showMsg()");1
2.JsBridge#2.1 Java调用Js的functionInJs方法的流程图
##什么是JsBridge
WebViewJavascriptBridge是移动UIView和Html交互通信的桥梁,用作者的话来说就是实现java和js的互相调用的桥梁。替代了WebView的自带的JavascriptInterface的接口,使得开发者更方便的让js和native灵活交互,使我们的开发更加灵活和安全。##JSBridge的优点
Android API 4.4以前,谷歌的webview存在安全漏洞,网站可以通过js注入就可以随便拿到客户端的重要信息,甚至轻而易举的调用本地代码进行流氓行为,谷歌后来发现有此漏洞后,在API 4.4以后增加了防御措施,如果用js调用本地代码,开发者必须在代码申明JavascriptInterface, 列如在4.0之前我们要使得webView加载js只需如下代码:mWebView.addJavascriptInterface(new JsToJava(), "myjsfunction");1
4.4之后使用时需要在调用Java方法加入@JavascriptInterface注解,如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对客户端的进行窃取和攻击。 但是即使这样,我们很多时候需要在js调用本地java代码的时候,要做一些判断和限制,或者有的场景也会做些过滤或者对用户友好提示,甚至更复杂的Hybrid模式下,需要js和native之间进行交互通讯,拍照上传,因此原生的JavascriptInterface 就比较维护了,特此有了基于JavascriptInterface 封装的WebViewJavascriptBridge框架。##JsBridge的原理
基于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) {
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
下面是前端的界面的一段代码
(function(){
var bridge = app.util.webviewBridge;
console.log(app.util)
var _isBidding = false;
bridge.registerHandler('Loign', function(data){
$('#J_client-show').html(data)
})
$('#J_client-option').on('click', function(){
console.log(bridge.isBridge);
bridge.callHandler('clientOption', {
'bidding': true
})
});
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后 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();
}
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这就是一段完整的交互过程##使用JsBridge正确姿势
配置gradle
然后 在布局中加入bridgeWebView这个控件就可以了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。