0202的开篇,延续9012年最后一篇还没介绍完的剩余部分-WKWebview的通信原理。

  • WKWebview和ios的交互:

1 ) oc调用js的方法:evaluateJavaScript
该方法主要是在wkwebview之中调用js代码使用的,该方法能接受js代码以及一个回调函数(js代码执行后的结果会塞到该回调之中);在rn中的RCTWKWebview之中还会对evaluateJavascript进行封装:

- (void)evaluateJS:(NSString \*)js
           thenCall: (void (^)(NSString\*)) callback
  {
     [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError \*error) {
     if (error == nil && callback != nil) {
       callback([NSString stringWithFormat:@"%@", result]);
        }
     }];
  }

该方法大概的意思是evaluateJavascript执行完js代码后,会判断callback是否为null,如果不为null且error不存在就会执行对应的回调函数;

2 ) js调用oc的方法:
WKScriptMessageHandler:该协议类制定了js和oc之间的通信协议,js层必须满足该协议的格式,方能唤起oc的方法,而oc必须要注册制定的方法名,才能去监听js层发起的通信;一般情况下该协议是由WKUserContentController和WKScriptMessage这两个类完成。

WKUserContentController:该类有两个方面用途,第一方面就是制定js和oc通信的桥梁--js方法,另一方面时监听js层调用的方法;只有被该类注册过的js方法名,在js层调用时才会被该类所监听和触发。
1. addUserScript: 在网页端添加js代码;
2. addScriptMessageHandler:给js和oc之间的通信建立桥梁(就是方法名);

WKScriptMessage:是用来接收js层发来的信息,其中name就是js层调用时传来的方法名,body就是js传来的信息。

window.webkit.messageHandlers.<name>.postMessage(<messageBody>) : 在js层用来唤起oc的方式,其中name就是WKUserContentController注册过得方法名。

一般过程:

image.png

demo:

// js配置
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"jsCallOC"];
// WKWebView的配置
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = userContentController;
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
   NSLog(@"方法名:%@", message.name);
   NSLog(@"参数:%@", message.body);
   // 方法名
   NSString *methods = [NSString stringWithFormat:@"%@:", message.name];
   SEL selector = NSSelectorFromString(methods);
  // 调用方法
   if ([self respondsToSelector:selector]) { 
     [self performSelector:selector withObject:message.body]; 
   } else { 
     NSLog(@"未实行方法:%@", methods); 
   } 
 }
 - (void)jsCallOC:(id)body { 
  if ([body isKindOfClass:[NSDictionary class]]) {
    NSDictionary *dict = (NSDictionary *)body; 
    // oc调用js代码 
    NSString *jsStr = [NSString stringWithFormat:@"ocCallJS('%@')", [dict objectForKey:@"data"]];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable data, NSError * _Nullable error) {
    if (error) { 
    NSLog(@"错误:%@", error.localizedDescription); 
    }
  }];
 } 
}
// 点击确定按钮 function onClickButton() { 
   // 复杂数据 
   var list = [1,2,3];
   var dict = {"name":"阳君", "qq":"937447974", "data":input.value, "list":list}; alert(dict);
   // JS通知WKWebView      
   window.webkit.messageHandlers.jsCallOC.postMessage(dict);
  } 
  // WKWebView调用JS 
  function ocCallJS(params) { 
    show.innerHTML = params; 
 }
  • WKWebview的生命周期:

在rn中,并没有全部采用wkwebview的生命周期,只是采用了几个来作为rn中webview的生命周期;
a) initWithFrame:初始化的时候;
b) didFinishNavigation:页面资源加载完成时调用;
c) didFailProvisionalNavigation:页面加载失败的生命周期,此时会调用js层onError和onLoadEnd;

  • rn中WKWebview的通信原理:

step1: 注册方法,在js层调用window.webkit.message.ReactNative.postMessage时能被oc层的WKUserContentController所监听,这满足WKScriptMessageHandle协议;

// 注册 
static NSString *const MessageHanderName = @"ReactNative";
WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new]; 
wkWebViewConfig.userContentController = [WKUserContentController new];
[wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName];
// 监听 
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
 if (_onMessage != nil) { 
  NSMutableDictionary *event = [self baseEvent];
  [event addEntriesFromDictionary: @{@"data": message.body}];
  _onMessage(event); 
  } 
}

step2: 在页面资源加载完成时重新定义window.postMessage方法;

- (void) webView:(WKWebView \*)webView 
  didFinishNavigation:(WKNavigation *)navigation 
  { 
  if (_messagingEnabled) { 
   #if RCT\_DEV
   // Implementation inspired by Lodash.isNative. 
   NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))";
   [self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) {
    if (! [result isEqualToString:@"true"]) { 
     RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
     } 
   }];
  #endif 
  NSString *source = [NSString stringWithFormat: 
  @"(function() {" 
  "window.originalPostMessage = window.postMessage;" 
  "window.postMessage = function(data) {" 
  "window.webkit.messageHandlers.%@.postMessage(String(data));" 
  "};" 
  "})();", 
  MessageHanderName ];
  [self evaluateJS: source thenCall: nil];
 } 
 if (_injectedJavaScript) { 
  [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
  NSMutableDictionary *event = [self baseEvent];
  event [@"jsEvaluationValue"] = jsEvaluationValue; 
  if (self.onLoadingFinish) { 
   self.onLoadingFinish(event); 
   } 
 }]; 
} else if (_onLoadingFinish) { 
 _onLoadingFinish([self baseEvent]); 
} 
 [self setBackgroundColor: _savedBackgroundColor];
}

step3: 提供给rn层调用postmessage,实际是为了触发一个事件,让内嵌页面的document能够监听到;

- (void)postMessage:(NSString \*)message {
  NSDictionary *eventInitDict = @{@"data": message};
  NSString *source = [NSString stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", 
  RCTJSONStringify(eventInitDict, NULL) ];
  [self evaluateJS: source thenCall: nil];
}

step4: rn层webview的执行message事件;

_onMessage = (event: Event) => { 
   const {onMessage} = this.props; 
   onMessage && onMessage(event);
};

全部流程:
image.png

总结:
终于将rn终webview的安卓和ios通信原理全部介绍了,如果还有不清晰的地方可以指出来,最后我将安卓的webview和ios的两种webview整理乘一个表格形式:汇总


DragonChen
285 声望15 粉丝

下一篇是:Axios源码解析。