日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

jsbridge实现及原理_JSBridge 实现原理解析

發(fā)布時間:2023/12/4 javascript 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jsbridge实现及原理_JSBridge 实现原理解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

JSBridge 項目以 js 與 android 通信為例,講解 JSBridge 實現(xiàn)原理,下面提到的方法在 iOS(UIWebview 或 WKWebview)均有對應(yīng)方法。

1. native to js

兩種 native 調(diào)用 js 方法,注意被調(diào)用的方法需要在 JS 全局上下文上

loadUrl

evaluateJavascript

1.1 loadUrlmWebview.loadUrl("javascript: func()");

1.2 evaluateJavascriptmWebview.evaluateJavascript("javascript: func()", new ValueCallback() {

@Override

public void onReceiveValue(String value) {

return;

}

});

上述兩種 native 調(diào)用 js 的方式對比如下表:方式優(yōu)點缺點loadUrl兼容性好1. 會刷新頁面 2. 無法獲取 js 方法執(zhí)行結(jié)果

evaluateJavascript1. 性能好 2. 可獲取 js 執(zhí)行后的返回值僅在安卓 4.4 以上可用

2. js to native

三種 js 調(diào)用 native 方法

攔截 Url Schema(假請求)

攔截 prompt alert confirm

注入 JS 上下文

2.1 攔截 Url Schema

即由 h5 發(fā)出一條新的跳轉(zhuǎn)請求,native 通過攔截 URL 獲取 h5 傳過來的數(shù)據(jù)。

跳轉(zhuǎn)的目的地是一個非法不存在的 URL 地址,例如:"jsbridge://methodName?{"data": arg, "cbName": cbName}"

具體示例如下:"jsbridge://openScan?{"data": {"scanType": "qrCode"}, "cbName": "handleScanResult"}"

h5 和 native 約定一個通信協(xié)議,例如 jsbridge, 同時約定調(diào)用 native 的方法名 methodName 作為域名,以及后面帶上調(diào)用該方法的參數(shù) arg,和接收該方法執(zhí)行結(jié)果的 js 方法名 cbName。

具體可以在 js 端封裝相關(guān)方法,供業(yè)務(wù)端統(tǒng)一調(diào)用,代碼如下:window.callbackId = 0;

function callNative(methodName, arg, cb) {

const args = {

data: arg === undefined ? null : JSON.stringify(arg),

};

if (typeof cb === 'function') {

const cbName = 'CALLBACK' + window.callbackId++;

window[cbName] = cb;

args['cbName'] = cbName;

}

const url = 'jsbridge://' + methodName + '?' + JSON.stringify(args);

...

}

以上封裝中較為巧妙的是將用于接收 native 執(zhí)行結(jié)果的 js 回調(diào)方法 cb 掛載到 window 上,并為防止命名沖突,通過全局的 callbackId 來區(qū)分,然后將該回調(diào)函數(shù)在 window 上的名字放在參數(shù)中傳給 native 端。native 拿到 cbName 后,執(zhí)行完方法后,將執(zhí)行結(jié)果通過 native 調(diào)用 js 的方式(上面提到的兩種方法),調(diào)用 cb 傳給 h5 端(例如將掃描結(jié)果傳給 h5)。

至于如何在 h5 中發(fā)起請求,可以設(shè)置 window.location.href 或者創(chuàng)建一個新的 iframe 進(jìn)行跳轉(zhuǎn)。function callNative(methodName, arg, cb) {

...

const url = 'jsbridge://' + method + '?' + JSON.stringify(args);

// 通過 location.href 跳轉(zhuǎn)

window.location.href = url;

// 通過創(chuàng)建新的 iframe 跳轉(zhuǎn)

const iframe = document.createElement('iframe');

iframe.src = url;

iframe.style.width = 0;

iframe.style.height = 0;

document.body.appendChild(iframe);

window.setTimeout(function() {

document.body.removeChild(iframe);

}, 800);

}

native 會攔截 h5 發(fā)出的請求,當(dāng)檢測到協(xié)議為 jsbridge 而非普通的 http/https/file 等協(xié)議時,會攔截該請求,解析出 URL 中的 methodName、arg、 cbName,執(zhí)行該方法并調(diào)用 js 回調(diào)函數(shù)。

下面以安卓為例,通過覆蓋 WebViewClient 類的 shouldOverrideUrlLoading 方法進(jìn)行攔截,android 端具體封裝會在下面單獨的板塊進(jìn)行說明。import android.util.Log;

import android.webkit.WebView;

import android.webkit.WebViewClient;

public class JSBridgeViewClient extends WebViewClient {

@Override

public boolean shouldOverrideUrlLoading(WebView view, String url) {

JSBridge.call(view, url);

return true;

}

}

