【問】native和js怎麼互動?
【答】
- 概念說明:HybridAPP開發中常遇到native與webview中h5(即js)需要互動的問題。如js想要調用native拍照、圖檔上傳、支付、頁面分享等;native想要發送相冊圖檔給js等。為了解決這兩者間的通信問題,就有了JsBridge(需要注意的是,jsBridge更多的是指一種形式,一種思想,而不是一種具體的技術方案),我們需要實作它。
-
jsBridge實作:
2.1 js調用native:主要有兩種方式,注入API和攔截URL Scheme
(1)注入API:該方式的主要原理是,通過Webview提供的接口,向js的context(window)中注入對象或者方法,讓js調用時,直接執行相應native代碼,達到js調用native的目的。
ios端執行個體:
Android端執行個體://IOS UIwebview代碼 JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) { // Native 邏輯 }; //js代碼 window.postBridgeMessage(message);
//Android 代碼 public class JavaScriptInterfaceDemoActivity extends Activity { private WebView Wv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Wv = (WebView)findViewById(R.id.webView); final JavaScriptInterface myJavaScriptInterface = new JavaScriptInterface(this); Wv.getSettings().setJavaScriptEnabled(true); Wv.addJavascriptInterface(myJavaScriptInterface, "nativeBridge"); // TODO 顯示 WebView } public class JavaScriptInterface { Context mContext; JavaScriptInterface(Context c) { mContext = c; } public void postMessage(String webMessage){ // Native 邏輯 } } } //注: //在 4.2 之前,Android 注入 JavaScript 對象的接口是 addJavascriptInterface,但是這個接口有漏洞,可以被不法分子利用,危害使用者的安全,是以在 4.2 中引入新的接口 @JavascriptInterface(上面代碼中使用的)來替代這個接口,解決安全問題。是以 Android 注入對對象的方式是 有相容性問題的。(4.2 之前很多方案都采用攔截 prompt 的方式來實作) //js代碼 window.nativeBridge.postMessage(message);
(2)攔截URL Scheme:該方式是攔截h5以某種方式(如iframe.src、alert等)發送的URL Scheme(比如test1://ab/url?a=2,具體攔截哪種特定請求需要兩端協商好)請求,如以自定義協定“test1://ab”開頭的都攔截下來,執行相應的native方法;其他開頭的放行。
2.2 native調用js:其實就是直接調用挂載在context環境(window)中的對象或方法,執行拼接的js字元串。
ios端執行個體:
Android端執行個體://IOS UIwebview代碼 result = [uiWebview stringByEvaluatingJavaScriptFromString:javaScriptString]; //或者IOS WKwebview代碼 [wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
2.3 互相調用時的callback實作:用一個自增的唯一 id,來辨別并存儲回調函數,并把此 id 以參數形式傳遞給 Native,而 Native 也以此 id 作為回溯的辨別。這樣,即可實作 Callback 回調邏輯。//Android代碼 webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() { @Override public void onReceiveValue(String value) { } });
- JsBridge代碼實作:
(function () {
var id = 0,
callbacks = {},
registerFuncs = {};
window.JSBridge = {
// 調用 Native
invoke: function(bridgeName, callback, data) {
// 判斷環境,擷取不同的 nativeBridge
var thisId = id ++; // 擷取唯一 id
callbacks[thisId] = callback; // 存儲 Callback
nativeBridge.postMessage({
bridgeName: bridgeName,
data: data || {},
callbackId: thisId // 傳到 Native 端
});
},
receiveMessage: function(msg) {
var bridgeName = msg.bridgeName,
data = msg.data || {},
callbackId = msg.callbackId, // Native 将 callbackId 原封不動傳回
responstId = msg.responstId;
// 具體邏輯
// bridgeName 和 callbackId 不會同時存在
if (callbackId) {
if (callbacks[callbackId]) { // 找到相應句柄
callbacks[callbackId](msg.data); // 執行調用
}
} else if (bridgeName) {
if (registerFuncs[bridgeName]) { // 通過 bridgeName 找到句柄
var ret = {},
flag = false;
registerFuncs[bridgeName].forEach(function(callback) => {
callback(data, function(r) {
flag = true;
ret = Object.assign(ret, r);
});
});
if (flag) {
nativeBridge.postMessage({ // 回調 Native
responstId: responstId,
ret: ret
});
}
}
}
},
register: function(bridgeName, callback) {
if (!registerFuncs[bridgeName]) {
registerFuncs[bridgeName] = [];
}
registerFuncs[bridgeName].push(callback); // 存儲回調
}
};
})()
-
JsBridge引入方式:
(1)由native端進行注入,直接執行JsBridge的全部代碼
(2) 由js端執行JsBridge全部代碼
參考1:移動混合開發中的JsBridge