当前位置: 首页 > Web前端 > HTML5

H5与IOS、Android、RN的通信与回调

时间:2023-04-05 01:40:38 HTML5

混合应用模型中,原生客户端通常通过webView容器打开外部链接,通过BridgeRights向H5提供客户端方法获取设备硬件控制,如摄像头、音视频、定位、屏幕亮度等。这里详细介绍H5与Android、IOS、react-native的通信实现细节。一、webViewIOS容器概述在IOS客户端中,我们首先要提到的是一个名为UIWebView的容器。Apple对他的介绍是:UIWebView是一个可以加载网页的对象。Web内容是可编程的。说白了,UIWebView有一个类似浏览器的功能。我们可以用它来打开页面,做一些自定义的功能,比如让js调用某个方法获取手机的GPS信息。但需要注意的是,Safari浏览器使用的浏览器控件与UIwebView组件并不相同,两者在性能上存在较大差距。幸运的是,Apple在发布iOS8时,新增了一个WKWebView组件容器。如果你的APP只考虑支持iOS8及以上版本,那么可以使用这个新的浏览器控件。WKWebView重构了原有UIWebView的14个类和3个协议。在提升性能的同时,也为开发者提供了更详细的配置(这些配置仅针对客户端IOS开发,对于前端H5,维护了两种容器调用方式,方法的一致性很重要)。Android容器在Android客户端中,webView容器与手机自带的浏览器内核一致,多为android-chrome。没有兼容性和性能问题。在react-native的发展中,RN容器从rn0.37版本开始正式引入了组件,在Android中调用原生浏览器,在IOS中默认调用UIWebView容器。从IOS12开始,苹果正式放弃UIWebView,统一采用WKWebView。RN从0.57开始,可以指定使用WKWebView作为WebView的实现//rnjs代码不要在原生点击组件中嵌入WebView组件Set,会导致H5页面滚动失败。2.通信原理H5调用IOS客户端方法Javascript调用Native。没有现成的API可以直接使用,只能通过一些方法间接实现。UIWebView和WKWebView有一个特点:所有在H5中发起的网络请求,都可以通过拦截通知到Native层。这样我们就可以在容器中发起一个自定义协议的网络请求,并带上参数,通常是这样的格式:jsbridge://methodName?param1=value1¶m2=value2在客户端代码中,只要拦截层发现该协议为jsbridge,请求不会透传到网络请求层,直接处理相应的业务调用逻辑。在H5中有两种方式可以发起这个特定的协议请求:通过localtion.href;2.通过iframe;通过location.href。Native层只能接收最后一个请求,之前的请求会被忽略。使用iframe方式,以NativeAPP的Share组件为例,简单实现如下://h5jscodevarurl='jsbridge://getShare?title=Sharetitle&desc=Sharedescription&link=http%3A%2F%2Fwww.douyu.com&cbName=jsCallClientBack';variframe=document.createElement('iframe');iframe.style.width='1px';iframe.style.height='1px';iframe.style.display='none';iframe.src=url;document.body.appendChild(iframe);setTimeout(function(){iframe.remove();},100);然后Webview可以拦截这个请求并解析出相应的方法和参数://IOSswift代码funcwebView(webView:UIWebView,shouldStartLoadWithRequestrequest:NSURLRequest,navigationType:UIWebViewNavigationType)->Bool{leturl=request.URLletscheme=url?.schemeletmethod=url?.hostletquery=url?.查询ifurl!=nil&&scheme=="jsbridge"{切换方法!{case"getShare":self.getShare()default:print("default")}returnfalse}else{returntrue}}这里我们在请求参数中加入cbName=jsCallClientBack,这个jsCallClientBack是JS调用客户端定义的回调函数,在业务层的jsBridge包中,我们传入一个匿名函数作为回调,底层在window的jsbridge对象下绑定了这个函数,并为它定义了一个唯一的key。这个键是jsCallClientBack。客户端处理完逻辑后,会通过上面介绍的方法回调窗口下的方法。在window下绑定回调时要特别注意使用bind保持函数中this的原点不变。IOS客户端调用H5方法。Native调用Javascript语言,通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法实现。该方法返回js脚本的执行结果。//IOSswift代码webview.stringByEvaluatingJavaScriptFromString("window.methodName()")从上面的代码可以看出,它实际上执行的是一段字符串化的js代码,调用了window下的一个对象。如果我们想让native调用我们js写的方法,那么这个方法必须在window下是可以访问的。但是从全局来看,我们只需要暴露一个对象比如JSBridge,就可以进行native调用。调用客户端native方法的回调函数也会绑定到window下,以便客户端成功反向调用。实际上调用一次客户端方法最终的结果是双向的相互调用。H5调用Android客户端方法。AndroidwebView中调用native有3种方式:通过schema方法,客户端使用shouldOverrideUrlLoading方法解析url请求协议。这个js的调用方式和ios一样,使用iframe调用native方法。通过直接在webview页面注入原生js代码,使用addJavascriptInterface方法来实现。//androidJAVA代码classJSInterface{@JavascriptInterfacepublicStringgetShare(){//...return"share";}}webView.addJSInterface(newJSInterface(),"AndroidNativeApi");上面的代码是在页面的window对象中注入AndroidNativeApi对象。在js中可以直接调用native方法。使用prompt、console.log和alert方法。这三个方法是js原生的。这三个方法可以在androidwebview层重写。一般我们使用prompt,因为在js中用的不多,用来和native通信,副作用小。//androidJAVAcodeclassWebChromeClientextendsWebChromeClient{@OverridepublicbooleanonJsPrompt(WebViewview,Stringurl,Stringmessage,StringdefaultValue,JsPromptResultresult){//重写窗口下的提示,通过result返回结果}@OverridepublicbooleanonConsoleMessage(ConsoleMessageconsoleMessage){}@OverridepublicbooleanonJsAlert(WebViewview,Stringurl,Stringmessage,JsResultresult){}}一般来说,Android客户端采用1和2方案中的一种进行通信。从前端层面,建议所有客户端都使用schema协议,方便前端jsBridge底层代码的维护和迭代。Android客户端调用H5方法在AndroidAPP中,客户端通过webview的loadUrl调用://androidJAVA代码webView.loadUrl("javascript:window.jsBridge.getShare()");H5端绑定方法到window下面的对象就够了,不用和IOS区分开来。H5调用RN客户端。我们知道RN的webView组件其实是对原生容器的二次封装,所以我们不需要直接通过schema协议进行通信,只需要使用浏览器的postMessage,onMessage来传递消息,类似于iframe,真正的通信过程RN已经为我们做好了。//h5jscodewindow.postMessage(data);//rnjs代码{let{data}=e.nativeEvent;//...}}/>RN客户端调用H5postMessage是双向的,所以也可以在RN中发送消息,在H5中接收消息触发相应的回调this.refs.webView.postMessage({cbName:'xxx',参数:{}});3、前端jsBridge的封装在了解js和客户端底层通信原理之后,我们可以将IOS和Android统一成jsBridge,提供给业务层开发调用。classJsBridge{staticlastCallTimeconstructor(){if(UA.isReactNative()){document.addEventListener('message',function(e){window.jsClientCallBack[e.data.cbName](e.data.param);});}}//通用callNtive方法callClient(functionName,data,callback){//避免连续调用if(JsBridge.lastCallTime&&(Date.now()-JsBridge.lastCallTime)<100){setTimeout(()=>{this.callClient(functionName,data,callback);},100);返回;}JsBridge.lastCallTime=Date.now();数据=数据||{};如果(回调){constcbName=randomName();Object.assign(数据,{cbName});window.jsClientCallBack[cbName]=callBack.bind(this);}if(UA.isIOS()){data.forEach((key,value)=>{try{data[key]=JSON.stringify(值);}catch(e){}});varurl='jsbridge://'+functionName+'?'解析JSON(数据);variframe=document.createElement('iframe');iframe.style.width='1px';iframe.style.height='1px';iframe.style.display='无';iframe.src=url;document.body.appendChild(iframe);setTimeout(()=>{iframe.remove();},100);}elseif(UA.isAndroid()){//这里Android客户端使用上面提到的第二种通信方式window.AndroidNativeApi&&window.AndroidNativeApi[functionName]&&window.AndroidNativeApi[functionName](JSON.stringify(data));}elseif(UA.isReactNative()){//rn的组件可以设置props.userAgent让H5识别window.postMessage(JSON.stringify(data););}else{console.error('获取平台信息失败,调用api失败');}}//业务层自定义方法getShare(data,callBack){//..}}在核心封装的基础上,我们可以做更多的优化,比如调用每个回调函数后自毁,释放内存4.调试Android使用chrome://inspect调试,需要翻墙IOS使用macsafari的develop选项调试使用RN的http://localhost:8081/debugger-ui只能调试RN代码,不能调试webView代码,RN下的webView调试和对应native一样,但是在chrome://inspect下会有样式问题除非是纯RN写的,直接打包进APP,不建议调用webView组件在注册护士下