写点什么

小白必看,JSBridge 初探

  • 2021-03-18
  • 本文字数:4606 字

    阅读完需:约 15 分钟

小白必看,JSBridge 初探

JSBridge 的起源


近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题。Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际项目的开发过程中往往会融合两者进行 Hybrid 开发。Native 和 H5 分处两地,看起来无法联系,那么如何才能让双方协同实现功能呢?


这时我们想到了 Codova ,Codova 提供了一组与设备相关的 API,是早期 JS 调用原生代码来实现原生功能的常用方案。不过 JSBridge 真正在国内广泛应用是由于移动互联网的盛行。


JSBridge 是一种 JS 实现的 Bridge,连接着桥两端的 Native 和 H5。它在 APP 内方便地让 Native 调用 JS,JS 调用 Native,是双向通信的通道。JSBridge 主要提供了 JS 调用 Native 代码的能力,实现原生功能如查看本地相册、打开摄像头、指纹支付等。


H5 与 Native 对比

nameH5Native
稳定性调用系统浏览器内核,稳定性较差使用原生内核,更加稳定
灵活性版本迭代快,上线灵活迭代慢,需要应用商店审核,上线速度受限制
受网速 影响较大较小
流畅度有时加载慢,给用户“卡顿”的感觉加载速度快,更加流畅
用户体验功能受浏览器限制,体验有时较差原生系统 api 丰富,能实现的功能较多,体验较好
可移植性兼容跨平台跨系统,如 PC 与 移动端,iOS 与 Android可移植性较低,对于 iOS 和 Android 需要维护两套代码


JSBridge 的双向通信原理


JS 调用 Native

JS 调用 Native 的实现方式较多,主要有拦截 URL Scheme 、重写 prompt 、注入 API 等方法。

拦截 URL Scheme

Android 和 iOS 都可以通过拦截 URL Scheme 并解析 Scheme 来决定是否进行对应的 Native 代码逻辑处理。


Android 的话,Webview 提供了 shouldOverrideUrlLoading 方法来提供给 Native 拦截 H5 发送的 URL Scheme 请求。代码如下:


publicclass CustomWebViewClient extends WebViewClient {  @Override  public boolean shouldOverrideUrlLoading(WebView view, String url) {  ......    // 场景一:拦截请求、接收 scheme    if (url.equals("xxx")) {
// handle ... // callback view.loadUrl("javascript:setAllContent(" + json + ");") returntrue; } returnsuper.shouldOverrideUrlLoading(url); }}
复制代码


iOS 的 WKWebview 可以根据拦截到的 URL Scheme 和对应的参数执行相关的操作。代码如下:


- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{    if ([navigationAction.request.URL.absoluteString hasPrefix:@"xxx"]) {        [[UIApplication sharedApplication] openURL:navigationAction.request.URL];    }    decisionHandler(WKNavigationActionPolicyAllow);}
复制代码


这种方法的优点是不存在漏洞问题、使用灵活,可以实现 H5 和 Native 页面的无缝切换。例如在某一页面需要快速上线的情况下,先开发出 H5 页面。某一链接填写的是 H5 链接,在对应的 Native 页面开发完成前先跳转至 H5 页面,待 Native 页面开发完后再进行拦截,跳转至 Native 页面,此时 H5 的链接无需进行修改。但是使用 iframe.src 来发送 URL Scheme 需要对 URL 的长度作控制,使用复杂,速度较慢。


重写 prompt 等原生 JS 方法


Android 4.2 之前注入对象的接口是 addJavascriptInterface ,但是由于安全原因慢慢不被使用。一般会通过修改浏览器的部分 Window 对象的方法来完成操作。主要是拦截 alert、confirm、prompt、console.log 四个方法,分别被 Webview 的 onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt 监听。其中 onJsPrompt 监听的代码如下:


public boolean onJsPrompt(WebView view, String origin, String message, String 		defaultValue, final JsPromptResult result) {  String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message,			 defaultValue);  xxx;  returntrue;}
复制代码


iOS 由于安全机制, WKWebView 对 alert、confirm、prompt 等方法做了拦截,如果通过此方式进行 Native 与 JS 交互,需要实现 WKWebView 的三个 WKUIDelegate 代理方法。代码示例如下:


-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
复制代码


使用该方式时,可以与 Android 和 iOS 约定好使用传参的格式,这样 H5 可以无需识别客户端,传入不同参数直接调用 Native 即可。剩下的交给客户端自己去拦截相同的方法,识别相同的参数,进行自己的处理逻辑即可实现多端表现一致。如:


alert("确定xxx?", "取消", "确定", callback());
复制代码


另外,如果能与 Native 确定好方法名、传参等调用的协议规范,这样其它格式的 prompt 等方法是不会被识别的,能起到隔离的作用。


注入 API


基于 Webview 提供的能力,我们可以向 Window 上注入对象或方法。JS 通过这个对象或方法进行调用时,执行对应的逻辑操作,可以直接调用 Native 的方法。使用该方式时,JS 需要等到 Native 执行完对应的逻辑后才能进行回调里面的操作。


Android 的 Webview 提供了 addJavascriptInterface 方法,支持 Android 4.2 及以上系统。


gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge');publicclass JavaScriptInterface {  Context mContext;
JavaScriptInterface(Context c) { mContext = c; }
public void share(String webMessage){ // Native 逻辑 }}
复制代码


JS 调用示例:


window.NativeApi.share(xxx);
复制代码


iOS 的 UIWebview 提供了 JavaScriptScore 方法,支持 iOS 7.0 及以上系统。WKWebview 提供了 window.webkit.messageHandlers 方法,支持 iOS 8.0 及以上系统。UIWebview 在几年前常用,目前已不常见。以下为创建  WKWebViewConfiguration 和 创建 WKWebView 示例:


WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];WKPreferences *preferences = [WKPreferences new];preferences.javaScriptCanOpenWindowsAutomatically = YES;preferences.minimumFontSize = 40.0;configuration.preferences = preferences;    
- (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"share"]; [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"pickImage"];}- (void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"share"]; [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"pickImage"];}
复制代码


