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.
一般过程:
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);
};
全部流程:
总结:
终于将rn终webview的安卓和ios通信原理全部介绍了,如果还有不清晰的地方可以指出来,最后我将安卓的webview和ios的两种webview整理乘一个表格形式:汇总