攔截 URL Schema 的問題連續(xù)發(fā)送時消息丟失

如下代碼:window.location.href = "jsbridge://callNativeNslog?{"data": "111", "cbName": ""}";

window.location.href = "jsbridge://callNativeNslog?{"data": "222", "cbName": ""}";

js 此時的訴求是在同一個運(yùn)行邏輯內(nèi),快速的連續(xù)發(fā)送出 2 個通信請求,用客戶端本身 IDE 的 log,按順序打印 111,222,那么實際結(jié)果是 222 的通信消息根本收不到,直接會被系統(tǒng)拋棄丟掉。

原因:因為 h5 的請求歸根結(jié)底是一種模擬跳轉(zhuǎn),跳轉(zhuǎn)這件事情上 webview 會有限制,當(dāng) h5 連續(xù)發(fā)送多條跳轉(zhuǎn)的時候,webview 會直接過濾掉后發(fā)的跳轉(zhuǎn)請求,因此第二個消息根本收不到,想要收到怎么辦?js 里將第二條消息延時一下。//發(fā)第一條消息

location.href = "jsbridge://callNativeNslog?{"data": "111", "cbName": ""}";

//延時發(fā)送第二條消息

setTimeout(500,function(){

location.href = "jsbridge://callNativeNslog?{"data": "222", "cbName": ""}";

});

但這并不能保證此時是否有其他地方通過這種方式進(jìn)行請求,為系統(tǒng)解決此問題,js 端可以封裝一層隊列,所有 js 代碼調(diào)用消息都先進(jìn)入隊列并不立刻發(fā)送,然后 h5 會周期性比如 500 毫秒,清空一次隊列,保證在很快的時間內(nèi)絕對不會連續(xù)發(fā) 2 次請求通信。URL 長度限制

如果需要傳輸?shù)臄?shù)據(jù)較長,例如方法參數(shù)很多時,由于 URL 長度限制,仍以丟失部分?jǐn)?shù)據(jù)。

2.2 攔截 prompt alert confirm

即由 h5 發(fā)起 alert confirm prompt,native 通過攔截 prompt 等獲取 h5 傳過來的數(shù)據(jù)。

因為 alert confirm 比較常用,所以一般通過 prompt 進(jìn)行通信。

約定的傳輸數(shù)據(jù)的組合方式以及 js 端封裝方法的可以類似上面的 攔截 URL Schema 提到的方式。function callNative(methodName, arg, cb) {

...

const url = 'jsbridge://' + method + '?' + JSON.stringify(args);

prompt(url);

}

native 會攔截 h5 發(fā)出的 prompt,當(dāng)檢測到協(xié)議為 jsbridge 而非普通的 http/https/file 等協(xié)議時,會攔截該請求,解析出 URL 中的 methodName、arg、 cbName,執(zhí)行該方法并調(diào)用 js 回調(diào)函數(shù)。

下面以安卓為例,通過覆蓋 WebChromeClient 類的 onJsPrompt 方法進(jìn)行攔截,android 端具體封裝會在下面單獨的板塊進(jìn)行說明。import android.webkit.JsPromptResult;

import android.webkit.WebChromeClient;

import android.webkit.WebView;

public class JSBridgeChromeClient extends WebChromeClient {

@Override

public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

result.confirm(JSBridge.call(view, message));

return true;

}

}

這種方式?jīng)]有太大缺點,也不存在連續(xù)發(fā)送時信息丟失。不過 iOS 的 UIWebView 不支持該方式(WKWebView 支持)。

2.3 注入 JS 上下文

即由 native 將實例對象通過 webview 提供的方法注入到 js 全局上下文,js 可以通過調(diào)用 native 的實例方法來進(jìn)行通信。

具體有安卓 webview 的 addJavascriptInterface,iOS UIWebview 的 JSContext,iOS WKWebview 的 scriptMessageHandler。

下面以安卓 webview 的 addJavascriptInterface 為例進(jìn)行講解。

首先 native 端注入實例對象到 js 全局上下文,代碼大致如下,具體封裝會在下面的單獨板塊進(jìn)行講解:public class MainActivity extends AppCompatActivity {

private WebView mWebView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mWebView = (WebView) findViewById(R.id.mWebView);

...

// 將 NativeMethods 類下面的提供給 js 的方法轉(zhuǎn)換成 hashMap

JSBridge.register("JSBridge", NativeMethods.class);

// 將 JSBridge 的實例對象注入到 js 全局上下文中,名字為 _jsbridge,該實例對象下有 call 方法

mWebView.addJavascriptInterface(new JSBridge(mWebView), "_jsbridge");

}

}

