JSBridge通信原理
JSBridge是个啥
直接来重点,记住:JSBridge 是一个很简单的东西,更多的是一种形式、一种思想,为了解决 H5 和 Native 的双向通信。
就像我们刚接触 ajax 时,也很懵逼。其实,他们俩个差不多,ajax 是浏览器和服务器通信的规范(暂且叫规范,像 CMD 规范一样,SeaJS 是它的一种实现方式), JSBridge 是 H5 和 Native 通信的规范。axios 是 ajax 通信的一种实现方式,WebViewJavascriptBridge(下文要说) 是 JSBridge 的一种实现方式。明白了这些,下面就很好理解了。
回到顶部
H5 和 Native 的双向通信通用方法
H5通信方式和兼容性如下表所示。指的是借助 Native 的 webview 加载H5页面,H5 和 Native 之间通过注入API、URL拦截、全局调用等形式,实现消息通信。站在大厂的角度考虑,在实战的时候,会选择更兼容的方式。
H5调用Native方法
平台 | 方法 | 备注 |
---|---|---|
Android | shouldOverrideUrlLoading | scheme拦截方法 |
Android | addJavascriptInterface | API |
Android | onJsAlert()、onJsConfirm()、onJsPrompt() | |
IOS | 拦截URL | |
IOS | JavaScriptCore | API方法,IOS7+ 支持 |
IOS | window.webkit.messageHandlers | APi方法,IOS8+支持 |
1.注入 API 方式的主要原理:通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。
说白了就是,Native 往 window 对象挂对象或方法,让 H5 可以调 Native 的方法。具体挂的对象或方法是 Native 定义的,比如人家挂了个getName(arg),H5 调用就是window.getName(arg),当然调用时可以向 Native 传数据。
2.拦截 url scheme原理:先解释一下 url scheme: url scheme 是一种类似于 url 的链接,是为了方便app直接互相调用设计的,形式和普通的 url 近似,主要区别是 protocol 和 host 一般是自定义的,例如: httpsss://bridge_loaded/url?url=http://ymfe.tech,protocol 是 httpsss,host 则是 bridge_loaded。
拦截 url scheme 的主要流程是:**Web 端通过某种方式(例如 iframe.src)发送 url scheme 请求,之后 Native 拦截到请求并根据 url scheme(包括所带的参数)进行相关操作。 **
Native调用H5方法
平台 | 方法 | 备注 |
---|---|---|
Android | loadurl() | |
Android | evaluateJavascript() | Android 4.4 + |
IOS(UIwebview) | stringByEvaluatingJavaScriptFromString | |
IOS(UIwebview) | JavaScriptCore | IOS7+ 支持 |
IOS(Wkwebview) | evaluateJavaScript:javaScriptString | IOS8+支持 |
相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。
Native 调用 JavaScript,其实就是执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。(闭包里的方法,JavaScript 自己都调用不了,更不用想让 Native 去调用了)
通信原理总结
通信原理是 JSBridge 实现的核心,实现方式可以各种各样,但是万变不离其宗。这里,推荐的实现方式如下:
- JavaScript 调用 Native 推荐使用 注入 API 的方式(iOS6 忽略,Android 4.2以下使用 WebViewClient 的 onJsPrompt 方式)。
- Native 调用 JavaScript 则直接执行拼接好的 JavaScript 代码即可。
说实话,作为一个前端开发,刚开始看了上面这些方法啥的,我是一脸懵*。毕竟是嵌套在人家 Native 里面,规则都是他们实现的,我们H5只能遵循这个规则去玩。但一定要理清他们约定的这套规则是如何通信的,才能保证我们的愉快的交流。
通信的原理大概介绍完了,下面介绍实战中如何使用,帮我们更好的理解概念。补充一句,通信的实现方式有很多种,下面只是我司的实现方式:(没有 Native 代码,纯web前端角度)
回到顶部
H5 和 Native 通信实战
因为很多地方需要用同样的方法,比如:关闭H5页面并吐司、上传图片、预览图片、右上角的“增加”(╋)按钮、...所以,我们把和原生通信的方法写在一个js 文件里,直接 export 导出,方便代码的复用。
复制// mob.js
// 判断是什么平台(设备)
var browser = {versions:function(){var u = navigator.userAgent, app = navigator.appVersion;return {trident: u.indexOf('Trident') > -1, // IEpresto: u.indexOf('Presto') > -1, // operawebKit: u.indexOf('AppleWebKit') > -1, // webkitgecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, // firefoxmobile: !!u.match(/AppleWebKit.*Mobile.*/), // mobileios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // iOSandroid: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, // android or uciPhone: u.indexOf('iPhone') > -1 , // iPhone QQHDiPad: u.indexOf('iPad') > -1, // iPadwebApp: u.indexOf('Safari') == -1,teacherApp: u.indexOf('XRJ-Admin') > -1 // 教师端 原生在userAgent放不同的字符串代表不同appguardianApp: u.indexOf('XRJ-Edu') > -1 // 家长端};}(),language: (navigator.browserLanguage || navigator.language).toLowerCase()
}// 判断是否是移动设备打开。browser代码在下面
if(browser.versions.mobile) {var ua = navigator.userAgent.toLowerCase();//获取判断用的对象if (ua.match(/MicroMessenger/i) == "micromessenger") {//在微信中打开}if (ua.match(/WeiBo/i) == "weibo") {//在新浪微博客户端打开}if (ua.match(/QQ/i) == "qq") {//在QQ空间打开}if (browser.versions.ios) {//是否在IOS浏览器打开}if(browser.versions.android){//是否在安卓浏览器打开}
} else {//是PC浏览器打开
}/**
* 我司通信实现方案:
* iOS端注入 WebViewJavascriptBridge 对象或者拦截 url scheme,下面的setupWebViewJavascriptBridge是固定写法
* 这是我司iOS的做法,也是iOS的通用做法。
*
* Android当然也可以这么做,如果他们这样实现,后面H5封装的函数只用写一套就可以适配2端了。
* 我司Android不是这么做的,是通过注入对象或方法实现的,所以下面要针对2端写不同代码。
*/
function setupWebViewJavascriptBridge(callback) {if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }window.WVJBCallbacks = [callback];var WVJBIframe = document.createElement('iframe');WVJBIframe.style.display = 'none';WVJBIframe.src = 'https://__bridge_loaded__';document.documentElement.appendChild(WVJBIframe);setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}// 关闭页面并吐丝
// 调用:pageClose(1, '操作成功)
function pageClose(code, msg = ''){setupWebViewJavascriptBridge(function(bridge) {bridge.callHandler('iOS_RESPONSE_CALL_BACK', {"code":code,"msg":msg}, function responseCallback(responseData) {// 关闭页面完毕的回调函数,类似 ajax() 的 success: function(result) {}// responseData:回调函数返回的数据,类似 success 里面的 result});});if(browser.versions.android){if(code == 1){todo.closeWindow(0);}else if(code == 401){todo.refreshWebView;return false;};if(msg) todo.showToast(msg);}
}// 图片上传
// 调用:load()
function load(){setupWebViewJavascriptBridge(function(bridge) {bridge.callHandler('iOS_UPLOAD_PHOTO', [], function responseCallback(responseData) {// 上传完毕回调函数imgload(responseData,true); // H5 页面中定义的全局方法 window.imgload = function(data,flag) {}})});if(browser.versions.android){picture.showPictureDialog(); // webView.loadUrl("javascript:imgload");}
}// 图片浏览 - data数据格式和原生商量好
// 调用:var imgData = {"position":position,"list":[imgsrc0,imgsrc1,imgsrc2]}; imgSee(imgData);
function imgSee(data){setupWebViewJavascriptBridge(function(bridge) {bridge.callHandler('iOS_PHOTO_BROWSER',data); // 调用 iOS 的 'iOS_PHOTO_BROWSER' 方法,同时传数据data});if(browser.versions.android){data = JSON.stringify(data);todo.startGallery(data); // 调用 Android 的 startGallery 方法,同时传数据data}
}// 单个头部菜单,右上角的“新增”按钮。因为是原生组件,我们操作不到,所以需要初始化页面时往这个按钮上绑定js方法,以便我们之后操作。
// 调用: topMenu('╋',0) || topMenu('新增',0)
function topMenu(title, index){setupWebViewJavascriptBridge(function(bridge) {// js注册方法 'JS_MENU_ACTION' 给 iOS 调用 - 方法名 H5 决定bridge.registerHandler('JS_MENU_ACTION', function(data, responseCallback) {topMenuHandle(data,true); // H5 页面中定义的全局方法 window.topMenuHandle = function(data,flag) {}responseCallback(data); // 做完后,告诉 iOS 一声})// 调 iOS 的 'iOS_MENU_JSON' 并传参bridge.callHandler('iOS_MENU_JSON', [{"action":index,"title":title}]); // - 方法名 iOS 决定});if(browser.versions.android){// topMenuHandle是往“新增”按钮绑定的js方法,// index是触发这个函数时回传给js的数据,用来判定点击个哪个按钮,title是这个按钮的名字// H5 把一切安排的明明白白的var jsonStr = '[{"action":"javascript:topMenuHandle('+index+')","title":'+title+'}]'menu.inflateMenu(jsonStr); // jsonStr必须是字符串}
}// 多个头部菜单
// 调用:var titleData = [{'title':"添加行为",'src':base.config.imgHost+"/cs/img/icon_bullet_behavior_add.png"},{'title':"审核行为",'src':base.config.imgHost+"/cs/img/icon_bullet_behavior_review.png"}]
// topMenus(titleData)
function topMenus(data){var dataList = [];$.each(data,function(index,item){dataList.push('{"title":"'+item.title+'","src":"'+item.src+'"'+(browser.versions.android?',"action":"javascript:topMenusHandle('+index+')"':'')+'}');})setupWebViewJavascriptBridge(function(bridge) {bridge.registerHandler('JS_MORE_MENU_ACTION', function(responseData) {topMenusHandle(responseData,true);})bridge.callHandler('iOS_MORE_MENU', dataList)});if(browser.versions.android){menu.inflateCustomMenu('['+dataList+']');}
}// 导出
export { browser,load,imgSee,topMenu,topMenus,pageClose,
}
回到顶部
总结
Hybrid是一种连接 H5 跟 Native 的思路,即可以快速迭代H5功能,又可以有NA的体验,是混合开发的典型开发模式。实践过程中需要根据业务形态模型来定制代码实现,注入时机也不是一成不变的可以根据业务形态来选择。
参考链接:
JSBridge实战
移动混合开发中的 JSBridge
WebViewJavascriptBridge详细使用
Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
JSBridge通信原理相关推荐
- JSBridge 技术原理分析
- JSBridge的起源 - PhoneGap(Codova 的前身)作为 Hybrid 鼻祖框架,是一个开源的移动开发框架,允许你用标准的web技术-HTML5,CSS3和JavaS ...
- webview之JSB通信原理
1.前言 在这个移动互联网盛行的时代,移动应用的开发就需求量剧增,早期的移动端应用大都使用原生开发(android,ios),而现在的移动开发技术选型上基本都是混合开发(Hybrid),混合开发是一种 ...
- ReactNative与iOS通信原理解析-通信篇
文章首发个人博客: ReactNative与iOS通信原理解析-通信篇 导语:其实原本是想编写一篇 react-native (下文简称 rn) 在 iOS 中如何实现 jsbridge 的文章 ...
- JSBridge的原理及使用
一.什么是JSBridge 主要是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置.摄像头). 而且 JSBri ...
- jsbridge实现及原理_Android JSBridge的原理与实现
原标题:Android JSBridge的原理与实现 JSBridge 在Android中,JSBridge已经不是什么新鲜的事物了,各家的实现方式也略有差异.大多数人都知道WebView存在一个漏洞 ...
- JSBridge的原理与实现
为什么要用 JSBridge 顾名思义,JSBridge是js和Native之间通信的桥梁. Android4.2以下,addJavascriptInterface方式有安全漏洞. url schem ...
- Binder跨进程通信原理(三):Binder IPC实现原理
1. 动态内核可加载模块 && 内存映射 正如上一章所说, 跨进程通信是需要内核空间做支持的. 传统的 IPC 机制如 管道, Socket, 都是内核的一部分, 因此通过内核支持来实 ...
- Binder跨进程通信原理(一):动态内核加载模块
先上一张Binder 的工作流程图.(如果不清晰,可以 复制图片链接到浏览器 或 保存到本地 查看,我经常都是这样看图的哈) 一开始上手,陌生的东西比较多,But,其实并不复杂.喔,流程图是用 Pro ...
- 传统的Linux中IPC通信原理
在了解 Binder 跨进程通信原理之前, 我们先了解一下 Linux 传统的进程间通信的概念和基本原理, 这样有助于我们更好的理解 Binder 的通信原理. 这个部分基本都是理论, 基础不是很好的 ...
最新文章
- shell getopts命令
- OpenGL实现flocking (聚集)实例
- 【转】Android中定时器的3种实现方法
- 使用方法实现数组的对调与输出
- C#中泛型类型约束条件
- Xshell连接远程Linux服务器失败Could not connect to ‘192.xxx.xx.xxx‘ (port 22): Connection failed.
- cisco初级随堂笔记2
- 在Linux上安装Elasticsearch Kibaba.md
- Nginx源码目录介绍
- 苹果手机来电归属地_手机号码归属地能否取消?工信部回应...
- 计算机网络的社会环境分析_2020年昆仑银行分行社会招聘工作人员公告
- 铁道部网站登录难点分析
- 浅谈 CMap 与 map
- Unity接入穿山甲广告(使用unity插件SDK接入)看这一篇就够了
- perror和strerror的使用和区别
- 中国下一个十年的大趋势
- R之方差分析与秩和Kruskal-Wallis
- Qt使用qtwebapp编写http服务的步骤
- 10组团队项目-Alpha冲刺-2/6
- Bartender 4 for Mac(菜单栏应用图标管理)
热门文章
- v-if和v-for为什么不能一起使用
- 陕西有计算机专业的二本大学排名及分数线,陕西二本录取分数线2021?附陕西二本公办学校实力排名...
- Zipkin/Brave 整合Spring-MVC 框架实战
- 计算机职称photoshop,计算机职称考试Photoshop6.0图像处理考试大纲
- 我对git 、github的初印象
- ACSL 美国计算机科学联赛 2016-2017 R4 摩天大楼-Skyscraper 题解
- 写字楼价格大跌,摩天大楼面临危机
- OpenAI居然能自动写论文?导师直言我都犯难了...
- 微软等世界500强公司的面试问题(1)
- 步进电机转速与脉冲频率的关系(这篇文章有些地方似乎不对)