一篇文章学会jsBridge的运行机制

目录
  • js调用方式
  • 安卓
    • 1.js调用原生
    • 2.原生调用js
  • ios
  • 总结

我司的APP是一个典型的混合开发APP,内嵌的都是前端页面,前端页面要做到和原生的效果相似,就避免不了调用一些原生的方法,jsBridge就是js原生通信的桥梁,本文不讲概念性的东西,而是通过分析一下我司项目中的jsBridge源码,来从前端角度大概了解一下它是怎么实现的。

js调用方式

先来看一下,js是怎么来调用某个原生方法的,首先初始化的时候会调用window.WebViewJavascriptBridge.init方法:

window.WebViewJavascriptBridge.init()

然后如果要调用某个原生方法可以使用下面的函数:

function native (funcName, args = {}, callbackFunc, errorCallbackFunc) {
    // 校验参数是否合法
    if (args && typeof args === 'object' && Object.prototype.toString.call(args).toLowerCase() === '[object object]' && !args.length) {
        args = JSON.stringify(args);
    } else {
        throw new Error('args不符合规范');
    }
    // 判断是否是手机环境
    if (getIsMobile()) {
        // 调用window.WebViewJavascriptBridge对象的callHandler方法
        window.WebViewJavascriptBridge.callHandler(
            funcName,
            args,
            (res) => {
                res = JSON.parse(res);
                if (res.code === 0) {
                    return callbackFunc(res);
                } else {
                    return errorCallbackFunc(res);
                }
            }
        );
    }
}

传入要调用的方法名、参数和回调即可,它先校验了一下参数,然后会调用window.WebViewJavascriptBridge.callHandler方法。

此外也可以提供回调供原生调用:

window.WebViewJavascriptBridge.registerHandler(funcName, callbackFunc);

接下来看一下window.WebViewJavascriptBridge对象到底是啥。

安卓

WebViewJavascriptBridge.js文件内是一个自执行函数,首先定义了一些变量:

// 定义变量
var messagingIframe;
var sendMessageQueue = [];// 发送消息的队列
var receiveMessageQueue = [];// 接收消息的队列
var messageHandlers = {};// 消息处理器

var CUSTOM_PROTOCOL_SCHEME = 'yy';// 自定义协议
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';

var responseCallbacks = {};// 响应的回调
var uniqueId = 1;

根据变量名简单翻译了一下,具体用处接下来会分析。接下来定义了WebViewJavascriptBridge对象:

var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
    init: init,
    send: send,
    registerHandler: registerHandler,
    callHandler: callHandler,
    _fetchQueue: _fetchQueue,
    _handleMessageFromNative: _handleMessageFromNative
};

可以看到就是一个普通的对象,上面挂载了一些方法,具体方法暂时不看,继续往下:

var doc = document;
_createQueueReadyIframe(doc);

调用了_createQueueReadyIframe方法:

function _createQueueReadyIframe (doc) {
    messagingIframe = doc.createElement('iframe');
    messagingIframe.style.display = 'none';
    doc.documentElement.appendChild(messagingIframe);
}

这个方法很简单,就是创建了一个隐藏的iframe插入到页面,继续往下:

// 创建一个Events类型(基础事件模块)的事件(Event)对象
var readyEvent = doc.createEvent('Events');
// 定义事件名为WebViewJavascriptBridgeReady
readyEvent.initEvent('WebViewJavascriptBridgeReady');
// 通过document来触发该事件
doc.dispatchEvent(readyEvent);

这里定义了一个自定义事件,并直接派发了,其他地方可以像通过监听原生事件一样监听该事件:

document.addEventListener(
    'WebViewJavascriptBridgeReady',
    function () {
        console.log(window.WebViewJavascriptBridge)
    },
    false
);

这里的用处我理解就是当该jsBridge文件如果是在其他代码之后引入的话需要保证之前的代码能知道window.WebViewJavascriptBridge对象何时可用,如果规定该jsBridge必须要最先引入的话那么就不需要这个处理了。

到这里自执行函数就结束了,接下来看一下最开始的init方法:

