WebView JS交互 JSBridge 案例 原理 MD
Markdown版本笔记 | 我的GitHub首页 | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
WebView JS交互 JSBridge 案例 原理 MD
目录
简介
gradle配置
Java端:注册提供给JS端调用的接口
JS端:注册提供给Java端调用的接口
JS 端注意事项
源码解析
BridgeWebView
java 调用 js 中注册的方法
调用过程
回调过程
一个疑惑
js 调用 java 中注册的方法
一个完整的Demo
MainActivity
注册的BridgeHandler
HTML+JS源码
DSBridge 简介
特性
使用
简介
Demo
JsBridge - Android专用
WebViewJavascriptBridge - iOS专用
PS:
这个库是专供 Android 端使用的,iOS 端可以使用 WebViewJavascriptBridge,这两个库对于前端JS代码来说是兼容的!
android java and javascript bridge, inspired by wechat webview jsbridge
This project make a bridge between Java and JavaScript.
It provides safe and convenient way to call Java code from js
and call js code from java
.
JS调用Android有三种方式:
- webView.
addJavascriptInterface()
- WebViewClient.
shouldOverrideUrlLoading()
- WebChromeClient.
onJsAlert()/onJsConfirm()/onJsPrompt()
方法分别回调拦截JS对话框alert()、confirm()、prompt()消息
Android调用JS有两种方式:
- webView.
loadUrl()
; - webView.
evaluateJavascript()
gradle配置
在project的build.gradle中引入jitpack.io
:
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}
在module中添加依赖:
implementation 'com.github.lzyzsd:jsbridge:1.0.4'
Java端:注册提供给JS端调用的接口
Register a Java handler
function so that js can call:
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
//do something
function.onCallBack("submitFromWeb exe, response data from Java"); //回调
}
});
js can call this Java handler
method "submitFromWeb" through:
WebViewJavascriptBridge.callHandler(
'submitFromWeb' //方法名
, {'param': str1} //参数
, function(responseData) { //回调
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
You can set a default handler
in Java, so that js can send message to Java without assigned handlerName
:
webView.setDefaultHandler(new DefaultHandler());
window.WebViewJavascriptBridge.send( //没有方法名
data
, function(responseData) {
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
JS端:注册提供给Java端调用的接口
Register a JavaScript handler
function so that Java can call:
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
Java can call this js handler
function "functionInJs" through:
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
You can also define a default handler
use init method, so that Java can send message to js without assigned handlerName
:
for example:
bridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': 'Wee!'
};
console.log('JS responding with', data);
responseCallback(data);
});
when Java call:
webView.send("hello");
will print 'JS got a message hello' and 'JS responding with' in webview console.
JS 端注意事项
This lib will inject注入 a WebViewJavascriptBridge
Object to window object
. So in your js, before use WebViewJavascriptBridge, you must detect检测 if WebViewJavascriptBridge exist. If WebViewJavascriptBridge does not exit, you can listen to WebViewJavascriptBridgeReady
event, as the blow code shows:
if (window.WebViewJavascriptBridge) {
//do your work here
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
//do your work here
},
false
);
}
源码解析
主要有三点:
- Android调用JS是通过
loadUrl(url)
,url中可以拼接要传给JS的对象 - JS调用Android是通过
shouldOverrideUrlLoading
- JsBridge将沟通数据封装成
Message
,然后放进Queue
,再将Queue进行传输
BridgeWebView
首先自定义了一个 WebView:
public class BridgeWebView extends WebView implements WebViewJavascriptBridge
继承的是系统自带的 WebView,鉴于很多人会使用腾讯的 X5 WebView,如果想保持使用 X5 的话,我们其实重新定义一个 WebView 并将其继承的 WebView 改成 X5 的就行了。
里面定义了两个集合,一个用于在调用 registerHandler
时保存Java中注册的 方法名 和 BridgeHandler
的映射,一个用于在调用 send
时保存 Java 向 JS 发送的请求和 CallBackFunction
的映射:
Map<String, BridgeHandler> messageHandlers = new HashMap<>();//供js调用
Map<String, CallBackFunction> responseCallbacks = new HashMap<>();//调用js
然后是一些初始化操作:
getSettings().setJavaScriptEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
setWebViewClient(new BridgeWebViewClient(this));
这里定义了一个 BridgeWebViewClient
,里面并没有什么复杂的逻辑,仅仅是重写了 WebViewClient 的 shouldOverrideUrlLoading
方法和 onPageFinished
方法。我们先看一下 onPageFinished 方法,shouldOverrideUrlLoading 后面我们用到时再分析。
onPageFinished 主要用于在页面加载后加载 js 文件,以及调用一些需要在页面加载后才能调用的 js 逻辑:
public static final String toLoadJs = "WebViewJavascriptBridge.js";
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String jsContent = assetFile2Str(view.getContext(), toLoadJs);
view.loadUrl("javascript:" + jsContent); //加载 JS 代码
if (webView.getStartupMessage() != null) {
for (Message m : webView.getStartupMessage()) {
webView.dispatchMessage(m);//需要在页面加载完执行的逻辑
}
webView.setStartupMessage(null);//清空这些任务
}
}
java 调用 js 中注册的方法
调用过程
接口 WebViewJavascriptBridge
其实就是让我们调用 js 中注册的方法的:
public interface WebViewJavascriptBridge {
void send(String data);
void send(String data, CallBackFunction responseCallback);
}
鉴于 js 和 java 交互无非这两种方式:webview.addJavascriptInterface
或 WebViewClient.shouldOverrideUrlLoading
,所以其实不用看源码我们也知道,其最终肯定是通过两者中的一种来实现的。
我们大致瞅一下其调用过程:
send(String data) -> send(data, null);
send(String data, CallBackFunction responseCallback) -> doSend(null, data, responseCallback);
两者都会调用 doSend
方法,这个方法其实主要就是将消息相关信息封装到 Message
中:
private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
Message m = new Message();
if (!TextUtils.isEmpty(handlerName)) m.setHandlerName(handlerName);
if (!TextUtils.isEmpty(data)) m.setData(data);
if (responseCallback != null) {
//为此次调用定义一个唯一的回调id,例如【JAVA_CB_1_541】【JAVA_CB_2_455】
String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
responseCallbacks.put(callbackStr, responseCallback);//将回调id和对应的回调方法保存起来
m.setCallbackId(callbackStr);//将回调id赋给此消息,此id是实现交互的最关键的信息
//之后,当具有同一id的消息通过 shouldOverrideUrlLoading 回调给我们时,我们就可以找到并执行这里定义的回调方法
}
queueMessage(m);
}
这里用到的 Message 是库中定义的一个非常简单的数据结构:
public class Message{
private String callbackId; //callbackId
private String responseId; //responseId
private String responseData; //responseData
private String data; //data of message
private String handlerName; //name of handler
private final static String CALLBACK_ID_STR = "callbackId";
private final static String RESPONSE_ID_STR = "responseId";
private final static String RESPONSE_DATA_STR = "responseData";
private final static String DATA_STR = "data";
private final static String HANDLER_NAME_STR = "handlerName";
}
接下来会做一个简单的判断,以决定是立即调用还是在页面加载完成后调用:
private void queueMessage(Message m) {
if (startupMessage != null) startupMessage.add(m); //在 onPageFinished 后调用
else dispatchMessage(m); //立即调用(默认操作)
}
下面是最核心的部分之一了,简单来说就是,经过一系列操作后,最终是通过 loadUrl 来实现原生调用 js 的方法的:
void dispatchMessage(Message m) {
String messageJson = m.toJson(); //以Json格式通讯
messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");//转义特殊字符
messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");//转义特殊字符
String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);//调用的js命令
if (Thread.currentThread() == Looper.getMainLooper().getThread()) { //必须在主线程中调用
this.loadUrl(javascriptCommand); //最核心的部分,原理就是通过 loadUrl 调用 js 方法
}
}
js命令的全部内容例如下,其中包含的内容有:唯一的callbackId、js中定义的方法名handlerName
、传递过去的数据data
。
javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"callbackId\":\"JAVA_CB_1_541\",\"data\":\"【包青天20094】\",\"handlerName\":\"showInHtml\"}');
至此,整个调用过程结束了。
回调过程
经过上面的操作,我们已经调用了 js 端的代码了,下面我们探究在调用时我们注册的回调方法是怎么被调用的。
我们不需要关心 js 那端的逻辑是怎么样的,因为这完全是前端同学需要关注的事情,我们只关注 js 调用之后是怎么和我们原生交互的。
前面我们已经说过了,js和java交互只有两种方式:webview.addJavascriptInterface
或 WebViewClient.shouldOverrideUrlLoading
,这个库采用的就是重写 shouldOverrideUrlLoading
这种方式。
我们首先看一下 shouldOverrideUrlLoading
这个方法,里面主要是根据回调 url 做不同的处理:
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {//格式为【yy://return/{function}/returncontent】
webView.handlerReturnData(url);
return true;
} else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //格式为【yy://】
webView.flushMessageQueue();
return true;
} else return super.shouldOverrideUrlLoading(view, url); //不做任何处理
}
上面的案例中,我们回调的 url 为yy://__QUEUE_MESSAGE__/
,所以会调用flushMessageQueue
方法,从方法名来看,其作用应该是"刷新消息队列"的,我们先看看其基本结构:
void flushMessageQueue() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) { //主线程的调用才处理
//JS代码为【"javascript:WebViewJavascriptBridge._fetchQueue();"】
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
@Override
public void onCallBack(String data) { //处理调用后的回调
//...
});
}
}
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);//调用 js 代码
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); //处理后的 id 为【_fetchQueue】
}
调用完 loadUrl 之后集合 responseCallbacks
中就有两个元素了。既然又调用了一次 loadUrl,那我们就继续追踪shouldOverrideUrlLoading
。这一次回调的 url 为:
yy://return/_fetchQueue/[{"responseId":"JAVA_CB_1_541","responseData":"【你好,我是白乾涛,这是JS名为showInHtml的Handler返回的数据】"}]
所以就会调用handlerReturnData
这个方法,这个方法是用于将返回的数据回调给 CallBackFunction
的,逻辑非常简单:
void handlerReturnData(String url) {
String functionName = BridgeUtil.getFunctionFromReturnUrl(url); //固定为【_fetchQueue】
CallBackFunction f = responseCallbacks.get(functionName); //找到对应的 CallBackFunction
String data = BridgeUtil.getDataFromReturnUrl(url); //拿到 js 返回的数据
if (f != null) {
f.onCallBack(data); //将返回的数据回调给 CallBackFunction
responseCallbacks.remove(functionName);//移除此 CallBackFunction
return;
}
}
解析后,这里的:
- functionName 为
_fetchQueue
- CallBackFunction 为上面我们在调用
flushMessageQueue
时定义的回调方法 - 返回的 data 为
[{"responseId":"JAVA_CB_1_541","responseData":"【你好,我是白乾涛,这是JS名为showInHtml的Handler返回的数据】"}]
,可以看到这个 data 其实是一个JSONArray
所以接下来就进入其 onCallBack
中了,这个方法里面的代码稍多一点点,大致逻辑为:
List<Message> list = Message.toArrayList(data); //解析返回的数据
for (int i = 0; i < list.size(); i++) { //遍历这个集合,本例中只有id为 JAVA_CB_1_541 的元素
if (!TextUtils.isEmpty(responseId) { //如果消息的responseId不为空
function.onCallBack(responseData); //【核心】,调用相应的回调方法
responseCallbacks.remove(responseId); //执行完就移除掉
}else{ //这里面的逻辑是在js主动调用原生方法时才会执行的,我们后面再分析
}
}
完成以后,所有的回调都执行了,集合 responseCallbacks 中的所有元素也都移除了。至此整个回调过程也结束了。
一个疑惑
通过上面的流程分析,我们知道,在我们首次 loadUrl 后的 shouldOverrideUrlLoading 回调中,我们首先是进入 flushMessageQueue 方法里面又 load 了一段固定的 js 代码,然后再在 shouldOverrideUrlLoading 回调中获取 js 返回的数据以及执行回调,为什么要这么搞呢,明明一次交互就可以了的?
js 调用 java 中注册的方法
这里的大多数流程和上面的基本是一致的,下面我们只简单的过一下:
- 首先肯定还是回调
shouldOverrideUrlLoading
,此时回调的url为固定的yy://__QUEUE_MESSAGE__/
- 接着调用
flushMessageQueue
,然后又调用一次 loadUrl,消息 id 同样为_fetchQueue
- 然后会再次回调
shouldOverrideUrlLoading
,此时的url为:
yy://return/_fetchQueue/[{"handlerName":"showTips","data":{"type":"dialog","title":"我是标题","msg":""},"callbackId":"cb_3_1540110644942"}]
- 所以会进入
handlerReturnData
,接着会回调flushMessageQueue
中的onCallBack
方法,接着就是遍历集合 - 目前集合中只有一个元素,由于
responseId
为空,所以会走和上面分析的 java 调 js 不同的分支
我们着重分析此时要走的逻辑:
CallBackFunction responseFunction = null; //临时定义一个回调
final String callbackId = m.getCallbackId();
if (!TextUtils.isEmpty(callbackId)) { //有 callbackId 时的回调是继续load一个js代码
responseFunction = new CallBackFunction() {
@Override
public void onCallBack(String data) {
Message responseMsg = new Message();
responseMsg.setResponseId(callbackId); //将 callbackId作为 responseId
responseMsg.setResponseData(data);
queueMessage(responseMsg);
}
};
} else { //没有 callbackId 时的回调是一个空实现(正常是不会走到这里的)
responseFunction = data -> { };
}
BridgeHandler handler = TextUtils.isEmpty(m.getHandlerName() ? defaultHandler : messageHandlers.get(m.getHandlerName());
if (handler != null) handler.handler(m.getData(), responseFunction); //调用我们定义的 handler
里面首先是定义了一个 CallBackFunction
回调,然后会调用我们定义的 BridgeHandler
的 handler
方法,例如下面是本案例中我写的部分逻辑:
@Override
public void handler(String data, CallBackFunction function) {
//...显示Toast或弹Dialog或打印Log
function.onCallBack(...); //返回数据
}
接下来就会走我们上面定义的 responseFunction 的 onCallBack 方法,接着会走 queueMessage 方法,和上面的流程一样,我们接着会走 dispatchMessage,然后调用 loadUrl 调用 js 的方法的,此时的 js 命令为:
javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"responseData\":\"{\\\"status\\\":\\\"success\\\",\\\"time\\\":0}\",\"responseId\":\"cb_2_1540112210451\"}');
至此,便完整实现了 js 调用原生方法,并在原生响应 js 的命令后回调数据给 js。
一个完整的Demo
MainActivity
public class MainActivity extends ListActivity {
private BridgeWebView mBridgeWebView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBridgeWebView = new BridgeWebView(this);
registerHandler();
mBridgeWebView.loadUrl("file:///android_asset/jsbridge.html");
getListView().addFooterView(mBridgeWebView);
String[] array = {"调用JS中的名为showInHtml的Handler",
"调用JS中的默认Handler,没有回调",
"调用JS中的默认Handler,有回调",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
}
private void registerHandler() {
mBridgeWebView.setDefaultHandler(new DefaultBridgeHandler());//默认的Handle
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_DEVICEINFO, new DeviceInfoBridgeHandler());//获取设备信息
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_BACKNATIVE, new BackNativeBridgeHandler(this));//结束当前Activity
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_SHOWTIPS, new ShowTipsBridgeHandler(this));//显示原生toast或弹窗
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
mBridgeWebView.callHandler(JBJsIDs.BRIDGE_ID_SHOWINHTML, "【包青天20094】", new SimpleCallBackFunction());
break;
case 1:
mBridgeWebView.send("【包青天20095】");
break;
case 2:
mBridgeWebView.send("【包青天20096】", new SimpleCallBackFunction());
break;
}
}
class SimpleCallBackFunction implements CallBackFunction {
@Override
public void onCallBack(String data) {
Log.i("bqt", "【JS回调】,是否在主线程:" + (Looper.myLooper() == Looper.getMainLooper()) + "\n" + data);
//也可以用Thread.currentThread() == Looper.getMainLooper().getThread()
}
}
}
注册的BridgeHandler
public class DeviceInfoBridgeHandler implements BridgeHandler {
/**
* @param data js调用原生所传递的参数
* @param function js调用原生所传递的回调接口
*/
@Override
public void handler(String data, final CallBackFunction function) {
Log.i("bqt", "【获取设备信息,参数】" + data);
new Thread(() -> {
SystemClock.sleep(2000);//模拟耗时操作
DeviceInfo deviceInfo = new DeviceInfo("小米6", "8.0");
JBRespBean bridgeRespBean = JBRespBean.newBuilder()
.status(JBRespBean.STATUS_SUCCESS)
.time(System.currentTimeMillis())
.data(deviceInfo)
.build();
final String response = new Gson().toJson(bridgeRespBean);
Log.i("bqt", "【给JS的响应】" + response);
new Handler(Looper.getMainLooper()).post(() -> {
function.onCallBack(response);//必须在主线程中回调给JS,因为JS中加载网页也是UI操作,也必须在主线程中
});
}).start();
}
static class DeviceInfo {
String phoneName;
String sysVersion;
DeviceInfo(String phoneName, String sysVersion) {
this.phoneName = phoneName;
this.sysVersion = sysVersion;
}
}
}
HTML+JS源码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSBridge测试页面</title>
</head>
<body>
<p>
<!--xmp标签可以在页面上输出html代码-->
<xmp id="show"></xmp>
</p>
<p><input type="text" id="inputMsg" value=""/></p>
<p><input type="button" id="button1" value="获取设备信息" onclick="deviceInfo();"/></p>
<p><input type="button" id="button2" value="显示原生dialog" onclick="showTips();"/></p>
<p><input type="button" id="button3" value="结束当前Activity" onclick="backNative();"/></p>
<p><input type="button" id="button4" value="使用默认的Handle" onclick="defaultHandler();"/></p>
<p><input type="button" id="button5" value="显示html源码" onclick="showHtml();"/></p>
<p>
<xmp id="htmlContent"></xmp>
</p>
</body>
<script>
//******************************************************************************************
// 点击事件
//******************************************************************************************
function deviceInfo() {
var inputMsg = document.getElementById("inputMsg").value;
var data = {
content: inputMsg
};
window.WebViewJavascriptBridge.callHandler("deviceInfo",
data,
function(responseData) {
document.getElementById("show").innerHTML = "【返回数据】" + responseData
}
);
}
function showTips() {
var inputMsg = document.getElementById("inputMsg").value;
var data = {
type: "dialog",
title: "我是标题",
msg: inputMsg
};
window.WebViewJavascriptBridge.callHandler('showTips',
data,
function(responseData) {
document.getElementById("show").innerHTML = "【返回数据】 " + responseData
}
);
}
function backNative() {
window.WebViewJavascriptBridge.callHandler('backNative',
null,
function(responseData) {
console.log("【此时Activity关闭了,不要再操作UI了】" + responseData);
}
);
}
function defaultHandler() {
var data = {
type: "toast",
msg: "使用默认的Handle处理"
};
window.WebViewJavascriptBridge.send(data,
function(responseData) {
window.WebViewJavascriptBridge.callHandler('showTips', data, null); //显示原生toast
}
);
}
function showHtml() {
document.getElementById("htmlContent").innerHTML = document.getElementsByTagName("html")[0].innerHTML;
}
//******************************************************************************************
// JS端注册提供给Java端调用的接口
//******************************************************************************************
function bridgeLog(logContent) {
document.getElementById("show").innerHTML = logContent;
}
// 调用注册方法
function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function() {
callback(WebViewJavascriptBridge)
},
false
);
}
}
connectWebViewJavascriptBridge(function(bridge) {
//注册默认的Handler
bridge.init(function(data, responseCallback) {
document.getElementById("show").innerHTML = ("【原生传过来的数据】" + data);
if (responseCallback) { //这行代码意思是:如果有responseCallback这个方法,就执行以下逻辑
responseCallback("【你好,我是白乾涛,这是JS默认的Handler返回的数据】");
}
});
//注册供原生调用的Handler
bridge.registerHandler("showInHtml", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("【原生传过来的数据】" + data);
if (responseCallback) {
responseCallback("【你好,我是白乾涛,这是JS名为showInHtml的Handler返回的数据】");
}
});
})
</script>
</html>
DSBridge 简介
public class MainActivity extends ListActivity {
private BridgeWebView mBridgeWebView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBridgeWebView = new BridgeWebView(this);
registerHandler();
mBridgeWebView.loadUrl("file:///android_asset/jsbridge.html");
getListView().addFooterView(mBridgeWebView);
String[] array = {"调用JS中的名为showInHtml的Handler",
"调用JS中的默认Handler,没有回调",
"调用JS中的默认Handler,有回调",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
}
private void registerHandler() {
mBridgeWebView.setDefaultHandler(new DefaultBridgeHandler());//默认的Handle
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_DEVICEINFO, new DeviceInfoBridgeHandler());//获取设备信息
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_BACKNATIVE, new BackNativeBridgeHandler(this));//结束当前Activity
mBridgeWebView.registerHandler(JBNativeIDs.BRIDGE_ID_SHOWTIPS, new ShowTipsBridgeHandler(this));//显示原生toast或弹窗
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
mBridgeWebView.callHandler(JBJsIDs.BRIDGE_ID_SHOWINHTML, "【包青天20094】", new SimpleCallBackFunction());
break;
case 1:
mBridgeWebView.send("【包青天20095】");
break;
case 2:
mBridgeWebView.send("【包青天20096】", new SimpleCallBackFunction());
break;
}
}
class SimpleCallBackFunction implements CallBackFunction {
@Override
public void onCallBack(String data) {
Log.i("bqt", "【JS回调】,是否在主线程:" + (Looper.myLooper() == Looper.getMainLooper()) + "\n" + data);
//也可以用Thread.currentThread() == Looper.getMainLooper().getThread()
}
}
}
public class DeviceInfoBridgeHandler implements BridgeHandler {
/**
* @param data js调用原生所传递的参数
* @param function js调用原生所传递的回调接口
*/
@Override
public void handler(String data, final CallBackFunction function) {
Log.i("bqt", "【获取设备信息,参数】" + data);
new Thread(() -> {
SystemClock.sleep(2000);//模拟耗时操作
DeviceInfo deviceInfo = new DeviceInfo("小米6", "8.0");
JBRespBean bridgeRespBean = JBRespBean.newBuilder()
.status(JBRespBean.STATUS_SUCCESS)
.time(System.currentTimeMillis())
.data(deviceInfo)
.build();
final String response = new Gson().toJson(bridgeRespBean);
Log.i("bqt", "【给JS的响应】" + response);
new Handler(Looper.getMainLooper()).post(() -> {
function.onCallBack(response);//必须在主线程中回调给JS,因为JS中加载网页也是UI操作,也必须在主线程中
});
}).start();
}
static class DeviceInfo {
String phoneName;
String sysVersion;
DeviceInfo(String phoneName, String sysVersion) {
this.phoneName = phoneName;
this.sysVersion = sysVersion;
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSBridge测试页面</title>
</head>
<body>
<p>
<!--xmp标签可以在页面上输出html代码-->
<xmp id="show"></xmp>
</p>
<p><input type="text" id="inputMsg" value=""/></p>
<p><input type="button" id="button1" value="获取设备信息" onclick="deviceInfo();"/></p>
<p><input type="button" id="button2" value="显示原生dialog" onclick="showTips();"/></p>
<p><input type="button" id="button3" value="结束当前Activity" onclick="backNative();"/></p>
<p><input type="button" id="button4" value="使用默认的Handle" onclick="defaultHandler();"/></p>
<p><input type="button" id="button5" value="显示html源码" onclick="showHtml();"/></p>
<p>
<xmp id="htmlContent"></xmp>
</p>
</body>
<script>
//******************************************************************************************
// 点击事件
//******************************************************************************************
function deviceInfo() {
var inputMsg = document.getElementById("inputMsg").value;
var data = {
content: inputMsg
};
window.WebViewJavascriptBridge.callHandler("deviceInfo",
data,
function(responseData) {
document.getElementById("show").innerHTML = "【返回数据】" + responseData
}
);
}
function showTips() {
var inputMsg = document.getElementById("inputMsg").value;
var data = {
type: "dialog",
title: "我是标题",
msg: inputMsg
};
window.WebViewJavascriptBridge.callHandler('showTips',
data,
function(responseData) {
document.getElementById("show").innerHTML = "【返回数据】 " + responseData
}
);
}
function backNative() {
window.WebViewJavascriptBridge.callHandler('backNative',
null,
function(responseData) {
console.log("【此时Activity关闭了,不要再操作UI了】" + responseData);
}
);
}
function defaultHandler() {
var data = {
type: "toast",
msg: "使用默认的Handle处理"
};
window.WebViewJavascriptBridge.send(data,
function(responseData) {
window.WebViewJavascriptBridge.callHandler('showTips', data, null); //显示原生toast
}
);
}
function showHtml() {
document.getElementById("htmlContent").innerHTML = document.getElementsByTagName("html")[0].innerHTML;
}
//******************************************************************************************
// JS端注册提供给Java端调用的接口
//******************************************************************************************
function bridgeLog(logContent) {
document.getElementById("show").innerHTML = logContent;
}
// 调用注册方法
function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function() {
callback(WebViewJavascriptBridge)
},
false
);
}
}
connectWebViewJavascriptBridge(function(bridge) {
//注册默认的Handler
bridge.init(function(data, responseCallback) {
document.getElementById("show").innerHTML = ("【原生传过来的数据】" + data);
if (responseCallback) { //这行代码意思是:如果有responseCallback这个方法,就执行以下逻辑
responseCallback("【你好,我是白乾涛,这是JS默认的Handler返回的数据】");
}
});
//注册供原生调用的Handler
bridge.registerHandler("showInHtml", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("【原生传过来的数据】" + data);
if (responseCallback) {
responseCallback("【你好,我是白乾涛,这是JS名为showInHtml的Handler返回的数据】");
}
});
})
</script>
</html>
三端易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数.
特性
跨平台
:同时支持ios/android
- 支持
同步、异步调
用 双向调用
:js可以调用native, native可以调用js。- 三端易用:三端指ios 、android和前端
- 支持进度回调:一次调用,多次返回
- Android支持腾讯X5内核
- 支持以类的方式集中统一管理API
- 支持API命名空间
- 支持调试模式
- 支持API存在性检测
- 支持Javascript关闭页面事件回调
- 支持Javascript 模态/非模态对话框
DSBridge 官方是同时支持 ios/android 的,WebViewJavascriptBridge 官方说明是支持 ios/osx 的,但 并不支持android , 当然,由于 WebViewJavascriptBridge 的人气 实在太高,也有一些人在 android 上实现了兼容的版本,如 gzsll/WebViewJavascriptBridge,但是总的来说,并非一家之作,这可能会给日后维护带来问题。
到目前为止,据作者所知,跨平台的 js bridge 中,DSBridge 是唯一一个支持同步调用的!
使用
1、添加依赖
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
implementation 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'
//或
implementation 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT' //支持X5内核
2、原生定义供JS调用的API
public class JsApi {
@JavascriptInterface
public String testSyn(Object msg) {
return msg + "[syn call]"; //同步API
}
@JavascriptInterface
public void testAsyn(Object msg, CompletionHandler<String> handler) {
handler.complete(msg + " [ asyn call]"); //异步API
}
}
3、JS定义供原生调用的API
//cdn方式引入初始化代码(中国地区慢,建议下载到本地工程)
//<script src="https://unpkg.com/dsbridge@3.1.3/dist/dsbridge.js" />
//npm方式安装初始化代码
//npm install dsbridge@3.1.3
var dsBridge=require("dsbridge")
//注册 javascript API
dsBridge.register('addValue',function(l,r){
return l+r;
})
4、JS调用原生API
var str=dsBridge.call("testSyn","testSyn"); //同步调用
dsBridge.call("testAsyn","testAsyn", function (v) {
alert(v); //异步调用
})
5、原生调用JS API
DWebView dwebView= (DWebView) findViewById(R.id.dwebview);
dwebView.addJavascriptObject(new JsApi(), null); //Object object, String namespace
dwebView.callHandler("addValue", new Object[]{3, 4}, new OnReturnValue<Integer>() {
@Override
public void onValue(Integer retValue) {
Log.d("jsbridge", "call succeed, return value is " + retValue);
}
});
2018-5-8
WebView JS交互 JSBridge 案例 原理 MD的更多相关文章
- WebView JS交互 addJavascriptInterface MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- AutoRegister ASM AOP 字节码 案例 原理 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- android webview js交互 第一节 (java和js交互)
转载请注明出处 挺帅的移动开发专栏 http://blog.csdn.net/wangtingshuai/article/details/8631835 在androi ...
- 安卓高级 WebView的使用到 js交互
我们先来学习 怎么使用再到用js和安卓源生方法交互 WebView简单使用 此部分转载并做了补充 原博客 原因:比较简单不是很想在写,我只要写js交互部分 WebView可以使得网页轻松的内嵌到app ...
- webView和js交互
与 js 交互 OC 调用 JS // 执行 js - (void)webViewDidFinishLoad:(UIWebView *)webView { NSString *title = [web ...
- Android WebView加载本地html并实现Java与JS交互
最近做的一个项目中,用到自定义地图,将自定义地图转换成html页面,现在需要做的是如何将本地的html加载到android中,并可以实现交互. 相关讲解: 其实webview加载资源的速度并不慢,但是 ...
- iOS webView与js交互在文本空格上输入文字
项目要求:webview加载html网址,内容为填空题型文本,需要在横线上添加答案,并点击提交按钮后再将答案进行回显 正常加载的效果图片: 这个是用js交互后的效果图: 点击空格,输入想输入的答案,如 ...
- Webview Android与js交互
Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true Android(Java)与JavaScript(HTML) ...
- js与webview 常用交互代码
常用js交互 css常用参数::: 是否允许用户选择元素的内容,选择值包括: auto:用户可以选择元素内的内容 none:用户不能选择任何内容 text:用户只能选择元素内的 ...
随机推荐
- Android activity之间数据传递和共享的方式之Application
1.基于消息的通信机制 Intent ---bundle ,extra 数据类型有限,比如遇到不可序列化的数据Bitmap,InputStream,或者LinkedList链表等等数据类型就不太好用 ...
- NOI.AC NOIP模拟赛 第二场 补记
NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...
- Xcode 模拟器复制解决方案
网址:http://blog.csdn.net/zhangao0086/article/details/38491271
- spring boot 集成 shiro
写在前面 1.Shiro是Apache下的一个开源项目,我们称之为Apache Shiro.它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与spring Security ...
- tesseract-ocr识别中文扫描图片实例讲解
当我浏览http://code.google.com/p/tesseract-ocr并下载了几个文件下来之后顿时感到一头雾水,不知该如何下手.网上看到有人在linux操作系统下的实现, 如: 利用开源 ...
- ARM JTAG 调试原理
ARM JTAG 调试原理 JTAG的接口是一种特殊的4/5个接脚接口连到芯片上 ,所以在电路版上的很多芯片可以将他们的JTAG接脚 通过Daisy Chain的方式连在一起,并且Probe只需连接到 ...
- 手把手教你使用C#操作SQLite数据库,新建数据库,创建表,插入,查询,删除,运算符,like
目录: 一.新建项目,添加引用 二.创建数据库 三.创建表 四.插入数据 五.查询数据 六.删除数据 七.运算符 八.like语句 我的环境配置:windows 64,VS,SQLite(点击下 ...
- [Deepin 15] 安装 Java 8、Git、Firefox、SafeEyes(定时提醒休息工具)
一.安装 JDK 8 1.到官网,用 迅雷下载 安装包 (jdk-8u131-linux-x64.tar.gz) 2.解压放到目录 /opt/software/jdk 3.配置环境变量 sudo vi ...
- WinForm多语言版本实战项目演练
一.课程介绍 关于如何实现“WinForm多语言版本”网上有很多实现技术方案,可以说是“琳琅满目”,"包罗万象".俗话说的好:一千个读者就有一千个哈姆雷特!如果您工作中恰好也遇到这 ...
- 利用HTTP Cache来优化网站
原文地址: http://www.cnblogs.com/cocowool/archive/2011/08/22/2149929.html 对于网站来说,速度是第一位的.用户总是讨厌等待,面对加载的V ...