开启成长之旅!这是我参加「日新计划 12 月更文应战」的第3天,点击查看活动详情

前沿

写这篇文章的首要意图是对 App 的 JSBridge 做一个全面的介绍,同时根据不同的运用场景总结出一份 App 完成 JSBridge 的最佳计划。关于没有触摸过 App 的同学能够对 JSBridge 有个大致的概念,关于做过 App 的 JSBridge 开发的同学也能有更体系的知道,也是自己关于相关知识点的概括总结。

一、概念

什么是 JSBridge ?

JSBridge 的全称:JavaScript Bridge,中文名 JS桥JS桥接器

JSBridge 是一种用于在 Android 和 iOS 运用与 H5 之间进行通讯的技能。它答应运用开发者在原生代码中调用 JavaScript 函数,以及 在JavaScript 中调用原生代码函数。其一般用于移动运用开发中,能够运用 JSBridge 技能在原生运用中嵌入网页,并在网页与原生运用之间进行交互。

二、原理

JSBridge 经过在 WebView 中注册 JavaScript 函数来完成通讯。WebView 是一种在运用中嵌入网页的组件,能够在运用中显现网页内容。JSBridge 经过在 WebView 中注册 JavaScript 函数,并在原生代码中调用这些函数来完成通讯

例如,下面是一个运用 JSBridge 完成通讯的示例代码:

/* Android 端完成 */
// 在WebView中注册JavaScript函数
webView.loadUrl("javascript:function myFunction() { /* JavaScript code here */ }");
// 在原生代码中调用JavaScript函数
webView.loadUrl("javascript:myFunction()");
/* iOS 端完成 */
// 在WebView中注册JavaScript函数
[self.webView stringByEvaluatingJavaScriptFromString:@"function myFunction() { /* JavaScript code here */ }"];
// 在原生代码中调用JavaScript函数
[self.webView stringByEvaluatingJavaScriptFromString:@"myFunction()"];

上面的代码经过在 WebView 中注册 JavaScript 函数 myFunction,并在原生代码中调用这个函数来完成通讯。

在实践开发中,咱们一般是创立一个 JSBridge 目标,然后经过 WebView 的 addJavascriptInterface 办法进行注册。

// WebView 的 addJavascriptInterface 办法源码
public void addJavascriptInterface(Object object, String name) {
  checkThread();
  if (object == null) {
    throw new NullPointerException("Cannot add a null object");
  }
  if (name == null || name.length() == 0) {
    throw new IllegalArgumentException("Invalid name");
  }
  mJavascriptInterfaces.put(name, object);
}

该办法首要查看当时线程是否是 UI 线程,以保证添加桥接目标的操作是在 UI 线程中进行的。接着,该办法会查看桥接目标和称号的有效性,保证它们都不为空。最终,该办法会把桥接目标与称号关联起来,并存储到 WebView 的 mJavascriptInterfaces 目标中。

当网页加载完成后,WebView 会把桥接目标的办法注入到网页中,使得网页能够调用这些办法。当网页中的 JavaScript 代码调用桥接目标的办法时,WebView 会把该办法调用映射到原生代码中,从而完成网页与原生运用之间的交互。

addJavascriptInterface 办法的首要作用是把桥接目标的办法注入到网页中,使得网页能够调用这些办法。它的详细完成办法可能会因渠道而异,但是它的基本原理是共同的。

三、原生完成

以 H5 获取 App 的版别号为例。Android相关源码

要完成一个获取 App 版别号的 JSBridge,需要在 H5 中编写 JavaScript 代码,并在 Android 原生代码中完成对应的原生办法。

首要,需要在 H5 中编写 JavaScript 代码,用于调用 Android 的原生办法。例如,能够在 H5 中界说一个函数,用于调用 Android 的原生办法:

// assets/index.html
function getAppVersion() {
    // 经过JSBridge调用Android的原生办法
    JSBridge.getAppVersion(function(version) {
        // 在这儿处理获取到的Android版别号
    });
}

然后,需要在 Android 的原生代码中完成对应的原生办法。例如,能够完成一个名为 getAppVersion 的办法,用于在 H5 中调用:

// com.fitem.webviewdemo.AppJSBridge
@JavascriptInterface
public String getAppVersion() {
    // 获取App版别号
    String version = BuildConfig.VERSION_NAME;
    // 将App版别号回来给H5
    return version;
}

最终经过 Webview 注入界说的 JavascriptInterface 办法的目标,在 H5 生成 window.jsBridge 目标进行调用。

