Getting to know JSBridge for the first time: from principle to use (a tool for intercommunication between android, ios, and js)


foreword

In the era when mobile terminals are popular, the technology selection is basically hybrid development (Hybrid). Hybrid development is a development model that refers to the use of multiple development models to develop Apps, which usually involves two types of technologies: native Native, Web H5.

  1. Native technology mainly refers to iOS (Object C) and Android (Java). The efficiency of native development is low. After the development is completed, the entire App needs to be repackaged, and user-dependent updates are released. The performance is higher and the coverage rate is higher;
  2. Web H5 is mainly composed of HTML, CSS, and JavaScript. Web can better implement release updates, and cross-platform is also better, but its performance is low and its features are limited.
    Hybrid development can draw on the advantages of both.
    Hybrid development rendering can be divided into the following:
  3. Web-rendered hybrid App (Codova, NativeScript);
  4. Hybrid apps with native rendering (ReactNative, Weex);
  5. Small programs
    Among them, native and Web communication are inseparable from JSBridge. The small program here is special, and the UI rendering and JS execution environment are isolated, based on the first two methods.

1. What does JSBridge do?

In Hybrid mode, H5 will often need to use Native functions, such as: open QR code scanning, call native pages, obtain user information, etc., and Native also needs to send pushes to the Web side, update status, etc., and JavaScript runs on A separate JSContext (Webview container, JSCore, etc.) is isolated from the native operating environment, so there needs to be a mechanism to realize two-way communication between the Native side and the Web side. This is JSBridge: using the JavaScript engine or Webview container as the medium, through the agreement agreement Communication, a mechanism to realize two-way communication between Native and Web.
Through JSBridge, the Web side can call the Java interface of the Native side, and the Native side can also call the JavaScript interface of the Web side through JSBridge to realize mutual two-way calls.
insert image description here

Two, WebView

First of all, let’s understand webView. webView is an environment for running JavaScript provided by the mobile terminal. It is a control for the system to render Web pages. It can interact with JavaScript on the page to achieve hybrid development. Android and iOS are somewhat different: Android’s webView
uses The lower and higher versions use different webkit kernels, and Chrome is used directly after 4.4.
UIWebView in iOS has existed since IOS2, but its performance is poor and its feature support is poor. WKWebView is an upgraded version after iOS8, with stronger performance and better feature support.
In addition to loading the specified url, the WebView control can also perform powerful processing on URL requests, JavaScript dialog boxes, progress loading, and page interaction. Later, it will be mentioned that intercepting requests and executing JS scripts all depend on this.

Three, JSB implementation principle

The Web side and Native can be compared to the Client/Server mode. When the Web side calls the native interface, it is similar to the Client sending a request to the Server side. JSB plays a role similar to the HTTP protocol here. There are two main points to implement JSBridge:

  1. Encapsulate the Native-side native interface into a JavaScript interface;
  2. Encapsulate the web-side JavaScript interface into a native interface.

3.1 Native->Web

First of all, the Native side calls the Web side. This is relatively simple. As an interpreted language, JavaScript can execute a piece of JS code through the interpreter anytime and anywhere, so the spliced ​​JavaScript code string can be passed to JS for analysis. The browser can execute it, and the JS parser is webView here.
Before Android 4.4, it can only be implemented with loadUrl, and the callback cannot be executed:

String jsCode = String.format("window.showWebDialog('%s')", text);
webView.loadUrl("javascript: " + jsCode);

After Android 4.4, evaluateJavascript is provided to execute JS code, and the return value can be obtained to execute the callback:

String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
    
    
  @Override
  public void onReceiveValue(String value) {
    
    

  }
});

iOS's UIWebView uses stringByEvaluatingJavaScriptFromString :

NSString *jsStr = @"执行的JS代码";
[webView stringByEvaluatingJavaScriptFromString:jsStr];

WKWebView for iOS uses evaluateJavaScript :

[webView evaluateJavaScript:@"执行的JS代码" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
    
    

}];

3.2 Web->Native

There are two main ways for the Web to call the Native side

3.2.1 URL Schema for Intercepting Webview Requests

