WKWebView 拦截网络请求实现秒开方案和实践(附demo)

demo地址

在 WKWebView 的秒开方案中,使用 WKURLSchemeTask 拦截网络请求已经成为现在的主流方式,本文主要介绍一下该方案中需要注意的几个地方:使用版本限制、如何拦截所有 http 和 https 请求、This task has already been stopped 问题处理、大视频文件播放白屏时间长问题处理、重定向导致的白屏处理。

如果还有其他更好的处理方案和文章里没提到的坑,非常非常非常欢迎留言!

效果图:

一、使用版本限制

本方案不支持 iOS 13.0 13.1 13.2 13.3 这四个版本,会导致崩溃,我暂时没找到解决的办法,只能避开这四个版本,如果有解决的方案欢迎留言指导。

崩溃:

demo处理:

ini 复制代码
//iOS 13.0 13.1 13.2 13.3 这四个版本不要使用离线包, 会导致崩溃
NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
if ([systemVersion compare:@"13.0"] == NSOrderedAscending || [systemVersion compare:@"13.3"] == NSOrderedDescending) {
    ZWKURLHandler *handler = [[ZWKURLHandler alloc] init];
    //直接拦截所有 http 和 https 的请求
    [config setURLSchemeHandler:handler forURLScheme:@"https"];
    [config setURLSchemeHandler:handler forURLScheme:@"http"];
    _urlHandler = handler;
}

二、如何拦截所有http和https请求

在 WKWebView 的分类里重写 handlesURLScheme 方法。

demo处理:

less 复制代码
@interface WKWebView (HandlesURLScheme)
@end
@implementation WKWebView (HandlesURLScheme)
#pragma mark 这里不返回 NO 的话, WKWebViewConfiguration 里 setURLSchemeHandler 就会崩溃
+ (BOOL)handlesURLScheme:(NSString *)urlScheme {
    return NO;
}

@end

三、This task has already been stopped 问题处理

该问题出现的原因是打开网页后快速返回,多次重复后有概率出现,而且概率不低。

处理的核心是在自己发起的网络请求回调里,把数据传给 webview 的地方加 try catch,其他非核心的处理有:控制器销毁后,在 startURLSchemeTask 里做拦截处理;当网络请求返回数据后,不要处理已经在 stopURLSchemeTask 中标记了的urlSchemeTask。

demo处理:

objectivec 复制代码
///记录urlSchemeTask是否被stop
@property (nonatomic,strong) NSMutableDictionary *holdUrlSchemeTasks;
///记录controller是否销毁,避免发起多余的请求
@property (nonatomic, assign) BOOL isControllerDealloced;

- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask {
    if (_isControllerDealloced) {
        //过滤掉多余的请求
        return;
    }
}

- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask {
    NSString *key = urlSchemeTask.request.requestId;
    if ([self.holdUrlSchemeTasks objectForKey:key]) {
        [self.holdUrlSchemeTasks removeObjectForKey:key];
    }
}

- (void)handleOnlineRequst:(NSURLRequest *)request urlSchemeTask:(__weak id <WKURLSchemeTask>)urlSchemeTask {
    //转网络请求
    NSMutableURLRequest *mutaRequest = [request mutableCopy];
    __weak typeof(self) weakSelf = self;
    NSString *requestUrlStr = request.URL.absoluteString.copy;
    
    NSURLSessionTask *task = [self.session dataTaskWithRequest:mutaRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (weakSelf.isControllerDealloced || !urlSchemeTask || ![weakSelf.holdUrlSchemeTasks objectForKey:urlSchemeTask.request.requestId]) {
            //过滤掉已经stop的网络请求
            return;
        }
        //避免 The task has already been stopped 这个断言导致的崩溃
        @try {
            if (error) {
                [urlSchemeTask didReceiveResponse:response];
                [urlSchemeTask didFailWithError:error];
            }else{
                //处理下重定向
                NSHTTPURLResponse *httpResp = [weakSelf handleRedirectUrlWithResponse:response requestUrlStr:requestUrlStr];
                [urlSchemeTask didReceiveResponse:httpResp];
                if (data) {
                    [urlSchemeTask didReceiveData:data];
                }
                [urlSchemeTask didFinish];
            }
        } @catch (NSException *exception) {
            NSLog(@"urlSchemeTask 停了停了");
        }
    }];
    
    [task resume];
    NSString *requestId = urlSchemeTask.request.requestId;
    self.holdUrlSchemeTasks[requestId] = @1;
}

四、大视频文件播放问题

该问题出现场景是 webView 播放一个很大的视频时,有时 startURLSchemeTask 传过来 request 的 Range 是整个视频的大小,如果不做处理,会白屏很长时间,或者请求超时导致视频播放异常。

处理方案:手动进行文件切片。

demo处理:

ini 复制代码
#pragma mark 大文件切片
- (NSMutableURLRequest * _Nullable)sliceDataRequest:(NSURLRequest *)request {
    NSString *range = [request.allHTTPHeaderFields objectForKey:@"Range"];
    if (range) {
        NSMutableURLRequest *mutaRequest = [request mutableCopy];
        NSString *rangeStr = [range stringByReplacingOccurrencesOfString:@"bytes=" withString:@""];
        NSArray *ranges = [rangeStr componentsSeparatedByString:@"-"];
        long long startPos = [ranges.firstObject longLongValue];
        long long endPos = [ranges.lastObject longLongValue];
        //这里切成 5M 一片
        if (endPos - startPos > 5 * 1024 *1024) {
            [mutaRequest setValue:[NSString stringWithFormat:@"bytes=%lld-%lld", startPos, MIN(endPos, startPos + 5 * 1024 *1024)] forHTTPHeaderField:@"Range"];
            return mutaRequest;
        }
    }
    return nil;
}

五、重定向导致的白屏处理

重定向处理较麻烦,需要 WKURLSchemeHandler 和 WKWebView代理共同合作,直接上 code。

拦截到重定向

objectivec 复制代码
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    NSURL *url = response.URL;
    if (url.pathExtension.length == 0 || [url.pathExtension isEqualToString:@"html"]) {
        //接收到 HTML 文档的重定向时,这里返回 nil, 那么在 dataTaskWithRequest 的回调里就能收到 code = 302 的response, 然后在 webview 里对 302 做特殊处理就行
        completionHandler(nil);
    } else {
        completionHandler(request);
    }
}

NSURLSessionTask 处理重定向

ini 复制代码
- (NSHTTPURLResponse *)handleRedirectUrlWithResponse:(NSURLResponse *)response requestUrlStr:(NSString *)requestUrlStr {
    NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
    NSInteger statusCode = httpResp.statusCode;
    NSString *newRequestUrl = httpResp.allHeaderFields[@"Location"];
    // 302 重定向
    if (statusCode >= 300 && statusCode < 400 && newRequestUrl && requestUrlStr.length > 0) {
        NSMutableDictionary *allHeaderFields = httpResp.allHeaderFields.mutableCopy;
        //重定向的时候,将原来的 url 通过 response 传给webview
        allHeaderFields[@"redirectUrl"] = requestUrlStr;
        
        httpResp = [[NSHTTPURLResponse alloc] initWithURL:httpResp.URL statusCode:httpResp.statusCode HTTPVersion:@"HTTP/1.1" headerFields:allHeaderFields];
    }
    return httpResp;
}

WKWebView代理里处理重定向

objectivec 复制代码
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
    if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)navigationResponse.response;
        NSInteger statusCode = httpResp.statusCode;
        NSString *newRequestUrl = httpResp.allHeaderFields[@"Location"];
        NSString *redirectUrl = httpResp.allHeaderFields[@"redirectUrl"];
        // 302 重定向
        if (statusCode >= 300 && statusCode < 400 && redirectUrl && newRequestUrl) {
            //记录下重定向之前的url, 不要显示错误界面
            self.redirectUrl = redirectUrl;
            //这里cancel掉, 然后直接load新的url
            decisionHandler(WKNavigationResponsePolicyCancel);
            _request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:newRequestUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
            [self.webView loadRequest:_request];
            return;
        }
    }
    decisionHandler(WKNavigationResponsePolicyAllow);
}

//不要显示错误界面
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    //如果是重定向的url,这里 return 掉, 不要显示错误界面
    if (self.redirectUrl.length > 0 && error.userInfo && [error.userInfo objectForKey:@"NSErrorFailingURLStringKey"]) {
        NSString *failingURLString = [NSString stringWithFormat:@"%@", error.userInfo[@"NSErrorFailingURLStringKey"]];
        if ([self.redirectUrl isEqualToString:failingURLString]) {
            self.redirectUrl = nil;
            return;
        }
    }
}

//页面加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    self.redirectUrl = nil;
}

内容参考:

WKWebview秒开的实践及踩坑之路

WKWebView完美网络请求拦截

再发一次demo地址:

demo地址

相关推荐
问道飞鱼2 小时前
【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现
android·ios·harmonyos·多webview互访
DanB243 小时前
html复习
javascript·microsoft·html
mascon3 小时前
U3D打包IOS的自我总结
ios
名字不要太长 像我这样就好3 小时前
【iOS】继承链
macos·ios·cocoa
karshey4 小时前
【IOS webview】IOS13不支持svelte 样式嵌套
ios
潜龙95274 小时前
第4.3节 iOS App生成追溯关系
macos·ios·cocoa
游戏开发爱好者813 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
paid槮14 小时前
HTML5如何创建容器
前端·html·html5
拾光拾趣录15 小时前
HTML行内元素与块级元素
前端·css·html
神策技术社区20 小时前
iOS 全埋点点击事件采集白皮书
大数据·ios·app