摘要:随着移动互联网应用大量投入市场,每天都有很多各行各业应用产品被开发出来,这就要求应用的开发效率被不断提升。由于现有流行的原生客户端IOS和Android开发周期较长,平台一致性有差异,双端开发人效高。那么可能会用到Web相关技术(HTML5、JavaScript、CSS3)作为替代原生开发的一种方案,此时会涉及原生应用和网页之间的交互和通信,而通信原理就显得尤为重要。原生的交互和通信一般会有比如设置顶部导航栏标题,播放声音、拍摄照片、地理定位、声音和传感器等功能。该文通过分析当前原生和H5之间多种通信方式的优缺点以及目前主流的跨端方案,给出一个较为合理的混合开发通信交互方案。
关键词:移动互联网;混合开发(Hybird)通信; schema协议;React Native
中图分类号:TP311 文獻标识码:A
文章编号:1009-3044(2022)19-0040-02
最早的混合开发解决方案要追溯到Apache Cordova[1](PhoneGap)时代,当时最主要解决的问题就是让开发者们可以在网页中能够调用到IOS、Android等智能手机的核心功能,例如地理定位、联系人、拍摄照片等。那么必然就要和原生Native通信和交互,而通信和交互又是混合开发中比较重要的、比较偏基础底层实现的一个环节。一个具有高可用性,高性能的通信过程封装库可以在实际的业务开发或项目实践中很好地避免问题的发生。那么高频通信且偏底层的方案必然就存在一定约定规范,所以一个标准的、规范化、通用的跨语言规范或者协议就尤为重要。本文就针对以上提出的问题先进行基于两端Android和IOS基础底层的通信方法的探索和梳理,同时给出一个较为合理的规范协议通信和交互的标准。
1 原生Android和网页(HTML)之间的通信方式
在Android中如果希望提供Web应用或者网页功能,通常是使用WebView[2]执行该操作。因为WebView类是Anrdoid的View类的扩展。所以通信就是Android里(Kotlin或Java)的代码和WebView中的JavaScript代码进行通信。那么通常会存在Android代码调用JavaScript代码或者JavaScript代码调用Android代码。
1.1 Android代码调用JavaScript代码
在Activity里OnCreate()方法中向应用添加WebView实例,然后使用loadUrl方法加载网页或者通过loadData方法去加载HTML字符串。这里可以理解成webview就是浏览器,Android可以像浏览器控制台或eval一样执行前端的JS代码。
1.2 JavaScript代码调用Android代码
常见有两种方式,方式一是在使用WebView时,可以在JavaScript代码和客户端Android代码之间创建新接口,该接口的实现就是Android代码侧调用addJavascriptInterface(),并传入类实例以绑定到JavaScript以及JavaScript可调用以访问类的接口名称。然后JavaScript侧就可以直接调用接口名称下的实例方法了。方式二是通过拦截网页的URL地址实现的通信,比如可以重载WebViewClient的shouldOverrideUrlLoading()方法,来判读当前请求的URL是否需要做特殊处理逻辑。注意这里的URL不仅限于网页也可以是各种静态资源文件比如图片、音频等。
2 原生IOS和网页(HTML)之间的通信方式
在IOS中承载网页的有UIWebView[3]和WKWebView[4],UIWebView目前已废弃,该文暂不做阐述,以下的webview均指WKWebView。WKWebView是IOS8才出现的。WKWebView和JavaScript代码的通信过程方式和Android的WebView基本一致,这里省略过程图。
2.1 IOS代码调用JavaScript代码
主要用到WKWebView的方法evaluateJavaScript[5],有三个重载方法,比较常用三个参数同时带返回值的。具体是用到了wkwebview中的evaluateJavaScript方法。
2.2 JavaScript代码调用IOS代码
IOS也有两种方式,方式之一也是拦截URL地址请求,然后做不同处理来进行通信,核心实现是基于对WKNavigationDelegate中的WKNavigationAction的实现。方式二是利用基于webkit内核的浏览器的JavaScriptCore去做IOS代码和JavaScript代码之间接口实现,因为JavaScriptCore[6]有JSVirtualMachine、JSContext和JSValue,这里主要是利用JSContext创建一个JavaScript代码执行环境然后利用JSVirtualMachine提供的能执行JavaScript代码的虚拟机,然后执行处理JSValue具体的值。
3 通信会遇到的问题以及解决方案思路
通过以上通信方式介绍,1.2中的方式一和2.2中的方式二显然对于双端(Native和HTML)都有比较强深的侵入性,不利于工程化结构。而作为浏览器网页的基础能力,对于URL的拦截和直接执行JavaScript代码显然简单,高效。但实际会遇到很多问题。比如双方通信的数据结构怎么定义,有回执的消息如何实现,cookie状态如何同步,安全漏洞[2]等。
3.1通信数据结构如何定义
探讨以URL为基础,定义协议规则比如:schema://host/path?query其中schema定义插件名字或APP协议,客户端仅接收内置于代码中的schema,比如fb。这样范围可控。host用来区分模块比如订单模块、用户模块等含义。path用来定义模块里的子项比如订单的详情、订单的列表。query子项携带的额外查询参数,比如id。完整的结构定义例子如:fabcd://order/detail?id=1234。
3.2 Cookie如何同步状态到HTML中
常用的是CookieManager,其作用主要有两点,一是cookie可以实现同步到WebView或WKWebView中;二是可以请求时携带cookie信息。
3.3通信消息回执如何实现
核心原理是传递一个全局唯一的随机数作为双方接口的方法名字,JavaScript提前注册该具名函数,以字符串形式传递到Native侧,在适当的时机执行该具名函数。这种模拟全双工通信,需要考虑消息时序[3],构建消息队列,考虑限流、节流、幂等发送消息。
3.4页面栈问题考虑
页面栈会有诸多问题,比如离线时导航栏如何显示HTML的title文本,是否可以在网络错误时不显示白屏网页,点击重试按钮就可以重试下载离线HTML资源,HTML页面间如何实现传参,如何实现自定义是否返回等。基于以上问题,方案一是禁止HTML里的跳转,统一走Native单独创建WebView实例进行跳转,优势是标准统一都有Native去控制,HTML不用去关心返回或者关闭,缺陷是硬件开销大。方案二是用复杂的时序来确定由H5处理返回还是NA处理返回。具体是,当用户点击了返回按钮而非关闭按钮时,NA调用JS的onBackClick。然后JS自己这边依照业务逻辑或者自己的页面栈是否为根来决定自己要如何处理。若需要关闭H5页面,则调用NA close,若调用自行处理,则调用自己的history.back。并且调用NA的方法已处理的回调。若NA长时间未收到回调,则自动关闭页面。
4 目前流行的混合通信相关方案分析
4.1 React-Native
我们先从官网给出的定义开始了解,React-Native[7]是一个使用React和应用平台的原生功能来构建Android和IOS应用的开源框架。也就是可以使用JavaScript代码(react代码)访问React Native的模块,进而最终调用原生模块。这个我们上边提到的JS代码执行NA的代码,也就是1.2中的android利用addJavascriptInterface和ios利用JavaScriptCore来实现直接让JS代码调用到原生的方法会不会一样呢,接下来就来对此分析一下。
4.1.1 Android端通信模型
首先Android的语言大多是Java,当然还有kotlin,这里先基于Java。React-Native在Native端的框架实现就是用的Java语言,那么本质上是Java和JavaScript两种语言程序之间的调用。那么其实上述已经说到了Webview之间的通信方式,但是React-Native和Webview没啥关系,所以这里猜测可能是利用的是so库,因为react-native必然依赖于webkit解析,而解析完了要和Java通信必然会找到一个中间的产物,也就是更往底层走,也就是C/C++做的一个bridge。
4.1.2 IOS端的通信模型
IOS和React-Native通信其实大致与Android相同,都是依赖于C/C++编写的Bridge,不过React-Native最新的架构是依赖于JSI,JSI核心要做的事就是对JS引擎与Native(C++)之间相互调用的封装。下面简单阐述下通信过程,首先js侧会直接去调用已经内置好的NativeModules或者三方的NativeModules,那么就会进入一个MessageQueue队列中,这个队列中存放是JS调用NativeModules方法的队列,这里是异步通信的,因为JS本身是单线程,如果同步执行,必然会阻塞后续JS执行。这里的异步执行时机也很重要,一般JS不会主动传递数据到OC,在调用OC方法时,会把ModuleID,MethodID等数据添加到一个队列里,等OC过来调用JS的任意方法时,再把这个队列返回给OC,此时OC再执行这个队列里要调用的方法。这样的好处就是保证了JS侧和Native侧的事件和逻辑同步,同时JS也可以顺便做call native,就避免JS和Native之间的频繁通信了。
5 通信整体解决方案以及架构图
通过从最传统的Aphche Cordova混合开发方式、自主探索混合开发通信,然后再到主流的跨端方案[4-5]探究其通信方案,探索出传统混合开发通信[6]经典方法是Native端会对URL进行拦截,但这里的URL最好是满足协议规范的schema规范,有host、子模块、query、同时还有约定的消息格式加解密、cookie的管理、特定的消息时序等等,解析后处理完毕后不管处理成功还是失败,都会执行对应回调方法通知前端JS侧,而JS侧会提前被注入一个对象[7],此对象包含了一些NA的方法,有需要时就调用NA的方法并传入对应的协议scheme和与之关联的参数,比如处理完的回调等;整体架构图如图1所示:
6 总结
本文重点先是阐述了早期的Android和IOS与HTML网页之间通信的基本原理,知道基本原理再看现如今的跨端方案, 有很多实现通信方式也是基于基本的原理不断进行演化而来,因为基本的跨语言通信方式,要么跨进程间通信,要么就通过更底层的语言(C/C++)实现中间语言传输通信交流。然而本身自主探索通信交互方案就会遇到一些世界要考虑的问题,比如协議制定、页面栈管理、cookie状态同步,消息时序等本文结合实践经验给出一些实践经验结论供参考,同时整理出理想的架构方案图。按照此基地接入至少能保证协议可维护、可扩展。
参考文献:
[1] 王亚飞.Apache Cordova移动应用开发实战/跨平台移动开发丛书[M].北京:清华大学出版社,2017.
[2] 柯芬芬.跨平台移动应用开发技术的安全性研究[J].无线互联科技,2020,17(5):152-153.
[3] 邹琼俊.H5+跨平台移动应用实战开发[M].北京:北京航空航天大学出版社,2019.
[4] 向治洪.React Native移动开发实战2版[M].北京:人民邮电出版社,2020.
[5] 邱鹏源.React Native精解与实战[M].北京:机械工业出版社,2018.
[6] 张静,薛茹.基于JSBridge技术的跨平台移动应用开发研究[J].信息与电脑(理论版),2021,33(6):100-102.
[7] 邵增光.一种插件化JSBridge的实现方法:CN108804082A[P].2018-11-13.
收稿日期:2021-10-11
作者简介:岳奎(1989—),男,陕西汉中人,本科,高级前端工程师,主要研究方向为大前端。