demo地址
在 WKWebView 的秒开计划中,运用 WKURLSchemeTask 阻拦网络恳求已经成为现在的主流方式,本文首要介绍一下该计划中需求留意的几个当地:运用版别约束、怎么阻拦一切 http 和 https 恳求、This task has already been stopped 问题处理、大视频文件播映白屏时刻长问题处理、重定向导致的白屏处理。
如果还有其他更好的处理计划和文章里没提到的坑,非常非常非常欢迎留言!
效果图:
一、运用版别约束
本计划不支持 iOS 13.0 13.1 13.2 13.3 这四个版别,会导致溃散,我暂时没找到解决的办法,只能避开这四个版别,如果有解决的计划欢迎留言辅导。
溃散:
demo处理:
//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处理:
@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处理:
///记载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处理:
#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。
阻拦到重定向
- (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 处理重定向
- (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署理里处理重定向
- (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地址