public class NativeMethods {

// 用來供 js 調(diào)用的方法

public static void methodName(WebView view, JSONObject arg, CallBack callBack) {

}

}

public class JSBridge {

private WebView mWebView;

public JSBridge(WebView webView) {

this.mWebView = webView;

}

private static Map> exposeMethods = new HashMap<>();

// 靜態(tài)方法,用于將傳入的第二個參數(shù)的類下面用于提供給 javacript 的接口轉(zhuǎn)成 Map,名字為第一個參數(shù)

public static void register(String exposeName, Class> classz) {

...

}

// 實例方法,用于提供給 js 統(tǒng)一調(diào)用的方法

@JavascriptInterface

public String call(String methodName, String args) {

...

}

}

然后 h5 端可以在 js 調(diào)用 window._jsbridge 實例下面的 call 方法,傳入的數(shù)據(jù)組合方式可以類似上面兩種方式。具體代碼如下:window.callbackId = 0;

function callNative(method, arg, cb) {

let args = {

data: arg === undefined ? null : JSON.stringify(arg)

};

if (typeof cb === 'function') {

const cbName = 'CALLBACK' + window.callbackId++;

window[cbName] = cb;

args['cbName'] = cbName;

}

if (window._jsbridge) {

window._jsbridge.call(method, JSON.stringify(args));

}

}

注入 JS 上下文的問題

以安卓 webview 的 addJavascriptInterface 為例,在安卓 4.2 版本之前,js 可以利用 java 的反射 Reflection API,取得構(gòu)造該實例對象的類的內(nèi)部信息,并能直接操作該對象的內(nèi)部屬性及方法,這種方式會造成安全隱患,例如如果加載了外部網(wǎng)頁,該網(wǎng)頁的惡意 js 腳本可以獲取手機(jī)的存儲卡上的信息。

在安卓 4.2 版本后,可以通過在提供給 js 調(diào)用的 java 方法前加裝飾器 @JavascriptInterface,來表明僅該方法可以被 js 調(diào)用。

上述三種 js 調(diào)用 native 的方式對比如下表:方式優(yōu)點缺點攔截 Url Schema(假請求)無安全漏洞1. 連續(xù)發(fā)送時消息丟失 2. Url 長度限制,傳輸數(shù)據(jù)大小受限

攔截 prompt alert confirm無安全漏洞iOS 的 UIWebView 不支持該方式

注入 JS 上下文官方提供,方便簡捷在安卓 4.2 以下有安全漏洞

3. 安卓端 java 的封裝

native 與 h5 交互部分的代碼在上面已經(jīng)提到了,這里主要是講述 native 端如何封裝暴露給 h5 的方法。

首先單獨封裝一個類 NativeMethods,將供 h5 調(diào)用的方法以公有且靜態(tài)方法的形式寫入。如下:public class NativeMethods {

public static void showToast(WebView view, JSONObject arg, CallBack callBack) {

...

}

}

接下來考慮如何在 NativeMethods 和 h5 之前建立一個橋梁,JSBridge 類因運(yùn)而生。

JSBridge 類下主要有兩個靜態(tài)方法 register 和 call。其中 register 方法是用來將供 h5 調(diào)用的方法轉(zhuǎn)化成 Map 形式,以便查詢。而 call 方法主要是用接收 h5 端的調(diào)用,分解 h5 端傳來的參數(shù),查找并調(diào)用 Map 中的對應(yīng)的 Native 方法。

JSBridge 類的靜態(tài)方法 register

首先在 JSBridge 類下聲明一個靜態(tài)屬性 exposeMethods,數(shù)據(jù)類型為 HashMap 。然后聲明靜態(tài)方法 register,參數(shù)有字符串 exposeName 和類 classz,將 exposeName 和 classz 的所有靜態(tài)方法 組合成一個 map,例如:{

jsbridge: {

showToast: ...

openScan: ...

}

}

代碼如下:private static Map> exposeMethods = new HashMap<>();

public static void register(String exposeName, Class> classz) {

if (!exposeMethods.containsKey(exposeName)) {

exposeMethods.put(exposeName, getAllMethod(classz));

}

}

由上可知我們需要定義一個 getAllMethod 方法用來將類里的方法轉(zhuǎn)化為 HashMap 數(shù)據(jù)格式。在該方法里同樣聲明一個 HashMap,并將滿足條件的方法轉(zhuǎn)化成 Map,key 為方法名,value 為方法。

其中條件為 公有 public 靜態(tài) static 方法且第一個參數(shù)為 Webview 類的實例,第二個參數(shù)為 JSONObject 類的實例,第三個參數(shù)為 CallBack 類的實例。 (CallBack 是自定義的類,后面會講到)

