热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

H5如何与原生App(ios,安卓,RN)通信?

关注前端公众号前端每日一博,每日为你分享一篇前端系列博文。永不断更!前言为了提高开发效率,开发人员往往会使用原生app里面嵌套前端h5页面

关注前端公众号 前端每日一博,每日为你分享一篇前端系列博文。永不断更!

前言

为了提高开发效率,开发人员往往会使用原生app里面嵌套前端h5页面的快速开发方式,这就要涉及到h5和原生的相互调用,互相传递数据,接下来就实践项目中的交互方式做一个简单的记录分享,废话不多说,直接上正文:

由于安卓和ios的处理方式不一样,所以我们要分开处理先贴上判断访问终端的代码


//判断访问终端
function browserVersion(){var u = navigator.userAgent;return {trident: u.indexOf('Trident') > -1, //IE内核presto: u.indexOf('Presto') > -1, //opera内核webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器iPad: u.indexOf('iPad') > -1, //是否iPadwebApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)qq: u.match(/\sQQ/i) == " qq" //是否QQ};

通信原理之先了解webview

IOS容器在IOS客户端中,我们首先要提到的是一个叫UIWebView的容器,苹果对他的介绍是:

UIWebView是一个可加载网页的对象,它有浏览记录功能,且对加载的网页内容是可编程的。说白了UIWebView有类似浏览器的功能,我们使用可以它来打开页面,并做一些定制化的功能,如可以让js调某个方法可以取到手机的GPS信息。

但需要注意的是,Safari浏览器使用的浏览器控件和UIwebView组件并不是同一个,两者在性能上有很大的差距。幸运的是,苹果发布iOS8的时候,新增了一个WKWebView组件容器,如果你的APP只考虑支持iOS8及以上版本,那么你就可以使用这个新的浏览器控件了。

WKWebView重构了原有UIWebView的14个类,3个协议,性能提升的同时,赋予了开发者更加细致的配置(这些配置仅针对客户端IOS开发,对于前端H5来说,保持两种容器调用方法的一致性很重要)。

Android容器在安卓客户端中,webView容器与手机自带的浏览器内核一致,多为android-chrome。不存在兼容性和性能问题。

RN容器在react-native开发中,从rn 0.37版本开始官方引入了组件,在安卓中调用原生浏览器,在IOS中默认调用的是UIWebView容器。从IOS12开始,苹果正式弃用UIWebView,统一采用WKWebView。

RN从0.57起,可指定使用WKWebView作为WebView的实现

// rn js code

WebView组件不要嵌套在或原生点击组件中,会造成H5内页面滚动失效

h5向ios客户端发送消息;

在ios中,并没有现成的api让js去调用native的方法,但是UIWebView与WKWebView能够拦截h5内发起的所有网络请求。所以我们的思路就是通过在h5内发起约定好的特定协议的网络请求,如'jsbridge://bridge2.native?params=' + encodeURIComponent(obj)然后带上你要传递给ios的参数;然后在客户端内拦截到指定协议头的请求之后就阻止该请求并解析url上的参数,执行相应逻辑

在H5中发起这种特定协议的请求方式分两种:

  1. 通过localtion.href;

通过location.href有个问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。

  1. 通过iframe方式;

使用iframe方式,以唤起Native;以唤起分享组件为例

// h5 js code 将它封装一下function createIframe(url){var url = 'jsbridge://getShare?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.douyu.com&cbName=jsCallClientBack';var iframe = document.createElement('iframe');iframe.style.width = '1px';iframe.style.height = '1px';iframe.style.display = 'none';iframe.src = https://segmentfault.com/a/url;document.body.appendChild(iframe);setTimeout(function() {iframe.remove();}, 100);}

然后客户端通过拦截这个请求,并且解析出相应的方法和参数:这里以ios为例:

// IOS swift codefunc webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {let url = request.URLlet scheme = url?.schemelet method = url?.hostlet query = url?.queryif url != nil && scheme == "jsbridge" {switch method! {case "getShare":self.getShare()default:print("default")}return false} else {return true}}

看不懂就略过,非重点。。。。。

这里我们在请求参数中加上了cbName=jsCallClientBack,这个jsCallClientBack为JS调用客户端所定义的回调函数,在业务层jsBridge封装中,我们传入一个匿名函数作为回调,底层将这个函数绑定在window的jsbridge对象下并为其定义一个独一无二的key,这个key就是jsCallClientBack,客户端在处理完逻辑后,会通过上面已经介绍过的方法来回调window下的方法。

ps: 在将回调绑定在window下时,特别注意要使用bind保持函数内this的原有指向不变

IOS客户端调用H5方法

Native调用Javascript语言,是通过UIWebView组件的stringByEvaluatingJavascriptFromString方法来实现的,该方法返回js脚本的执行结果。

// IOS swift codewebview.stringByEvaluatingJavascriptFromString("window.methodName()")

从上面代码可以看出它其实就是执行了一个字符串化的js代码,调用了window下的一个对象,如果我们要让native来调用我们js写的方法,那这个方法就要在window下能访问到。但从全局考虑,我们只要暴露一个对象如JSBridge给native调用就好了。

调用客户端原生方法的回调函数也将绑在window下供客户端成功反调用,实际上一次调用客户端方法最后产生的结果是双向互相调用。

H5调用Android客户端方法

在安卓webView中有三种调用native的方式:

通过schema方式,客户端使用shouldOverrideUrlLoading方法对url请求协议进行解析。这种js的调用方式与ios的一样,使用iframe来调用native方法。通过在webview页面里直接注入原生js代码方式,使用addJavascriptInterface方法来实现。

// android JAVA codeclass JSInterface {@JavascriptInterfacepublic String getShare() {//...return "share";}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidNativeApi");

上面的代码就是在页面的window对象里注入了AndroidNativeApi对象。在js里可以直接调用原生方法。

使用prompt,console.log,alert方式,这三个方法对js里是属性原生的,在android webview这一层是可以重写这三个方法的。一般我们使用prompt,因为这个在js里使用的不多,用来和native通讯副作用比较少。

// android JAVA code
class WebChromeClient extends WebChromeClient {@Overridepublic boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {// 重写window下的prompt,通过result返回结果}@Overridepublic boolean onConsoleMessage(ConsoleMessage consoleMessage) {}@Overridepublic boolean onJsAlert(WebView view, String url, String message, JsResult result) {}} 

一般而言安卓客户端选用1、2方案中的一种进行通信,从前端层面来讲,推荐客户端都使用schema协议的方式,便于前端jsBridge底层代码的维护与迭代。

Android客户端调用H5方法

在安卓APP中,客户端通过webviewloadUrl进行调用:

// android JAVA codewebView.loadUrl("Javascript:window.jsBridge.getShare()");

H5端将方法绑定在window下的对象即可,无需与IOS作区分

H5调用RN客户端

我们知道RN的webView组件实际上就是对原生容器的二次封装,因此我们不需要直接通过schema协议来通信,只需要使用浏览器postMessage、onMessage来传递消息即可,类似于iframe,而真正的通信过程RN已经帮我们做了。

// h5 js code
window.postMessage(data);
// rn js code
 {let { data } = e.nativeEvent;//...}}/>

RN客户端调用H5

postMessage是双向的,所以也可以在RN里发消息,H5里接消息来触发对应的回调

this.refs.webView.postMessage({cbName: 'xxx',param: {}
}); 

前端jsBridge的封装

在了解了js与客户端底层的通信原理后,我们可以将IOS、安卓统一封装成jsBridge提供给业务层开发调用。

class JsBridge {static lastCallTimeconstructor() {if (UA.isReactNative()) {document.addEventListener(&#39;message&#39;, function(e) {window.jsClientCallBack[e.data.cbName](e.data.param);});}}// 通用callNtive方法callClient(functionName, data, callback) {// 避免连续调用if (JsBridge.lastCallTime && (Date.now() - JsBridge.lastCallTime) < 100) {setTimeout(() &#61;> {this.callClient(functionName, data, callback);}, 100);return;}JsBridge.lastCallTime &#61; Date.now();data &#61; data || {};if (callback) {const cbName &#61; randomName();Object.assign(data, { cbName });window.jsClientCallBack[cbName] &#61; callBack.bind(this);}if (UA.isIOS()) {data.forEach((key, value) &#61;> {try {data[key] &#61; JSON.stringify(value);} catch(e) { }});var url &#61; &#39;jsbridge://&#39; &#43; functionName &#43; &#39;?&#39; parseJSON(data);var iframe &#61; document.createElement(&#39;iframe&#39;);iframe.style.width &#61; &#39;1px&#39;;iframe.style.height &#61; &#39;1px&#39;;iframe.style.display &#61; &#39;none&#39;;iframe.src &#61; url;document.body.appendChild(iframe);setTimeout(() &#61;> {iframe.remove();}, 100);} else if (UA.isAndroid()) {    //  这里安卓客户端使用的是上面说的第二种通信方法window.AndroidNativeApi && window.AndroidNativeApi[functionName] && window.AndroidNativeApi[functionName](JSON.stringify(data));} else if (UA.isReactNative()) {     //rn的组件可以设置props.userAgent来让H5识别window.postMessage(JSON.stringify(data););} else {console.error(&#39;未获取platform信息&#xff0c;调取api失败&#39;);}}// 业务层自定义方法getShare(data, callBack) {//..}
}

在核心封装的基础上&#xff0c;我们可以还做更多的优化&#xff0c;比如将每个回调函数调用后自我销毁释放内存

四、调试

安卓使用chrome://inspect进行调试&#xff0c;需要翻墙 IOS使用mac safari的develop选项进行调试 使用RN的http://localhost:8081/debugger-ui 只能调试RN代码&#xff0c;无法调试webView代码&#xff0c;RN下webView调试和对应native相同&#xff0c;但是在chrome://inspect下会出现样式问题。除非是纯RN编写&#xff0c;直接打包成APP&#xff0c;否则不建议在RN下调用webView组


推荐阅读
  • Unity3D引擎的体系结构和功能详解
    本文详细介绍了Unity3D引擎的体系结构和功能。Unity3D是一个屡获殊荣的工具,用于创建交互式3D应用程序。它由游戏引擎和编辑器组成,支持C#、Boo和JavaScript脚本编程。该引擎涵盖了声音、图形、物理和网络功能等主题。Unity编辑器具有多语言脚本编辑器和预制装配系统等特点。本文还介绍了Unity的许可证情况。Unity基本功能有限的免费,适用于PC、MAC和Web开发。其他平台或完整的功能集需要购买许可证。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 本文介绍了iOS开发中检测和解决内存泄漏的方法,包括静态分析、使用instruments检查内存泄漏以及代码测试等。同时还介绍了最能挣钱的行业,包括互联网行业、娱乐行业、教育行业、智能行业和老年服务行业,并提供了选行业的技巧。 ... [详细]
  • 项目需要实现弹幕,网上参考了各种方法,最后觉得transform+transition实现的效果在移动设备上性能最好,在iphone6和红米4上测试,看不到 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 本文介绍了一种图片处理应用,通过固定容器来实现缩略图的功能。该方法可以实现等比例缩略、扩容填充和裁剪等操作。详细的实现步骤和代码示例在正文中给出。 ... [详细]
  • 我一直都有记录信息的习惯,不知是从什么时候开始,大约是在工作后不久。如今还真有点庆幸从那时开始记了点东西,当然是电子版的,写 ... [详细]
author-avatar
凯锐斯_372
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有