// com.fitem.webviewdemo.MainActivity.kt
webView.addJavascriptInterface(jsBridge, "jsBridge")

iOS 的完成和 Android 相似:

- (void)getIOSVersion:(WVJBResponseCallback)callback {
    // 获取App版别号
    let version = Bundle.main.object(forInfoDictionaryKey: 
    "CFBundleShortVersionString") as! String
    // 将App版别号回来给H5
    callback(version);
}
// 在网页加载完成后设置JSBridge
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // 设置JSBridge
    [WebViewJavascriptBridge enableLogging];
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [self.bridge setWebViewDelegate:self];
}

四、跨渠道(Flutter

1. JSBridge 完成

Flutter 完成 JSBridge 功能的插件有很多,但基本上大多数都是基于原生的 JSBridge 才能完成。这儿首要介绍官方的 webview_flutter 插件。

webview_flutter 插件完成 App 与 H5 之前的通讯分为:App 发送音讯到 H5H5 发送音讯到 APP 两部分。

H5 发送音讯到 APP。首要在 Flutter 运用中添加 WebView 组件,并设置 JavascriptChannel

      WebView(
        initialUrl: 'https://www.example.com',
        javascriptMode: JavascriptMode.unrestricted,
        javascriptChannels: {
          // 设置JavascriptChannel
          JavascriptChannel(
            name: 'JSBridge',
            onMessageReceived: (JavascriptMessage message) {
              // 在这儿处理来自H5的音讯
            },
          ),
        },
      ),

在H5中,能够经过 JSBridge 目标来调用原生办法:

// 经过JSBridge调用原生办法
window.jsBridge.postMessage('Hello, world!');

App 发送音讯到 H5。 在 Flutter 中,经过 WebViewController 的 runJavascrip 调用 H5 中 window 目标的办法

controller.runJavascript("receiveMessage(${json.encode(res)})")

在 H5 中,能够经过 onmessage 事件来接收来自原生的音讯:

  // 接收来自原生的音讯
  window.receiveMessage = function receiveMessage(message) {
    console.log(message);
  };

2. 局限性

webview_flutter 最大的局限在于 App 端与 H5 端之间的通讯只支撑单向通讯,无法经过一次调用直接获取另一端的回来值。

五、App 完成 JSBridge 的最佳计划

1. 完成目标

  1. H5 兼容原生老版别 JSBridge。

  2. 支撑两端双向通讯。针对 webview_flutter 的单向通讯的局限性进行改造优化,使其能支撑回来值的回调。

2. NativeBridge 插件开发

NativeBridge 本质上是对 webview_flutter 的单向通讯才能进行扩展封装

NativeBridge 插件的运用和完成原理,请阅读之前的文章《Flutter插件之NativeBridge》和《NativeBridge完成原理解析》。

3. 完成效果

  1. H5 支撑原生老版别 JSBridge 兼容。
  // 获取app版别号 回来String
  async getVersionCode() {
    // 是否是新的JSBridge
    if (this.isNewJSBridge()) {
      return await window.jsBridgeHelper.sendMessage('getVersionCode', null)
    } else {
      return window.iLotJsBridge.getVersionCode()
    }
  }
  1. 支撑两端双向通讯。
  // H5 获取 App 的值
  const versionNo = await jsBridge.getVersionCode()
  // App 获取 H5 的值
  var isHome = await NativeBridgeHelper.sendMessage("isHome", null, webViewController).future ?? false;
  1. 新增超时连接机制

就像网络恳求相同,咱们不能让代码履行一向阻塞在获取回来值的方位上。因为单向发送音讯是不可靠的,可能存在音讯丢掉,或者另一端不响应音讯的状况。因而咱们需要相似网络恳求相同,添加超时回调机制。

   // 添加回调反常容错机制,避免音讯丢掉导致一向阻塞
   Future.delayed(const Duration(milliseconds: 100), (){
     var completer = _popCallback(callbackId);
     completer?.complete(Future.value(null));
   });

总结

咱们首要介绍了 JSBridge 的概念和原理,然后经过在 Android 、iOS 和 Flutter 中完成 JSBridge 来理解原生和 Flutter 之前的差异,最终总结了在 App 中完成 JSBridge 的最佳计划,计划包含支撑原生和 Flutter 的兼容,并优化 webview_flutter 只支撑单向通讯的局限性和添加超时回调机制。

相关文章:《Flutter插件之NativeBridge》和《NativeBridge完成原理解析》

源码:Android原生完成、Flutter完成源码