代碼如下:private static HashMap getAllMethod(Class injectedCls) {

HashMap methodHashMap = new HashMap<>();

Method[] methods = injectedCls.getDeclaredMethods();

for (Method method: methods) {

if(method.getModifiers()!=(Modifier.PUBLIC | Modifier.STATIC) || method.getName()==null) {

continue;

}

Class[] parameters = method.getParameterTypes();

if (parameters!=null && parameters.length==3) {

if (parameters[0] == WebView.class && parameters[1] == JSONObject.class && parameters[2] == CallBack.class) {

methodHashMap.put(method.getName(), method);

}

}

}

return methodHashMap;

}

JSBridge 類的靜態(tài)方法 call

由于注入 JS 上下文和兩外兩種,h5 端傳過來的參數(shù)形式不同,所以處理參數(shù)的方式略有不同。

下面以攔截 Prompt 的方式為例進(jìn)行講解,在該方式中 call 接收的第一個參數(shù)為 webView,第二個參數(shù)是 arg,即 h5 端傳過來的參數(shù)。還記得攔截 Prompt 方式時 native 端和 h5 端約定的傳輸數(shù)據(jù)的方式么?"jsbridge://openScan?{"data": {"scanType": "qrCode"}, "cbName":"handleScanResult"}"

call 方法首先會判斷字符串是否以 jsbridge 開頭(native 端和 h5 端之間約定的傳輸數(shù)據(jù)的協(xié)議名),然后該字符串轉(zhuǎn)成 Uri 格式,然后獲取其中的 host 名,即方法名,獲取 query,即方法參數(shù)和 js 回調(diào)函數(shù)名組合的對象。最后查找 exposeMethods 的映射,找到對應(yīng)的方法并執(zhí)行該方法。public static String call(WebView webView, String urlString) {

if (!urlString.equals("") && urlString!=null && urlString.startsWith("jsbridge")) {

Uri uri = Uri.parse(urlString);

String methodName = uri.getHost();

try {

JSONObject args = new JSONObject(uri.getQuery());

JSONObject arg = new JSONObject(args.getString("data"));

String cbName = args.getString("cbName");

if (exposeMethods.containsKey("JSBridge")) {

HashMap methodHashMap = exposeMethods.get("JSBridge");

if (methodHashMap!=null && methodHashMap.size()!=0 && methodHashMap.containsKey(methodName)) {

Method method = methodHashMap.get(methodName);

if (method!=null) {

method.invoke(null, webView, arg, new CallBack(webView, cbName));

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

return null;

}

CallBack 類

js 調(diào)用 native 方法成功后,native 有必要返回給 js 一些反饋,例如接口是否調(diào)用成功,或者 native 執(zhí)行后的得到的數(shù)據(jù)(例如掃碼)。所以 native 需要執(zhí)行 js 回調(diào)函數(shù)。

執(zhí)行 js 回調(diào)函數(shù)方式本質(zhì)是 native 調(diào)用 h5 的 js 方法,方式仍舊是上面提到的兩種方式 evaluateJavascript 和 loadUrl。簡單來說可以直接將 js 的回調(diào)函數(shù)名傳給對應(yīng)的 native 方法,native 執(zhí)行通過 evaluateJavascript 調(diào)用。

但為了統(tǒng)一封裝調(diào)用回調(diào)的方式,我們可以定義一個 CallBack 類,在其中定義一個名為 apply 的靜態(tài)方法,該方法直接調(diào)用 js 回調(diào)。

注意:native 執(zhí)行 js 方法需要在主線程上。public class CallBack {

private String cbName;

private WebView mWebView;

public CallBack(WebView webView, String cbName) {

this.cbName = cbName;

this.mWebView = webView;

}

public void apply(JSONObject jsonObject) {

if (mWebView!=null) {

mWebView.post(() -> {

mWebView.evaluateJavascript("javascript:" + cbName + "(" + jsonObject.toString() + ")", new ValueCallback() {

@Override

public void onReceiveValue(String value) {

return;

}

});

});

}

}

}

到此為止 JSBridge 的大致原理都講完了。但功能仍可再加完善,例如:

native 執(zhí)行 js 方法時,可接受 js 方法中異步返回的數(shù)據(jù),比如在 js 方法中請求某個接口在返回數(shù)據(jù)。直接調(diào)用 webview 提供的 evaluateJavascript,在第二個參數(shù)的類 ValueCallback 的實例方法 onReceiveValue 并不能接收到 js 異步返回的數(shù)據(jù)。

后面有空 native 調(diào)用 js 方式會繼續(xù)完善的,最后以一句古語互勉:

路漫漫其修遠(yuǎn)兮 吾將上下而求索

總結(jié)

以上是生活随笔為你收集整理的jsbridge实现及原理_JSBridge 实现原理解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。