4

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 = myURL1) 通过本机代码发送两个或多个连续消息(使用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 冻结)。

4

0 回答 0