美文网首页让前端飞
如何实现一个优雅的jsBridge

如何实现一个优雅的jsBridge

作者: 前端收藏家 | 来源:发表于2020-05-10 20:36 被阅读0次

    什么是jsbridge

    jsbridge是客户端和H5沟通的桥梁,通过它,我们可以获取部分原生能力,同时客户端也可以使用我们提供的一些方法。实现双向通信。

    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)调用

    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)方式调用。

    来看一下H5端具体实现

    假设我们需要一个getUserInfo方法,用于H5获取当前APP登录用户的信息。

    那么我们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);
        }
      });
    }
    

    我们定义一个getUserInfo方法,这个方法会去调用window.webkit.messageHandlers.getUserInfo.postMessage方法,这是客户端写入的方法,然后传入一个callback方法。客户端会通过callback方法把我们需要的信息返回给我们。

    我们可以再看下registerCallback这个方法是什么:

    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。

    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)

    收集全网优秀前端技术资讯,与你分享,共同成长。

    相关文章

      网友评论

        本文标题:如何实现一个优雅的jsBridge

        本文链接:https://www.haomeiwen.com/subject/majsnhtx.html