webView:shouldStartLoadWithRequest:navigationType:
编辑:解决方案是在收到 JavaScript 消息时从委托方法返回 NO ,这解决了所有版本中的所有问题。
UIWebView
在 iOS 8.3 中,JavaScript 与本机(Obj-C 或 Swift)通信的行为发生了变化。
假设我想从 JS 向本机代码1发送几条消息:
window.open('myurlscheme://webview?message=1');
window.open('myurlscheme://webview?message=2');
window.open('myurlscheme://webview?message=3');
window.open('myurlscheme://webview?message=4');
我使用这个委托方法来捕获本机代码中的消息:
- (BOOL)webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if ([inRequest.URL.scheme isEqualToString:@"myurlscheme"]) {
NSLog(@"%@", inRequest.URL.absoluteString);
}
return YES;
}
我在我的 Xcode 控制台中得到以下信息:
15:19:56.387 TestJSObjCComms[55731:766108] myurlscheme://webview?meaning=0
15:19:56.404 TestJSObjCComms[55731:766108] myurlscheme://webview?meaning=1
16:20:06.300 TestJSObjCComms[57967:819448] void SendDelegateMessage(NSInvocation *): delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode
15:20:06.407 TestJSObjCComms[55731:766108] myurlscheme://webview?meaning=2
15:20:16.409 TestJSObjCComms[55731:766108] myurlscheme://webview?meaning=3
15:20:26.411 TestJSObjCComms[55731:766108] myurlscheme://webview?meaning=4
注意每条消息的时间戳。前两个是直接进来的,但是连续的消息以10 秒的间隔准确地进来。在最后三个消息到达的 30 秒内,UI 冻结!控制台报错对应主线程(UI线程)被某些代码阻塞了10秒,超过这个时间限制后被系统杀死。
我猜 UIWebView 在 iOS 8.3 上在自己的线程中执行,但我对此还不太了解。根据这个答案,UIWebView
允许 JavaScript 最多执行 10 秒。所以我猜JavaScript在某种程度上冻结了,只有当它被杀死时,消息才会传递到本机代码。
另外,我认为window.open(myURL)
效果更好的原因window.location = myURL
是因为它必须创建一个新的线程或执行环境,这允许它将其消息发送到本机代码,而不会在它们到达之前压缩先前的消息。
1我使用window.open(myUrl)
而不是window.location = myUrl
,它只允许最后一条消息到达本机代码(有关可用的不同方法的详细信息,请参见附录 1)。
编辑 1
哦,不。setTimeout
再次来到“救援”。setTimeout
如果您以大约50 毫秒的延迟将后续消息包装在 a 中,那么测试用例 3 似乎适用于 iOS 8.3、8.1 和 7.0 。
var buffer = [];
function doRequest(url) {
buffer.push(url);
if (buffer.length === 1) {
window.location = url;
}
}
function resolveRequest(request) {
var index = buffer.indexOf(request);
if (index != -1) {
buffer.splice(index, 1);
}
if (buffer.length) {
setTimeout(function() {
window.location = buffer[0];
}, 50);
}
}
但我正在寻找一种不会引入黑客攻击和延迟的永久解决方案。走这条路就像在沙柱上构建你的应用程序。
附录 1:UIWebView 的 JavaScript <-> 原生通信指南。
测试用例
window.location = myURL
1) 通过本机代码发送两个或多个连续消息(使用window.location.href = myURL
,window.location.replace(myURL)
等是等效的)。
window.open(myURL)
2) 通过或window.open(myURL, ‘_blank’)
或通过创建以下标签之一并设置其src
属性来发送两个或多个连续消息: <iframe>
, <embed>
. 请注意,以下标签不起作用:<audio>
, <video>
, <script>
, <style>
.
3) 使用请求缓冲区发送两个或多个连续消息。这确保仅在当前请求已经调用UIWebView
委托方法时才调用下一个请求webView:shouldStartLoadWithRequest:navigationType:
。
示例 JavaScript:
var buffer = [];
function doRequest(url) {
buffer.push(url);
if (buffer.length === 1) {
window.location = url;
}
}
function resolveRequest(request) {
var index = buffer.indexOf(request);
if (index != -1) {
buffer.splice(index, 1);
}
if (buffer.length) {
window.location = buffer[0];
}
}
原生 Obj-C:
- (BOOL)webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if ([inRequest.URL.scheme isEqualToString:@"myurlscheme"]) {
NSString *javaScript = [NSString stringWithFormat:@"resolveRequest('%@')", inRequest.URL.absoluteString];
[self.webView stringByEvaluatingJavaScriptFromString:javaScript];
NSLog(@"%@", inRequest.URL.absoluteString);
}
return YES;
}
测试结果(使用 iPhone 5 模拟器)
iOS 7.1
情况 1:只有最后一条消息到达。
情况 2:消息按顺序到达,每个消息之间延迟大约四分之一秒(即不算太糟糕)。
情况 3:消息按顺序到达,每条消息之间延迟大约四分之一秒(即不算太糟糕)。
iOS 8.1
案例一:同iOS 7.1
情况 2:前两个通常会立即到达,但接下来的两个会以 10 秒的间隔进入(同时,UI 冻结)。
案例3:同iOS 7.1
iOS 8.3
案例一:同iOS 7.1
案例2:同iOS 8.1
案例 3:前两个通常会立即到达,但接下来的会以 10 秒的间隔进入(同时,UI 冻结)。