js-bridge原理

  • js无法直接调用 app 的 API ,需要通过一种方式 —— 通称 js-bridge ,它也是一些js代码。当然,前提是 app 得开发支持,控制权在 app 端。就像跨域,server 不开放支持,客户端再折腾也没用。
  • 任何一个移动操作系统中都包含可运行 JavaScript 的容器,例如 WebView 和 JSCore。所以,运行 JavaScript 不用像运行其他语言时,要额外添加运行环境。因此,基于上面种种原因,JSBridge 应运而生
  • JSBridge 就像其名称中的『Bridge』的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道

JSBridge实现

方式1 - 注入 API

  • 客户端为 webview 做定制开发,通过webview提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。

对于 iOS 的 UIWebView,实例如下:

JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) {
    // Native 逻辑
};

//前端调用方式
window.postBridgeMessage(message);


方式2 - 劫持 url scheme

  • 是一种类似于url的链接,主要区别是 protocol 和 host 一般是自定义的
  • 通过一个 iframe 请求 url
    • 为什么选择 iframe.src 不选择 locaiton.href ?因为如果通过 location.href 连续调用 Native,很容易丢失一些调用。
  • 如下方’my-app-name’ 是一个未识别的协议名称。既然未识别的协议,那就可以 app 监听所有的网络请求,遇到 my-app-name: 协议,就分析 path ,并返回响应的内容。
  • url 长度不够时,在 iOS 上采用了使用 Ajax 发送同域请求的方式,并将参数放到 head 或 body 里。这样,虽然规避了 url 长度的隐患,但是 WKWebView 并不支持这样的方式
  • 优点:
    • 支持 iOS6
const iframe1 = document.getElementById('iframe1')
iframe1.onload = () => {
    console.log(iframe1.contentWindow.document.body.innerHTML) // '{ version: '1.0.1' }'
}
iframe1.src = 'my-app-name://api/getVersion'
//微信的 scheme 以 'weixin://' 开头
//chrome://version 查看版本信息
//chrome://dino 恐龙小游戏

基于注入式封装sdk

  • native端不执行回调函数,只返回结果,所以需要发送方记录回调id,当结果返回时自己调用
(function () {
    var id = 0,
    //根据id存储的回调
    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 原封不动传回
                //如果需要回调native端,则需要记录native端本次的id
                responstId = msg.responstId;
            // 具体逻辑
            // bridgeName 和 callbackId 不会同时存在
            if (callbackId) {
                if (callbacks[callbackId]) { // 找到相应句柄
                    callbacks[callbackId](msg.data); // 执行调用
                }
            } elseif (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);
                        });
                    });
                    //发送方执行回调后,回调native端
                    if (flag) {
                        nativeBridge.postMessage({ // 回调 Native
                            responstId: responstId,
                            ret: ret
                        });
                    }
                }
            }
        },
        //通过调用名称存储回调
        register: function(bridgeName, callback) {
            if (!registerFuncs[bridgeName])  {
                registerFuncs[bridgeName] = [];
            }
            registerFuncs[bridgeName].push(callback); // 存储回调
        }
    };
})();

基于scheme封装sdk

const sdk = {
    invoke(url, data, success, err) {
        const iframe = document.createElement('iframe')
        iframe.style.display = 'none'
        document.body.appendChild(iframe)

        iframe.onload = () => {
            const content = iframe.contentWindow.document.body.innerHTML
            success(JSON.parse(content))
            iframe.remove()
        }
        iframe.onerror = () => {
            err()
            iframe.remove()
        }
        iframe.src = `my-app-name://${url}?data=${JSON.string(data)}`
    }

    fn1(data, success, err) {
        invoke('api/fn1', data, success, err)
    }

    fn2(data, success, err) {
        invoke('api/fn2', data, success, err)
    }
}

// 使用
sdk.fn1(
    {a: 10},
    (data) => { console.log('success', data) },
    () => { console.log('err') }
)

Native 调用 JavaScript

  • 执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。

JSBridge注入

由 Native 端进行注入

  • 注入方式和 Native 调用 JavaScript 类似,直接执行桥的全部代码
  • 优点在于:桥的版本很容易与 Native 保持一致,Native 端不用对不同版本的 JSBridge 进行兼容
  • 缺点是:注入时机不确定,需要实现注入失败后重试的机制,保证注入的成功率,同时 JavaScript 端在调用接口时,需要优先判断 JSBridge 是否已经注入成功

由 JavaScript 端引用

  • 直接与 JavaScript 一起执行。
  • 优点在于:JavaScript 端可以确定 JSBridge 的存在,直接调用即可;缺点是:如果桥的实现方式有更改,JSBridge 需要兼容多版本的 Native Bridge 或者 Native Bridge 兼容多版本的 JSBridge。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值