如何實現一個優雅的jsBridge

什麼是jsbridge

jsbridge是客戶端和H5溝通的橋樑,經過它,咱們能夠獲取部分原生能力,同時客戶端也能夠使用咱們提供的一些方法。實現雙向通訊。javascript

jsbridge原理

客戶端能夠經過webview裏面注入一些javascript的上下文,能夠理解爲在window對象上掛載了一些方法,而後H5經過特定的對象能夠獲取到這個方法,反過來也是同樣,js掛載了一些方法到window對象上,客戶端也就能夠調用js的某些方法。前端

具體實現

方案一:注入API

IOS UIWebView
JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) {
    // Native 邏輯
};

H5使用window.postBridgeMessage(message)調用java

IOS WKWebView
@interface WKWebVIewVC ()<WKScriptMessageHandler>

@implementation WKWebVIewVC

- (void)viewDidLoad {
    [super viewDidLoad];

    WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = [[WKUserContentController alloc] init];
    WKUserContentController *userCC = configuration.userContentController;
    // 注入對象,前端調用其方法時,Native 能夠捕獲到
    [userCC addScriptMessageHandler:self name:@"nativeBridge"];

    WKWebView wkWebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

    // TODO 顯示 WebView
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"nativeBridge"]) {
        NSLog(@"前端傳遞的數據 %@: ",message.body);
        // Native 邏輯
    }
}

H5使用window.webkit.messageHandlers.nativeBridge.postMessage(message)方式調用。android

來看一下H5端具體實現ios

假設咱們須要一個getUserInfo方法,用於H5獲取當前APP登陸用戶的信息。web

那麼咱們js能夠這樣:promise

import registerCallback from '../registerCallback';

export default function getUserInfo() {
  return new Promise((resolve, reject) => {
    try {
      window.webkit.messageHandlers.getUserInfo.postMessage({
        callback: registerCallback(resolve),
      });
    } catch (e) {
      reject(e);
    }
  });
}

咱們定義一個getUserInfo方法,這個方法會去調用window.webkit.messageHandlers.getUserInfo.postMessage方法,這是客戶端寫入的方法,而後傳入一個callback方法。客戶端會經過callback方法把咱們須要的信息返回給咱們。微信

咱們能夠再看下registerCallback這個方法是什麼:dom

window.knCallbacks = {};

function makeRandomId(func) {
  return `${func.name || 'anonymous'}_${Date.now()}`;
}

export default function registerCallback(callback, keepAlive) {
  if (!callback) {
    return null;
  }

  const callbackId = makeRandomId(callback);
  window.knCallbacks[callbackId] = (data) => {
    let result;
    if (typeof data === 'object') {
      result = data;
    } else if (typeof data === 'string') {
      try {
        result = JSON.parse(data);
      } catch (e) {
        result = data;
      }
    }
    callback(result);
    if (!keepAlive) {
      delete window.knCallbacks[callbackId];
    }
  };

  return `knCallbacks.${callbackId}`;
}

在這個方法裏面,咱們會爲promise的resolve方法生成一個隨機函數名,避免出現window可能會有同名函數致使將其覆蓋的問題。
而後咱們根據生成的函數名重寫一個函數,將這個函數最後返回給外部的callback,傳遞給了客戶端。
這裏的data就是客戶端調用callback傳遞過來的數據。
咱們在這裏對數據作一些處理,而後給到callback,也就是promise的resolve。ide

Android

客戶端實現:

publicclassJavaScriptInterfaceDemoActivityextendsActivity{
  private WebView Wv;

  @Override
  publicvoidonCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    Wv = (WebView)findViewById(R.id.webView);
    final JavaScriptInterface myJavaScriptInterface = new JavaScriptInterface(this);
    Wv.getSettings().setJavaScriptEnabled(true);
    Wv.addJavascriptInterface(myJavaScriptInterface, "knJSBridge");
    // TODO 顯示 WebView
  }

  publicclassJavaScriptInterface{
    Context mContext;
    JavaScriptInterface(Context c) {
        mContext = c;
    }
    publicvoidpostMessage(String webMessage){
        // Native 邏輯
    }
  }
}

H5實現

import registerCallback from '../registerCallback';

export default function getUserInfo() {
  return new Promise((resolve, reject) => {
    try {
      window.knJSBridge.getUserInfo(JSON.stringify({
        callback: registerCallback(resolve),
      }));
    } catch (e) {
      reject(e);
    }
  });
}

這裏咱們其實能夠看到,knJSBridge實際上是咱們和客戶端約定的一個字段。而後咱們經過window.knJSBridge就能夠調用客戶端的方法了。
registerCallback和IOS的是同樣的。

根據上面咱們H5這裏最後能夠實現這樣一個結構的jsbridge

jsbridge
  - index.js
  - ios/
    - index.js
    - getUserInfo.js
  - android/
    - index.js
    - getUserInfo.js
// jsbridge/index.js
import iosBridge from './iOs';
import androidBridge from './android';

export default class Bridge {
  constructor() {
    super();
    if (isAndroid) {
      this.jsbridge = androidBridge;
    } else {
      this.jsbridge = iosBridge;
    }
  }
  getUserInfo = (...args) => this.jsbridge.getUserInfo(...args);
}

// ios/index.js
import getUserInfo from './getUserInfo';

export {
  getUserInfo
}

// ios/getUserInfo.js
import registerCallback from '../registerCallback';

export default function getUserInfo() {
  return new Promise((resolve, reject) => {
    try {
      window.webkit.messageHandlers.getUserInfo.postMessage({
        callback: registerCallback(resolve),
      });
    } catch (e) {
      reject(e);
    }
  });
}

// android/index.js
import getUserInfo from './getUserInfo';

export {
  getUserInfo
}

// android/getUserInfo.js
import registerCallback from '../registerCallback';

export default function getUserInfo() {
  return new Promise((resolve, reject) => {
    try {
      window.webkit.messageHandlers.getUserInfo.postMessage({
        callback: registerCallback(resolve),
      });
    } catch (e) {
      reject(e);
    }
  });
}

方案二:url攔截

這種方案就是H5構造一個iframe,經過給iframe設置src發起請求,而後客戶端攔截請求實現。

通常會將url設爲一個特殊的字符串,好比https://__bridge__這個樣子,而後後面跟上咱們須要傳遞的數據,包括一個callback函數名,客戶攔截這種請求,而後經過callback將數據傳遞回來。

前端收藏家(微信號: fedaily)

收集全網優秀前端技術資訊,與你分享,共同成長。

前端收藏家.jpg

相關文章
相關標籤/搜索