App实现JSBridge的最佳方案

前沿

写这篇文章的主要目的是对 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实现源码

作者:Fitem
链接:https://juejin.cn/post/7177407635317063735

请添加图片描述

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

请添加图片描述

猜你喜欢

转载自blog.csdn.net/datian1234/article/details/128357819