前言

在9012的最后一篇写到了在rn中安卓的webview的通信原理,而作为0202年的第一篇,继续讨论上年rn中webview通信剩下的部分。

背景:

对于webview,了解过的人都知道在ios端会存在两种类型的webview(UIWebview和WKWebview),而他们之间的区别主要如下:

  • 时间上的差异:UIwebview是在ios2之后才有,Wkwebview是在ios8之后才有;
  • UIwebview占用的内存较大,而WKwebview侧是内存占有不多,因此性能会比前者好;
  • WKwebview拥有高达60FPS滚动刷新率(滚动时每秒可达到60次渲染,一般最多64次)及内置手势;
  • WKwebview比UIwebview拥有更多H5的特性;
  • WKwebview提供了加载网页的进度属性;
  • WKwebview在app和web通信方面比UIwebview要好;
  • WKWebView没有做缓存处理,所以对网页需要缓存的加载性能要求没那么高的还是可以考虑UIWebView.

RN中IOS的webview结构:

大致流程:

在ios中,应用层会设置rn中webview的某个属性,而weview会通过webview.iso.js进行处理,如果该属性是原生底层直接拥有的,那么就直接往下传递,如果该属性不是底层所拥有的,则需要通过在webview.ios.js层处理成底层组件所拥有的属性,然后再往下传递下去,由RCTWebview或者RCTWKWebview进行处理。

例如:
source属性:webview层设置了source -----> webview.ios.js往下传递 ------> RCTWebviewManager.m能接受到该属性,然后继续往下传递 -----> RCTWebview进行处理该属性;

onLoadStart属性:webview层设置了onLoadStart ----> webview.ios.js对该属性进行处理,包装成_onLoadingStart,然后传给RCTWebviewManager ----> RCTWebviewManager.m能接受到该属性,然后继续往下传递 -----> RCTWebview进行处理该属性;

大概的流程图:

image.png

UIWebview的相关知识:

ios和js的交互
  • stringEvaluatingJavascriptFromString:该方法时可以让ios层执行js代码;

语法形式:

- (void)injectJavaScript:(NSString *)script
{
  [_webView stringByEvaluatingJavaScriptFromString:script];
}

当js层传递js字符串给ios的时候,ios就会用stringEvaluatingJavascriptFromString这个方法执行这个script。

  • shouldStartLoadWithRequest: 该方法是用来对webview中的url进行拦截,然后判断是否打开这个url链接,需要返回一个boolean值。当webview中存在一个url链接,就会触发这个方法,然后对url进行判断,如果是返回boolean,则可以打开url的链接,否则不行。

image.png

生命周期

对于uiwebview来说,一般有三个生命周期他们分别是:

  • shouldStartLoadWithRequest:页面在准备加载资源;
  • webViewDidFinishLoad:页面资源完成加载完毕;
  • didFailLoadWithError:资源加载失败;

rn中UIWebview和js之间的通信机制:

在webview之中通信最为重要,通过postMessage向rn层传递信息,rn层只需要message就行,相反也行。该实现的逻辑是在资源加载完成的时候执行的,就是在webviewDidFinishLoad
image.png

内嵌页面向rn传递信息和接受信息:

window.postMessage(data); 
document.addEventListener('message', (event) => {});

rn层向内嵌页面传递信息:

<Webview
   onMessage={event => {}}
   ref={(_ref) => {this.webview = _ref}}
   ...
/>
this.webview.postMessage(data)

细心的朋友此处应该可以发现出这里的postMessage和平时我们调用的不太一样:
一般情况下:window.postMessage(data, origin);
内嵌在webview之中:window.postMessage(data);
原因是因为在uiwebview之中重新定义了window.postMessage,原来的postmessage已经被赋予到originMessage之中,所以如果在内嵌页面中出现了postmessage(data),放在浏览器会报错,放在rn的webview就会没事。

当页面资源加载完成之后,就会触发webviewDidFinishLoad这个生命周期,在这个生命周期内,会做几件事情:

image.png

其中重新定义postmessage的js代码:

image.png

大概的意思就是将内嵌页面调用postmessage的信息存在messagequeue之中(以字符串形式放进去),然后通过定义window.location的值来传递信息给原生成,其中window.location的格式是:
'%@://%@?' + encodeURIComponent(messageQueue.shift());" ,当中的两个%@会被RCTJSNavigationScheme和kPostMessageHost代替从而形成新的url地址(不是常用的http协议的地址,是指定的特殊协议)

例如:'react-js-navigation://postMessage?' + encodeURIComponent('{"A":1}') ===> "react-js-navigation://postMessage?%7B%22A%22%3A1%7D";由于内嵌页面没有window,所以只能用document来监听webview传来的信息。

postmessage的信息如何传递给原生层:每当webview之中出现url的请求,都会触发shouldStartLoadWithRequest,此时就会由

image.png

大概就是判断url的host是否为kPostMessageHost,如果是则认为是postmessage传来的,然后执行_onMessage方法,而这个方法事由上层js定义的:
image.png

webview如何将信息传递给内嵌页面:
image.png

大概就是通过document.dispatchEvent发起一个事件,然后在内嵌页面之中由document进行监听;

下面就是整一块uiwebview的通信流程:
image.png

所以如果要js层调用原生层能力,可以通过协议请求,然后在原生对该协议进行拦截处理,从而判断是否调用原生能力。

以上就是对uiwebview的一个比较浅的概述,下一篇会继续介绍一下rn中wkwebview的通信原理。


DragonChen
285 声望15 粉丝

下一篇是:Axios源码解析。