zoukankan      html  css  js  c++  java
  • 移动混合开发的 JSBridge

    【导读】关于 JSBridge,绝大多数同学最早遇到的是微信的 WeiXinJSBridge(现在被封装成 JSSDK),各种 Web 页面可以通过 Bridge 调用微信提供的一些原生功能,为用户提供相关的功能。其实,JSBridge 很早就出现在软件开发中,在一些桌面软件中很早就运用了这样的形式,多用在通知、产品详情、广告等模块中,然后这些模块中,使用的是 Web UI,而相关按钮点击后,调用的是 Native 功能。现在移动端盛行,不管是 Hybrid 应用,还是 React-Native 都离不开 JSBridge,当然也包括在国内举足轻重的微信小程序。那么,JSBridge 到底是什么?它的出现是为了什么?它究竟是怎么实现的?在这篇文章中,会在移动混合开发的范畴内,将给大家带来 JSBridge 的深入剖析。

    1 前言

    有些童鞋听到 JSBridge 这个名词,就是觉得非常高上大,有了它 Web 和 Native 可以进行交互,就像『进化药水』,让 Web 摇身一变,成为移动战场的『上将一名』。其实并非如此,JSBridge 其实真是一个很简单的东西,更多的是一种形式、一种思想。

    2 JSBridge 的起源

    为什么是 JSBridge ?而不是 PythonBridge 或是 RubyBridge ?

    当然不是因为 JavaScript 语言高人一等(虽然斯坦福大学已经把算法导论的语言从 Java 改成 JavaScript,小得意一下,嘻嘻),主要的原因还是因为 JavaScript 主要载体 Web 是当前世界上的 最易编写 、 最易维护 、最易部署 的 UI 构建方式。工程师可以用很简单的 HTML 标签和 CSS 样式快速的构建出一个页面,并且在服务端部署后,用户不需要主动更新,就能看到最新的 UI 展现。

    因此,开发维护成本 和 更新成本 较低的 Web 技术成为混合开发中几乎不二的选择,而作为 Web 技术逻辑核心的 JavaScript 也理所应当肩负起与其他技术『桥接』的职责,并且作为移动不可缺少的一部分,任何一个移动操作系统中都包含可运行 JavaScript 的容器,例如 WebView 和 JSCore。所以,运行 JavaScript 不用像运行其他语言时,要额外添加运行环境。因此,基于上面种种原因,JSBridge 应运而生。

    PhoneGap(Codova 的前身)作为 Hybrid 鼻祖框架,应该是最先被开发者广泛认知的 JSBridge 的应用场景;而对于 JSBridge 的应用在国内真正兴盛起来,则是因为杀手级应用微信的出现,主要用途是在网页中通过 JSBridge 设置分享内容。

    移动端混合开发中的 JSBridge,主要被应用在两种形式的技术方案上:

    • 基于 Web 的 Hybrid 解决方案:例如微信浏览器、各公司的 Hybrid 方案
    • 非基于 Web UI 但业务逻辑基于 JavaScript 的解决方案:例如 React-Native
      【注】:微信小程序基于 Web UI,但是为了追求运行效率,对 UI 展现逻辑和业务逻辑的 JavaScript 进行了隔离。因此小程序的技术方案介于上面描述的两种方式之间。

    3 JSBridge 的用途

    JSBridge 简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口,让混合开发中的『前端部分』可以方便地使用地址位置、摄像头甚至支付等 Native 功能。

    既然是『简单来讲』,那么 JSBridge 的用途肯定不只『调用 Native 功能』这么简单宽泛。实际上,JSBridge 就像其名称中的『Bridge』的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道。

    所谓 双向通信的通道:

    • JS 向 Native 发送消息 : 调用相关功能、通知 Native 当前 JS 的相关状态等。
    • Native 向 JS 发送消息 : 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。

    这里有些同学有疑问了:消息都是单向的,那么调用 Native 功能时 Callback 怎么实现的?
    对于这个问题,在下一节里会给出解释。

    4 JSBridge 的实现原理

    JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些 Context 与原生运行环境的天然隔离,我们可以将这种情况与 RPC(Remote Procedure Call,远程过程调用)通信进行类比,将 Native 与 JavaScript 的每次互相调用看做一次 RPC 调用。如此一来我们可以按照通常的 RPC 方式来进行设计和实现。

    在 JSBridge 的设计中,可以把前端看做 RPC 的客户端,把 Native 端看做 RPC 的服务器端,从而 JSBridge 要实现的主要逻辑就出现了:通信调用(Native 与 JS 通信) 和 句柄解析调用。(如果你是个前端,而且并不熟悉 RPC 的话,你也可以把这个流程类比成 JSONP 的流程)

    通过以上的分析,可以清楚地知晓 JSBridge 主要的功能和职责,接下来就以 Hybrid 方案 为案例从这几点来剖析 JSBridge 的实现原理。

    4.1 JSBridge 的通信原理

    Hybrid 方案是基于 WebView 的,JavaScript 执行在 WebView 的 Webkit 引擎中。因此,Hybrid 方案中 JSBridge 的通信原理会具有一些 Web 特性。

    4.1.1 JavaScript 调用 Native

    JavaScript 调用 Native 的方式,主要有两种:注入 API 和 拦截 URL SCHEME。

    4.1.1.1 注入API

    注入 API 方式的主要原理是,通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。

    对于 iOS 的 UIWebView,实例如下:

    4.1.2 Native 调用 JavaScript

    相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。

    Native 调用 JavaScript,其实就是执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。(闭包里的方法,JavaScript 自己都调用不了,更不用想让 Native 去调用了)

    对于 iOS 的 UIWebView,示例如下:

    4.2 JSBridge 接口实现

    从上面的剖析中,可以得知,JSBridge 的接口主要功能有两个:调用 Native(给 Native 发消息) 和 接被 Native 调用(接收 Native 消息)。因此,JSBridge 可以设计如下:
    最后用同样的方式加上 Native 调用的回调逻辑,同时对代码进行一些优化,就大概实现了一个功能比较完整的 JSBridge。其代码如下:

    (function () {
        var id = 0,
            callbacks = {},
            registerFuncs = {};
    
        window.JSBridge = {
            // 调用 Native
            invoke: function(bridgeName, callback, data) {
                // 判断环境,获取不同的 nativeBridge
                var thisId = id ++; // 获取唯一 id
                callbacks[thisId] = callback; // 存储 Callback
                nativeBridge.postMessage({
                    bridgeName: bridgeName,
                    data: data || {},
                    callbackId: thisId // 传到 Native 端
                });
            },
            receiveMessage: function(msg) {
                var bridgeName = msg.bridgeName,
                    data = msg.data || {},
                    callbackId = msg.callbackId, // Native 将 callbackId 原封不动传回
                    responstId = msg.responstId;
                // 具体逻辑
                // bridgeName 和 callbackId 不会同时存在
                if (callbackId) {
                    if (callbacks[callbackId]) { // 找到相应句柄
                        callbacks[callbackId](msg.data); // 执行调用
                    }
                } else if (bridgeName) {
                    if (registerFuncs[bridgeName]) { // 通过 bridgeName 找到句柄
                        var ret = {},
                            flag = false;
                        registerFuncs[bridgeName].forEach(function(callback) => {
                            callback(data, function(r) {
                                flag = true;
                                ret = Object.assign(ret, r);
                            });
                        });
                        if (flag) {
                            nativeBridge.postMessage({ // 回调 Native
                                responstId: responstId,
                                ret: ret
                            });
                        }
                    }
                }
            },
            register: function(bridgeName, callback) {
                if (!registerFuncs[bridgeName])  {
                    registerFuncs[bridgeName] = [];
                }
                registerFuncs[bridgeName].push(callback); // 存储回调
            }
        };
    })();
    

    JavaScript 端的 JSBridge 的实现,对于 Native 端涉及的并不多。在 Native 端配合实现 JSBridge 的 JavaScript 调用 Native 逻辑也很简单,主要的代码逻辑是:接收到 JavaScript 消息 => 解析参数,拿到 bridgeName、data 和 callbackId => 根据 bridgeName 找到功能方法,以 data 为参数执行 => 执行返回值和 callbackId 一起回传前端。 Native 调用 JavaScript 也同样简单,直接自动生成一个唯一的 ResponseId,并存储句柄,然后和 data 一起发送给前端即可。

    5 JSBridge 如何引用

    对于 JSBridge 的引用,常用有两种方式,各有利弊。

    5.1 由 Native 端进行注入

    注入方式和 Native 调用 JavaScript 类似,直接执行桥的全部代码。

    它的优点在于:桥的版本很容易与 Native 保持一致,Native 端不用对不同版本的 JSBridge 进行兼容;与此同时,它的缺点是:注入时机不确定,需要实现注入失败后重试的机制,保证注入的成功率,同时 JavaScript 端在调用接口时,需要优先判断 JSBridge 是否已经注入成功。

    5.2 由 JavaScript 端引用

    直接与 JavaScript 一起执行。

    与由 Native 端注入正好相反,它的优点在于:JavaScript 端可以确定 JSBridge 的存在,直接调用即可;缺点是:如果桥的实现方式有更改,JSBridge 需要兼容多版本的 Native Bridge 或者 Native Bridge 兼容多版本的 JSBridge。

    JSBridge传值注意点

    (1) JSBridge只能传String

    切忌勿撕逼
    前端要JSON
    对不起,我只能传String,我帮你拼接,你那边转。
    为什么iOS可以你Android不行呢?
    对不起,我只能传String

    5 JSBridge 如何引用
    对于 JSBridge 的引用,常用有两种方式,各有利弊。

    5.1 由 Native 端进行注入
    注入方式和 Native 调用 JavaScript 类似,直接执行桥的全部代码。

    它的优点在于:桥的版本很容易与 Native 保持一致,Native 端不用对不同版本的 JSBridge 进行兼容;与此同时,它的缺点是:注入时机不确定,需要实现注入失败后重试的机制,保证注入的成功率,同时 JavaScript 端在调用接口时,需要优先判断 JSBridge 是否已经注入成功。

    5.2 由 JavaScript 端引用
    直接与 JavaScript 一起执行。

    与由 Native 端注入正好相反,它的优点在于:JavaScript 端可以确定 JSBridge 的存在,直接调用即可;缺点是:如果桥的实现方式有更改,JSBridge 需要兼容多版本的 Native Bridge 或者 Native Bridge 兼容多版本的 JSBridge。

    https://mp.weixin.qq.com/s/I812Cr1_tLGrvIRb9jsg-A 微信的 JSBridge说明
    https://github.com/lzyzsd/JsBridge github地址
    https://blog.csdn.net/carson_ho/article/details/64904691/?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242

  • 相关阅读:
    centos8 安装postresql12
    vs code 开启远程调试步骤
    node 版本管理器 nvs
    Vue I18n Vue.js 的国际化插件+elementUI的使用
    c#结构
    下拉菜单
    使用Convert 类和Parse方法将字符串转换为数值类型
    c# try..... catch
    c#迭代算法
    网页兼容各种浏览器
  • 原文地址:https://www.cnblogs.com/liliuyu/p/13830565.html
Copyright © 2011-2022 走看看