JS 调用示例:


window.webkit.messageHandlers.share.postMessage(xxx);
复制代码


  • Native 调用 JS


Native 调用 JS 比较简单,只要 H5 将  JS 方法暴露在 Window 上给 Native 调用即可。

Android 中主要有两种方式实现。在 4.4 以前,通过 loadUrl 方法,执行一段 JS 代码来实现。在 4.4 以后,可以使用 evaluateJavascript 方法实现。loadUrl 方法使用起来方便简洁,但是效率低无法获得返回结果且调用的时候会刷新 WebView。evaluateJavascript 方法效率高获取返回值方便,调用时候不刷新 WebView,但是只支持 Android 4.4+。相关代码如下:


webView.loadUrl("javascript:" + javaScriptString);webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {  @Override  public void onReceiveValue(String value){    xxx  }});
复制代码


iOS 在 WKWebview 中可以通过 evaluateJavaScript:javaScriptString 来实现,支持 iOS 8.0 及以上系统。


// swiftfunc evaluateJavaScript(_ javaScriptString: String,  completionHandler: ((Any?, Error?) -> Void)? = nil)// javaScriptString 需要调用的 JS 代码// completionHandler 执行后的回调
复制代码


// objective-c[jsContext evaluateJavaScript:@"ZcyJsBridge(ev, data)"]
复制代码


JSBridge 的使用


  • 如何引用

  • 由 H5 引用

    在我司移动端初期版本时采用的是该方式,采用本地引入 npm 包的方式进行调用。这种方式可以确定 JSBridge 是存在的,可直接调用 Native 方法。但是如果后期 Bridge 的实现方式改变,双方需要做更多的兼容,维护成本高

  • 由 Native 注入

    这是当前我司移动端选用的方式。在考虑到后期业务需要的情况下,进行了重新设计,选用 Native 注入的方式来引用 JSBridge。这样有利于保持 API 与 Native 的一致性,但是缺点是在 Native 注入的方法和时机都受限,JS 调用 Native 之前需要先判断 JSBridge 是否注入成功

  • 使用规范


H5 调用 Native 方法的伪代码实例,如:


params = {  api_version: "xxx",	// API 版本  title: "xxx",	// 标题  filename: "xxx",	// 文件名称  image: "xxx",	// 图片链接  url: "xxx",	// 网址链接  success: function (res) {    xxx;	// 调用成功后执行  },  fail: function (err) {    if (err.code == '-2') {      fail && fail(err);	//	调用了当前客户端中不存在的 API 版本    } else {      const msg = err.msg;	//异常信息      Toast.fail(msg);    }  }};window.NativeApi.share(params);
复制代码


以下简要列出通用方法的抽象,目前基本遵循以下规范进行双端通信。


window.NativeApi.xxx({  api_version:'',  name: "xxx",  path: "xxx",  id:	"xxx",  success: function (res) {    console.log(res);  },  fail: function (err) {    console.log(err);  }});
复制代码


由于初期版本选择了由 H5 本地引用 JSBridge,后期采用 Native 注入的方式。现有的 H5 需要对各种情况做兼容,逻辑抽象如下:


reqNativeBridge(vm, fn) {  if (!isApp()) {    // 如果不在 APP 内进行调用    vm.$dialog.alert({      message: "此功能需要访问 APP 才能使用",    });  } else {    if (!window.NativeApi) {      // 针对初期版本      vm.$dialog.alert({        message: "请更新到最新 APP 使用该功能",      });    } else {      // 此处只针对“调用了当前客户端中不存在的 API 版本”的报错进行处理      // 其余种类的错误信息交由具体的业务去处理      fn && fn((err) => {        vm.$dialog.alert({          message: "请更新到最新 APP 使用该功能",        });      });    }  }}
复制代码


总结


上述内容简要介绍了 JSBridge 的部分原理,希望对从未了解过 JSBridge 的同学能有所帮助。如果需要更深入的了解 JSBridge 的原理和实现,如 JSBridge 接口调用的封装实现,JS 调用 Native 时的回调的唯一性等。大家可以去查阅更多资料,参考更详细的相关文档或他人的整理成文的沉淀。



头图:Unsplash

作者:见一

原文:https://mp.weixin.qq.com/s/Tsc52538Se2VumuiszEpnA

原文:小白必看,JSBridge 初探

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-03-18 00:254275

评论

发布
暂无评论
发现更多内容

企业引进外部专家合作开发时,如何保证数字资产既开放又安全?

ModelWhale

数字化转型 数据安全 资产安全 技术专家 协同开发

阿里的职级是如何上升的,是工作经验还是能力?(附阿里面试题)

程序知音

Java 阿里巴巴 java面试 后端技术 八股文

教育行业运维审计用什么堡垒机好?有什么作用?

行云管家

网络安全 教育 堡垒机 IT运维 运维审计

从InfluxDB到TDengine,阳光氢能为什么会做出这个选择?

TDengine

数据库 tdengine 时序数据库

SAP AMDP 介绍 - ABAP 托管的 HANA 数据库过程

汪子熙

数据库 SAP abap 8月月更 AMDP

「GitLab篇」如何用Git平台账号登录建木CI

Jianmu

开源 持续集成 CI/CD 持续部署 流水线

MAUI + Masa Blazor 开发带自动更新功能的安卓App

MASA技术团队

.net blazor MASA MAUI Xamarin

LCD液晶屏和LED显示屏有什么区别?

Dylan

LED显示屏 户外LED显示屏 led显示屏厂家

即时通讯安全篇(十):IM聊天系统安全手段之通信连接层加密技术

JackJiang

网络安全 https 网络编程 即时通讯 SSL/TLS

SAP Fiori Launchpad Tile,UI5 应用,和 PFCG Role 的对应关系

汪子熙

SAP Fiori Launchpad ui5 8月月更

【IT运维】Linux运维需要掌握哪些技能?

行云管家

Linux 运维 linux运维 IT运维

零故障支持数百场重大会议成功举办,HW云会议做了这些事

科技怪咖

从入门到高手,数据从业者的成长一般都要经过哪些阶段?

雨果

数据工程师必备技能

开源一夏 | 如何在 JavaScript 中创建虚拟键盘

海拥(haiyong.site)

JavaScript 开源 前端 8月月更

太强了!字节大佬的《设计模式宝典》越读越有意思!

退休的汤姆

Java、 面经 社招 Java工程师 秋招

ArkID 企业级开源 IDaaS/IAM 统一身份认证授权管理解决方案

龙归科技

开源项目 iam SSO Idaas

如何做好分支管理,保证高效CI/CD?

华为云开发者联盟

git 开发

开源一夏 |企业内部应用接入钉钉获取部门及人员信息

六月的雨在InfoQ

开源 钉钉 API 钉钉开放平台 8月月更

技术开发人员:一款远超Hue的SQL工具!

雨果

sql

什么样的数据架构可以彻底解决企业数据孤岛的问题?

雨果

数据孤岛

Spring Security + Vue + Flowable 怎么玩?

江南一点雨

Java spring springsecurity flowable

开源一夏 | STM32对接涂鸦wifi模块项目(智能插座-开源)

矜辰所致

开源 stm32 WiFi物联网智能插座 8月月更 涂鸦智能

阿里云 EMAS Serverless 重磅发布

hum建应用专家

云原生

MSE 费芮新金融行业标杆案例

阿里巴巴中间件

阿里云 微服务 云原生

企业如何将自身的数字技术及研究成果快速对外发布应用

ModelWhale

数字化转型 部署 应用模型 对外接口 协同开发

2022年中国生鲜电商年度综合分析

易观分析

电商 生鲜

云会议玩法升级

sofiya

字节内部MySQL宝典意外流出!堪称数据库的天花板

退休的汤姆

Java、 面经 Java工程师 秋招 MySQL 数据库

发展靠扩大人力规模,而不是技术研发创新,国内软件行业如何破局?

龙归科技

开源项目 Idaas 龙归科技 统一软件市场 ArkID

1对1直播源码:运行多个任务,资源如何切换?

开源直播系统源码

软件开发 一对一直播源码 直播系统源码 语音连麦app

【有奖评测局】阿里云容器镜像 ACR 测评团限时招募中!

阿里巴巴中间件

阿里云 云原生 容器镜像

小白必看,JSBridge 初探_语言 & 开发_政采云前端团队_InfoQ精选文章