function init (messageHandler) {
    if (WebViewJavascriptBridge._messageHandler) {
        throw new Error('WebViewJavascriptBridge.init called twice');
    }
    // init调用的时候没有传参,所以messageHandler=undefined
    WebViewJavascriptBridge._messageHandler = messageHandler;
    // 当前receiveMessageQueue也只是一个空数组
    var receivedMessages = receiveMessageQueue;
    receiveMessageQueue = null;
    for (var i = 0; i < receivedMessages.length; i++) {
        _dispatchMessageFromNative(receivedMessages[i]);
    }
}

从初始化的角度来看,这个init方法似乎啥也没做。接下来我们来看callHandler方法,看看是如何调用安卓的方法的:

function callHandler (handlerName, data, responseCallback) {
    _doSend({
        handlerName: handlerName,
        data: data
    }, responseCallback);
}

处理了一下参数又调用了_doSend方法:

function _doSend (message, responseCallback) {
    // 如果提供了回调的话
    if (responseCallback) {
        // 生成一个唯一的回调id
        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
        // 回调通过id存储到responseCallbacks对象上
        responseCallbacks[callbackId] = responseCallback;
        // 把该回调id添加到要发送给native的消息里
        message.callbackId = callbackId;
    }
    // 消息添加到消息队列里
    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

这个方法首先把调用原生方法时的回调函数通过生成一个唯一的id保存到最开始定义的responseCallbacks对象里,然后把该id添加到要发送的信息上,所以一个message的结构是这样的:

{
    handlerName,
    data,
    callbackId
}

接着把该message添加到最开始定义的sendMessageQueue数组里,最后设置了iframesrc属性:yy://__QUEUE_MESSAGE__/,这其实就是一个自定义协议的url,我简单搜索了一下,native会拦截这个url来做相应的处理,到这里我们就走不下去了,因为不知道原生做了什么事情,简单搜索了一下,发现了这个库:WebViewJavascriptBridge,我司应该是在这个库基础上修改的,结合了网上的一些文章后大概知道了,原生拦截到这个url后会调用jswindow.WebViewJavascriptBridge._fetchQueue方法:

function _fetchQueue () {
    // 把我们要发送的消息队列转成字符串
    var messageQueueString = JSON.stringify(sendMessageQueue);
    // 清空消息队列
    sendMessageQueue = [];
    // 安卓无法直接读取返回的数据,因此还是通过iframe的src和java通信
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}

安卓拦截到url后,知道js给安卓发送消息了,所以主动调用js_fetchQueue方法,取出之前添加到队列里的消息,因为无法直接读取js方法返回的数据,所以把格式化后的消息添加到url上,再次通过iframe来发送,此时原生又会拦截到yy://return/_fetchQueue/这个url,那么取出后面的消息,解析出要其中要执行的原生方法名和参数后执行对应的原生方法,当原生方法执行完后又会主动调用jswindow.WebViewJavascriptBridge._handleMessageFromNative方法:

function _handleMessageFromNative (messageJSON) {
    // 根据之前的init方法的逻辑我们知道receiveMessageQueue是会被设置为null的,所以会走else分支
    if (receiveMessageQueue) {
        receiveMessageQueue.push(messageJSON);
    } else {
        _dispatchMessageFromNative(messageJSON);
    }
}

看一下_dispatchMessageFromNative方法做了什么:

function _dispatchMessageFromNative (messageJSON) {
    setTimeout(function () {
        // 原生发回的消息是字符串类型的,转成json
        var message = JSON.parse(messageJSON);
        var responseCallback;
        // java调用完成,发回的responseId就是我们之前发送给它的callbackId
        if (message.responseId) {
            // 从responseCallbacks对象里取出该id关联的回调方法
            responseCallback = responseCallbacks[message.responseId];
            if (!responseCallback) {
                return;
            }
            // 执行回调,js调用安卓方法后到这里顺利收到消息
            responseCallback(message.responseData);
            delete responseCallbacks[message.responseId];
        } else {
            // ...
        }
    });
}

messageJSON就是原生发回的消息,里面除了执行完原生方法后返回的相关信息外,还带着之前我们传给它的callbackId,所以我们可以通过这个id来在responseCallbacks里找到关联的回调并执行,本次js调用原生方法流程结束。但是,明显函数里还有不存在id时的分支,这里是用来干啥的呢,我们前面介绍的都是js调用原生方法,但是显然,原生也可以直接给js发消息,比如常见的拦截返回键功能,当原生监听到返回键事件后它会主动发送信息告诉前端页面,页面就可以执行对应的逻辑,这个else分支就是用来处理这种情况:

function _dispatchMessageFromNative (messageJSON) {
    setTimeout(function () {
        if (message.responseId) {
            // ...
        } else {
            // 和我们传给原生的消息可以带id一样,原生传给我们的消息也可以带一个id,同时原生内部也会通过这个id关联一个回调
            if (message.callbackId) {
                var callbackResponseId = message.callbackId;
                //如果前端需要再给原生回消息的话那么就带上原生之前传来的id,这样原生就可以通过id找到对应的回调并执行
                responseCallback = function (responseData) {
                    _doSend({
                        responseId: callbackResponseId,
                        responseData: responseData
                    });
                };
            }
            // 我们并没有设置默认的_messageHandler,所以是undefined
            var handler = WebViewJavascriptBridge._messageHandler;
            // 原生发送的消息里面有处理方法名称
            if (message.handlerName) {
                // 通过方法名称去messageHandlers对象里查找是否有对应的处理方法
                handler = messageHandlers[message.handlerName];
            }
            try {
                // 执行处理方法
                handler(message.data, responseCallback);
            } catch (exception) {
                if (typeof console !== 'undefined') {
                    console.log('WebViewJavascriptBridge: WARNING: javascript handler threw.', message, exception);
                }
            }
        }
    });
}

比如我们要监听原生的返回键事件,我们先通过window.WebViewJavascriptBridge对象的方法注册一下:

window.WebViewJavascriptBridge.registerHandler('onBackPressed', () => {
    // 做点什么...
})

registerHandler方法如下:

function registerHandler (handlerName, handler) {
    messageHandlers[handlerName] = handler;
}

很简单,把我们要监听的事件名和方法都存储到messageHandlers对象上,然后如果原生监听到返回键事件后会发送如下结构的消息:

{
    handlerName: 'onBackPressed'
}

这样就可以通过handlerName找到我们注册的函数进行执行了。

到此,安卓环境的js和原生互相调用的逻辑就结束了,总结一下就是:

1.js调用原生

生成一个唯一的id,把回调和id保存起来,然后将要发送的信息(带上本次生成的唯一id)添加到一个队列里,之后通过iframe发送一个自定义协议的请求,原生拦截到后调用jswindow.WebViewJavascriptBridge对象的一个方法来获取队列的信息,解析出请求和参数后执行对应的原生方法,然后再把响应(带上前端传来的id)通过调用jswindow.WebViewJavascriptBridge的指定方法传递给前端,前端再通过id找到之前存储的回调,进行执行。

2.原生调用js

首先前端需要事先注册要监听的事件,把事件名和回调保存起来,然后原生在某个时刻会调用jswindow.WebViewJavascriptBridge对象的指定方法,前端根据返回参数的事件名找到注册的回调进行执行,同时原生也会传过来一个id,如果前端执行完相应逻辑后还要给原生回消息,那么要把该id带回去,原生根据该id来找到对应的回调进行执行。

可以看到,js和原生两边的逻辑都是一致的。

ios

ios和安卓基本是一致的,部分细节上有点区别,首先是协议不一样,ios的是这样的:

var CUSTOM_PROTOCOL_SCHEME_IOS = 'https';
var QUEUE_HAS_MESSAGE_IOS = '__wvjb_queue_message__';

然后ios初始化创建iframe的时候会发送一个请求:

var BRIDGE_LOADED_IOS = '__bridge_loaded__';
function _createQueueReadyIframe (doc) {
    messagingIframe = doc.createElement('iframe');
    messagingIframe.style.display = 'none';
    if (isIphone()) {
        // 这里应该是ios需要先加载一下bridge
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME_IOS + '://' + BRIDGE_LOADED_IOS;
    }
    doc.documentElement.appendChild(messagingIframe);
}

再然后是ios获取我们的消息队列时不需要通过iframe,它能直接获取执行js函数返回的数据:

function _fetchQueue () {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;// 直接返回,不需要通过iframe
}

其他部分都是一样的。

总结

本文分析了一下jsBridge的源码,可以发现其实是个很简单的东西,但是平时可能就没有去认真了解过它,总想做一些”大“的事情,以至于沦为了一个”好高骛远“的人,希望各位不要像笔者一样。

到此这篇关于一篇文章学会jsBridge的运行机制的文章就介绍到这了,更多相关jsBridge 运行机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 微信浏览器内置JavaScript对象WeixinJSBridge使用实例

    微信公众平台开始支持前端网页,大家可能看到很多网页上都有分享到朋友圈,关注微信等按钮,点击它们都会弹出一个窗口让你分享和关注,这个是怎么实现的呢?今天就给大家讲解下如何在微信公众平台前端网页上添加分享到朋友圈,关注微信号等按钮. 一.微信内置浏览器 通过 Mac 远程调试 iPhone 上微信自己的网页,我们可以发现微信内嵌浏览器定义了一个私有 JavaScript 对象:WeixinJSBridge,通过操作这个对象的相关方法可以实现分享到微信朋友圈,和判断一个微信号的关注状态以及实现关注指定

  • Javascript之JSBridge初探

    目录 JSBridge 的起源 JSBridge 的双向通信原理 JS 调用 Native Native 调用 JS JSBridge 的使用 总结 JSBridge 的起源 近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题.Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际项目的开发过程中往往会融合两者进行 Hybrid 开发.Native 和 H5 分处两地,看起来无法联系,那么如何才能让双方协同实现功能呢? 这时我们想到了 Cod

  • Flutter使用JsBridge方式处理Webview与H5通信的方法

    目前,移动跨平台开发作为移动开发的重要组成部分,是移动开发者必须掌握的技能,也是自我提升的重要手段.作为Google推出的跨平台技术方案,Flutter具有诸多的优势,已经或正在被广大开发者应用在移动应用开发中.在过去的2019年,我看到越来越多的公司和个人开始使用Flutter来开发跨平台应用,对于移动应用开发来说,Flutter能够满足几乎所有的业务开发需求,所以,学习Flutter正当时. 众所周知,使用Flutter进行项目开发时,就免不了要加载H5页面,在移动开发中打开H5页面需要使用

  • android和js的交互之jsbridge使用教程

    前言 众所周知,app的一些功能可能会使用到H5开发,这就难免会遇到java与js 的相互调用,android 利用WebViewJavascriptBridge 实现js和java的交互,这里介绍下JsBridge第三方库的使用. github传送门:https://github.com/lzyzsd/JsBridge  (本地下载) 简单分析 java与js相互调用如下: java发送数据给js,js接收并回传给java 同理,js发送数据给java,java接收并回传给js 同时两套流程都

  • 一篇文章学会jsBridge的运行机制

    目录 js调用方式 安卓 1.js调用原生 2.原生调用js ios 总结 我司的APP是一个典型的混合开发APP,内嵌的都是前端页面,前端页面要做到和原生的效果相似,就避免不了调用一些原生的方法,jsBridge就是js和原生通信的桥梁,本文不讲概念性的东西,而是通过分析一下我司项目中的jsBridge源码,来从前端角度大概了解一下它是怎么实现的. js调用方式 先来看一下,js是怎么来调用某个原生方法的,首先初始化的时候会调用window.WebViewJavascriptBridge.in

  • 一篇文章学会Vue中间件管道

    通常,在构建SPA时,需要保护某些路由.例如假设有一个只允许经过身份验证的用户访问的 dashboard 路由,我们可以通过使用 auth 中间件来确保合法用户才能访问它. 在本教程中,我们将学到怎样用 Vue-Router[1] 为Vue应用程序实现中间件管道. 什么是中间件管道? 中间件管道(middleware pipeline) 是一堆彼此并行运行的不同的中间件. 继续前面的案例,假设在 /dashboard/movies 上有另一个路由,我们只希望订阅用户可以访问.我们已经知道要访问

  • 一篇文章学会GO语言中的变量

    目录 1.标识符 2.关键字 3.变量 3.1 Go语言中变量的声明 3.2 批量声明 3.3 变量的初始化 3.4 短变量声明 3.5匿名变量 4.常量 5.iota 总结 1.标识符 在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名,常量名,函数 .bc,_123,a1232 2.关键字 关键字是指编程语言中预先定义好的具有特殊含义的标识符,关键字和保留字都不建议用作变量名 Go语言中有25个关键字 break        default      func        

  • 一篇文章弄懂JVM类加载机制过程以及原理

    目录 一.做一个小测试 二.类的初始化步骤: 三.看看你写对了没? 四.类的加载过程 五.类加载器的分类 1.启动类加载器(引导类加载器) 2.扩展类加载器 3.应用程序类加载器(系统类加载器) 六.类加载器子系统的作用 七.总结 一.做一个小测试 通过注释,标注出下面两个类中每个方法的执行顺序,并写出studentId的最终值. package com.nezha.javase; public class Person1 { private int personId; public Perso

  • 一篇文章学会java死锁与CPU 100%的排查

    目录 01 Java死锁排查和解决 1.啥是死锁? 2.为啥子会出现死锁? 3.怎么排查代码中出现了死锁?[重点来了] 第一个姿势:使用 jps + jstack 第二个姿势:使用jconsole 第三个姿势:使用Java Visual VM 4.如何避免死锁? 02.Java CPU 100% 排查技巧 第一个姿势,步骤有点多,难度四星 第二个姿势,待开发[奸笑脸] 03 推荐两个高效排查问题工具 04 总结 05 彩蛋-另一个姿势 00 本文简介 作为一名搞技术的程序猿或者是攻城狮,想必你应

  • 一篇文章搞懂MySQL加锁机制

    目录 前言 锁的分类 乐观锁和悲观锁 共享锁(S锁)和排他锁(X锁) 按加锁粒度区分 全局锁 表级锁(表锁和MDL锁) 意向锁 行锁 间隙锁 next-key lock(临键锁) 加锁规则 死锁和死锁检测 总结 前言 在数据库中设计锁的目的是为了处理并发问题,在并发对资源进行访问时,数据库要合理控制对资源的访问规则. 而锁就是用来实现这些访问规则的一个数据结构. 在对数据并发操作时,没有锁可能会引起数据的不一致,导致更新丢失. 锁的分类 乐观锁和悲观锁 乐观锁: 对于出现更新丢失的可能性比较乐观

  • 一篇文章学会Docker命令小结

    简介 Docker的命令分为使用命令和管理命令,而本文对Docker的使用命令和管理命令进行了汇总和样例提示,以便于他人学习和本人回顾使用. Docker不仅提供了在各个环节下使用的命令,还提供了DockerAPI供我们使用Http来和Docker进行交互,从而开发我们自己的Docker. 由于命令太多,下面给出一个大致的清单供大家对所有命令有一个初步了解,然后就是哪里不会点哪里. 管理命令: container 管理容器 image 管理镜像 network 管理网络 node 管理Swarm

  • 一篇文章学会两种将python打包成exe的方式

    目录 前言 详细步骤 图形窗口打包 总结 前言 python 可以做网站应用,也可以做客户端应用.但是客户端应用需要运行 py 脚本,如果用户不懂 python 就是一件比较麻烦的事情.幸好 pyton 有第三方模块可以将脚本可以转成 exe 执行. 有些人可能要问了既然可以做成网站,为啥还要做成客户端的,直接部署到服务器给客户不就可以了吗?小编的回答是当然是为了追小姐姐呀.在公司给小姐姐写点 python 脚本打包成 exe 减轻上班的工作量.再弄出点 bug,一来二去不就会产生故事了? py

  • 一篇文章学会MySQL基本查询和运算符

    目录 MySQL基本查询 查询概念: 1.查询所有商品: 2.查询某列: 3.别名查询: 4.列别名查询: 5.去重复值查询: 6.查询结果是表达式--运算查询 运算符 1.将所以商品价格上调10%: 2.查询商品名为“海尔洗衣机”的商品的信息 3.查询价格是200或800的所以商品: 4.like-----通配符匹配 5.NULL的使用: 6.函数的使用: 总结 MySQL基本查询 查询概念: 查询是数据库管理系统中一个重要功能,数据查询不应只是简单返回数据库中存储的信息 还应该根据需要对数据

  • 一篇文章教你学会js实现弹幕效果

    目录 新建一个html文件: 建好html文件,搞出初始模版 HTML添加 CSS填充 js逻辑代码 动画效果 下面是弹幕效果 : 相信小伙伴们都看过了,那么它实现的原理是什么呢,那么我们前端怎么用我们web技术去实现呢?? 新建一个html文件: 哈哈哈,大家别像我一样用中文命名. 中文命名是不合规范的,行走江湖,大佬们看见你的中文命名会笑话你的. 上图中,我们引入了jquery插件,没错我们用jq写,回归原始(找不到cdn链接的小伙伴可以百度bootcdn,在里面搜索jquery).并且取了

随机推荐