URL Schema is a URL-like request format, the format is as follows:

<protocol>://<host>/<path>?<query>#fragment

We can customize the URL Schema of JSBridge communication, for example: jsbridge://showToast?text=hello
After Native loads WebView, all requests sent by the Web will pass through the WebView component, so Native can rewrite the methods in WebView to never intercept Web initiation request, judge the format of the request:

  • If it matches our custom URL Schema, parse the URL, get relevant operations, and then call the native Native method;
  • If it does not conform to the custom URL Schema, forward it directly and request the real service.
    insert image description here
    There are several ways for the Web to send URL requests:
  • aLabel;
  • location.href
  • use iframe.src;
  • Send ajaxrequest.

For these methods, atags require user operations, location.href may cause page jumps to lose calls, and Android does not have a corresponding interception method for sending ajax requests, so using iframe.src is a frequently used solution:

  • Android provides shouldOverrideUrlLoading method interception;
  • UIWebView uses shouldStarLoadWithRequest, WKWebView uses decidePolicyForNavigationAction.

This method has existed since the early days, and the compatibility is very good, but because it is based on the URL, the length is limited and not very intuitive, the data format is limited, and it is time-consuming to create a request.

3.2.2 Inject JS API into Webview

This method will use the interface provided by webView, and the App will inject Native-related interfaces into the JS Context (window) object. Generally speaking, the method name in this object is the same as the Native-related method name, and the Web side can directly Use this exposed global JS object under the global window , and then call the method on the native side.
This process will be simpler and more intuitive, but there are compatibility issues, and this method will be used in most cases:
Android (4.2+) provides addJavascriptInterface injection:

// 注入全局JS对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
    
    
  private Context ctx;
  NativeBridge(Context ctx) {
    
    
    this.ctx = ctx;
  }

  // 增加JS调用接口
  @JavascriptInterface
  public void showNativeDialog(String text) {
    
    
    new AlertDialog.Builder(ctx).setMessage(text).create().show();
  }
}

Call this method directly on the Web side:

window.NativeBridge.showNativeDialog('hello');

iOS's UIWebView provides JavaScriptCore ;
iOS's WKWebView provides WKScriptMessageHandler ;

3.3 Call with callback

The two methods of two-way communication between Native and Web have been mentioned above, but at one end it is still a one-way communication process, such as from the perspective of Web: Web calls the method of Native, Native is directly related to the operation but cannot transfer the result Return to the Web, but in actual use, it is often necessary to return the result of the operation, that is, the JS callback.
So operating on the opposite end and returning the result, with input and output is a complete call, so how to achieve it?
In fact, based on the previous one-way communication, it can be realized. When calling at one end, add a callbackId to the parameter to mark the corresponding callback. After receiving the call request, the other end will perform the actual operation. Once called, the result and
callbackId will be sent back, and this end will match the corresponding callback according to the callbackId, and then pass the result into execution.
insert image description here
It can be seen that it is actually realized through two one-way communications.
Take Android as an example to implement JSB calls with callbacks on the Web side:

// Web端代码:
<body>
  <div>
    <button id="showBtn">获取Native输入,以Web弹窗展现</button>
  </div>
</body>
<script>
  let id = 1;
  // 根据id保存callback
  const callbackMap = {
    
    };
  // 使用JSSDK封装调用与Native通信的事件,避免过多的污染全局环境
  window.JSSDK = {
    
    
    // 获取Native端输入框value,带有回调
    getNativeEditTextValue(callback) {
    
    
      const callbackId = id++;
      callbackMap[callbackId] = callback;
      // 调用JSB方法,并将callbackId传入
      window.NativeBridge.getNativeEditTextValue(callbackId);
    },
    // 接收Native端传来的callbackId
    receiveMessage(callbackId, value) {
    
    
      if (callbackMap[callbackId]) {
    
    
        // 根据ID匹配callback,并执行
        callbackMap[callbackId](value);
      }
    }
  };

    const showBtn = document.querySelector('#showBtn');
  // 绑定按钮事件
  showBtn.addEventListener('click', e => {
    
    
    // 通过JSSDK调用,将回调函数传入
    window.JSSDK.getNativeEditTextValue(value => window.alert('Natvie输入值:' + value));
  });
