1.如何实现和设计一套JSBridge?
前端JS调用native的方式有很多种,或者说android有很多种方式可以拦截或者获取到JS的行为。如下使用onConsoleMessage的方式,来设计一个简单的JSBridge:
- 前端代码片段
(function () {
var callbackArr = {
};
window.XJSBridge = {
//JS调动native
callNative: function (func, param, callback) {
//生成调用的序列号Id
var seqId = "__bridge_id_" + Math.random() + "" + new Date().getTime();
//保存回调
callbackArr[seqId] = callback;
//生成约定的JSBridge消息
var msgObj = {
func: func,
param: param,
msgType: "callNative",
seqId: seqId,
};
//打印约定的日志
console.log("____xbridge____:" + JSON.stringify(msgObj));
},
//native调用JS的方法
nativeInvokeJS: function (res) {
//解析native的消息
res = JSON.parse(res);
//判断是否是callback消息
if (res && res.msgType === "jsCallback") {
//获取之前保持的回调方法
var func = callbackArr[res.seqId];
//delete callbackArr[res.seqId];
//调用JS的回调
if ("function" === typeof func) {
setTimeout(function () {
//调用js的回调
func(res.param);
}, 1);
}
}
return true;
},
};
document.dispatchEvent(new Event("xbridge inject success..."), null);
console.log("xbridge inject success...");
})();
如上代码就是在window对象上挂载一个XJSBridge对象,可以通过callNative函数来调用android的native方法,其原理就是JS调用console.log打印一行约定的日志(Bridge协议),android端通过WebChromeClient的onConsoleMessage方法,获取到打印的日志,然后针对协议解析出对应的协议。
- 其中WebChromeClient的代码如下
public class XWebChromeClient extends WebChromeClient {
private WebViewPage mWebViewPage;
public XWebChromeClient setWebViewPage(WebViewPage mWebViewPage) {
this.mWebViewPage = mWebViewPage;
return this;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (ConsoleMessage.MessageLevel.LOG.equals(consoleMessage.messageLevel())) {
XLog.d("onConsoleMessage:" + consoleMessage.message()
+ ",line = " + consoleMessage.lineNumber()
+ ",sourceId = " + consoleMessage.sourceId()
+ ",messageLevel = " + consoleMessage.messageLevel());
//log级别的日志
if (mWebViewPage.handleMsgFromJS(consoleMessage.message())) {
return true;
}
} else if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) {
//ERROR级别的日志
XLog.e("onConsoleMessage:" + consoleMessage.message()
+ ",line = " + consoleMessage.lineNumber()
+ ",sourceId = " + consoleMessage.sourceId()
+ ",messageLevel = " + consoleMessage.messageLevel());
} else {
XLog.w("onConsoleMessage:" + consoleMessage.message()
+ ",line = " + consoleMessage.lineNumber()
+ ",sourceId = " + consoleMessage.sourceId()
+ ",messageLevel = " + consoleMessage.messageLevel());
}
return super.onConsoleMessage(consoleMessage);
}
}
这里的mWebViewPage会调用到XJSBridgeImpl的handleLogFromJS方法,在这里针对JSBridge进行解析,比如:
public class XJSBridgeImpl {
private WebView mWebView;
public XJSBridgeImpl(WebView view) {
mWebView = view;
}
private static String XJSBRIDGE_HEADER = "____xbridge____:";
public boolean handleLogFromJS(String log) {
if (log != null && log.startsWith(XJSBRIDGE_HEADER)) {
String msg = log.substring(XJSBRIDGE_HEADER.length());
XLog.d("msg:" + msg);
return handleMsgFromJS(msg);
}
return false;
}
private boolean dispatch(String func, final String seqId, String param) {
if (func.equals("nativeMethod")) {
//模拟js bridge的native实现
Toast.makeText(mWebView.getContext(), "Hello XJSBridge!I am in native.", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
XLog.d("hello, I am native method...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
JSONObject mockRes = new JSONObject();
try {
mockRes.put("bridge", XJSBridgeImpl.class.getName());
mockRes.put("data", "I am from native");
} catch (JSONException e) {
e.printStackTrace();
}
invokeJS(seqId, mockRes);
}
}).start();
}
return false;
}
private void invokeJS(String seqId, JSONObject res) {
final JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("seqId", seqId);
jsonObject.put("msgType", "jsCallback");
jsonObject.put("param", res);
} catch (JSONException e) {
XLog.e(e);
}
mWebView.post(new Runnable() {
@Override
public void run() {
//需要在主线程调用
mWebView.evaluateJavascript(String.format("javascript: window.XJSBridge.nativeInvokeJS('%s')", jsonObject.toString()), new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
XLog.d("onReceiveValue:s = " + s);
}
});
}
});
}
private boolean handleMsgFromJS(String message) {
try {
JSONObject jsonObject = new JSONObject(message);
String func = jsonObject.getString("func");
String seqId = jsonObject.getString("seqId");
String param = jsonObject.getString("param");
dispatch(func, seqId, param);
return true;
} catch (JSONException e) {
XLog.e(e);
}
return false;
}
public void changeH5Background() {
mWebView.evaluateJavascript(String.format("javascript: changeColor('%s')", "#f00"), new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
XLog.d("changeH5Background:onReceiveValue:s = " + s);
}
});
}
public void addObjectForJS(){
mWebView.addJavascriptInterface(new NativeLog(),"nativeLog");
}
}
- native如何回调给前端?
android通过mWebView.evaluateJavascript或者mWebView.loadUrl的方式调用JS,在android 4.4以上推荐使用evaluateJavascript,loadUrl会导致页面刷新,键盘收回等问题。
- native如何向前端注入对象?
通过addJavascriptInterface添加对象,比如:
mWebView.addJavascriptInterface(new NativeLog(),"nativeLog");
其中NativeLog的实现如下:
public class NativeLog {
@JavascriptInterface
public void print(String log) {
XLog.d("nativeLog:" + log);
}
}
这里面的方法print,需要添加注解@JavascriptInterface,JS才能访问到。
添加之后,前端可以通过如下方式调用:
document.getElementById('a2').addEventListener('click', function () {
console.log('clicked....');
if (nativeLog) {
nativeLog.print('content from js');
}
});
2.如果实现url的拦截和重定向?
WebView的WebViewClient中,可以通过重写shouldOverrideUrlLoading实现,如果需要拦截,可以return true;比如:
public class XWebViewClient extends WebViewClient {
public XWebViewClient() {
super();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
XLog.d("shouldOverrideUrlLoading2...");
if (shouldOverrideUrlLoadingInternal(view, request.getUrl().toString())) {
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
XLog.d("shouldOverrideUrlLoading1...");
if (shouldOverrideUrlLoadingInternal(view, url)) {
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
private boolean shouldOverrideUrlLoadingInternal(WebView view, String url) {
if (url != null && url.startsWith("https://www.baidu.com")) {
view.loadUrl("file:///android_asset/xbridge_demo.html");
return true;
}
if (url != null && url.startsWith("xscheme://native_page")) {
//根据前端的href进行scheme拦截,跳转到native的页面
view.getContext().startActivity(new Intent(view.getContext(), TestActivity.class));
return true;
}
if(url != null && url.startsWith("")){
}</