</script>
// Android端代码
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
    
    
  private Context ctx;
  NativeBridge(Context ctx) {
    
    
    this.ctx = ctx;
  }

  // 获取Native端输入值
  @JavascriptInterface
  public void getNativeEditTextValue(int callbackId) {
    
    
    MainActivity mainActivity = (MainActivity)ctx;
    // 获取Native端输入框的value
    String value = mainActivity.editText.getText().toString();
    // 需要注入在Web执行的JS代码
    String jsCode = String.format("window.JSSDK.receiveMessage(%s, '%s')", callbackId, value);
    // 在UI线程中执行
    mainActivity.runOnUiThread(new Runnable() {
    
    
      @Override
      public void run() {
    
    
        mainActivity.webView.evaluateJavascript(jsCode, null);
      }
    });
  }
}

The above code simply implements a demo. Clicking the button on the Web side will get the value of the input box on the Native side, and display the value in a pop-up box on the Web side. In this way, Web->Native JSB calls with callbacks are realized. Similarly Native->Web is also the same logic, the difference is that the callback is saved on the Native side, so I won’t discuss it in detail here.

4. Open source JSBridge

It can be seen that it is quite troublesome to implement a complete JSBridge. We also need to consider the compatibility of low-end models and the issue of synchronous and asynchronous calls. Fortunately, there is already an open source JSBridge for us to use directly:

  • DSBridge, mainly in the form of API injection, DSBridge for Android, DSBridge for IOS;
  • JsBridge, mainly by intercepting URL Schema, JsBridge.

Take DSBridge-Android as an example:

// Web端代码
<body>
  <div>
    <button id="showBtn">获取Native输入,以Web弹窗展现</button>
  </div>
</body>
// 引入SDK
<script src="https://unpkg.com/[email protected]/dist/dsbridge.js"></script>
<script>
  const showBtn = document.querySelector('#showBtn');
  showBtn.addEventListener('click', e => {
    
    
    // 注意,这里代码不同:SDK在全局注册了dsBridge,通过call调用Native方法
    dsBridge.call('getNativeEditTextValue', '', value => {
    
    
      window.alert('Native输入值' + value);
    })
  });
</script>
// Android代码
// 使用dwebView替换原生webView
dwebView.addJavascriptObject(new JsApi(), null);

class JSApi {
    
    
  private Context ctx;
  public JSApi (Context ctx) {
    
    
    this.ctx = ctx;
  }

  @JavascriptInterface
  public void getNativeEditTextValue(Object msg, CompletionHandler<String> handler) {
    
    
    String value = ((MainActivity)ctx).editText.getText().toString();
    // 通过handler将value传给Web端,实现回调的JSB调用
    handler.completed(value);
  }
}

As you can see, the code has been simplified a lot, and you can just look at the documentation for other uses.


V. Summary

At this point, everyone should have a deeper understanding of the principle and use of JSBridge. Here is a summary of the article: Hybrid
development is currently the mainstream technology option for mobile development. Among them, the two-way communication between Native and Web is inseparable. JSBridge ;
Among them, the Native calls the Web side directly to execute the JS code directly in the JS Context. There are two methods for the Web side to call the Native side, one is based on the interception operation of the URL Schema , and the other is to the JS Context (window) Injecting Api, among which injecting Api is currently the best choice. The complete call is a two-way communication, which requires a callback function. In terms of technical implementation, two one-way communication is used.
Secondly, rather than making wheels, it is recommended to use the currently open source JSBridge: DSBridge, jsBridge.

Additional:

Record the application examples in the project for future reference: (PS: The project example is based on nuxt2+vue2 as an example)
1. Bridging method, dsbrisge.js code:

/*
  *  桥接的方法
  *  dsBridge.call('约定的方法名称') // 调用原始的方法
  *  dsBridge.register('约定好的名称',function(){})  //注册方法给原始使用
  *  时间2019/1/9
  */
  var bridge = {
    
    
    default: this,
    call: function (b, a, c) {
    
    
      var e = "";
      "function" == typeof a && (c = a, a = {
    
    });
      a = {
    
    
        data: void 0 === a ? null : a
      };
      if ("function" == typeof c) {
    
    
        var g = "dscb" + window.dscb++;
        window[g] = c;
        a._dscbstub = g
      }
      a = JSON.stringify(a);
      if (window._dsbridge) e = _dsbridge.call(b, a);
      else if (window._dswk || -1 != navigator.userAgent.indexOf("_dsbridge")) e = prompt("_dsbridge=" + b, a);
      return JSON.parse(e || "{}").data
    },
    register: function (b, a, c) {
    
    
      c = c ? window._dsaf : window._dsf;
      window._dsInit || (window._dsInit = !0, setTimeout(function () {
    
    
          bridge.call("_dsb.dsinit")
        },
        0));
      "object" == typeof a ? c._obs[b] = a : c[b] = a
    },
    registerAsyn: function (b, a) {
    
    
      this.register(b, a, !0)
    },
    hasNativeMethod: function (b, a) {
    
    
      return this.call("_dsb.hasNativeMethod", {
    
    
        name: b,
        type: a || "all"
      })
    },
    disableJavascriptDialogBlock: function (b) {
    
    
      this.call("_dsb.disableJavascriptDialogBlock", {
    
    
        disable: !1 !== b
      })
    }
  };
  if (!window._dsf) {
    
    
    var b = {
    
    
        _dsf: {
    
    
          _obs: {
    
    }
        },
        _dsaf: {
    
    
          _obs: {
    
    }
        },
        dscb: 0,
        dsBridge: bridge,
        close: function () {
    
    
          bridge.call("_dsb.closePage")
        },
        _handleMessageFromNative: function (a) {
    
    
          var e = JSON.parse(a.data),
            b = {
    
    
              id: a.callbackId,
              complete: !0
            },
            c = this._dsf[a.method],
            d = this._dsaf[a.method],
            h = function (a, c) {
    
    
              b.data = a.apply(c, e);
              bridge.call("_dsb.returnValue", b)
            },
            k = function (a, c) {
    
    
              e.push(function (a, c) {
    
    
                b.data = a;
                b.complete = !1 !== c;
                bridge.call("_dsb.returnValue", b)
              });
              a.apply(c, e)
            };
          if (c) h(c, this._dsf);
          else if (d) k(d, this._dsaf);
          else if (c = a.method.split("."), !(2 > c.length)) {
    
    
            a = c.pop();
            var c = c.join("."),
              d = this._dsf._obs,
              d = d[c] || {
    
    },
              f = d[a];
            f && "function" == typeof f ? h(f, d) : (d = this._dsaf._obs, d = d[c] || {
    
    }, (f = d[a]) && "function" == typeof f && k(f, d))
          }
        }
      },
      a;
    for (a in b) window[a] = b[a];
    bridge.register("_hasJavascriptMethod", function (a, b) {
    
    
      b = a.split(".");
      if (2 > b.length) return !(!_dsf[b] && !_dsaf[b]);
      a = b.pop();
      b = b.join(".");
      return (b = _dsf._obs[b] || _dsaf._obs[b]) && !!b[a]
    })
  }

  export default dsBridge

insert image description here
2. In the mainUse.js file in the plugins folder, global registration references:

import dsbridge from "../utils/dsbridge";
Vue.prototype.dsBridge = dsbridge;

insert image description here
3. How to specifically call in the page:
There are two bridge methods, ①dsBridge.call('agreed method name') //call the original method; ②dsBridge.register('agreed name', function(){ }) // Register method for primitive use.

this.dsBridge.call("orderPayAndBindSuccess", this.uid); // 支付成功通知app,第一个参数和app原生的交互协议名称;第二个参数JS回调callbackId

Reference blog :
Explain JSBridge in simple terms: from principle to use https://zhuanlan.zhihu.com/p/438763800
dsbridge (android, ios, js three-terminal intercommunication tool) https://www.jianshu.com/p/356492d94089

Guess you like

Origin blog.csdn.net/qq_26780317/article/details/126269925
Recommended