41- WKWebView项目实践分享(六)- 项目实践:User Agent、跨域、白屏、重定向及其它

系列文章:

设置User Agent

User Agent百度百科释义是。中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
简单理解就是一句话:让服务器知道C端设备的信息,白话点就是, 你得让H5后台知道打开这个网页是从你们公司app上打开的,这对大数据和广告统计非常关键。

User Agent

设置方式两种,一种是iOS8.0开始都使用NSUserDefaults。另一种是从iOS9.0开始,使用WKWebView提供的API:customUserAgent。

 if ([UIDevice currentDevice].systemVersion.floatValue >= 9.0) 
{
NSString *newUserAgent = @"YCWebKit";
      self.wkWebView.customUserAgent = newUserAgent;
}

 if ([UIDevice currentDevice].systemVersion.floatValue < 9.0)
{
NSString *newUserAgent = @"YCWebKit";
      [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":newUserAgent}];
      [[NSUserDefaults standardUserDefaults] synchronize];   
}

注意:
1. NSUserDefaults这种方式一定要在初始化WKWebView之前设置才有效
**2. 后期项目使用中的过程中,遇到了一个关于User Agent的坑, 特别注意, 设置的时候不要覆盖手机原生User Agent, 我们要把我们自己公司的自定义User Agent字段追加到原生后边可以。否则会发生一些意想不到的错误。
具体看《42- WKWebView(6) - 补充: 实践中的坑》
**

跨域问题

跨域分成两种:

  1. 一个是在相同请求协议下,host不同。比如说,在http://www.a.com/点击一个按钮跳转到了http://www.b.com/页面是这个就叫做跨域。
  2. 直接请求协议就不同,这也是跨域。比如说:http://www.a.com/https://www.a.com/

跨域对WkWebView有什么影响呢?基于上一篇Cookie的方案,经过实践发现,在iOS11.0以下,WKWebView中HTTPS 对 HTTPS、HTTP 对 HTTP 的跨域是能载入的。但是没办法跨域用document.cookie设置cookie,也就是前一页面document.cookie中的cookie带不过去。 在iOS11.0以上,使用WKHttpCookieStore,从b.com页面执行goBack()方法返回到上一页a.com时,a.com的request Header中额外添加设置的appver和devised两个属性丢失,但是Cookie还在。

跨域问题的出现是因为WebKit框架对跨域进行了安全性检查限制,不允许跨域。那么怎么解决呢?我一共试验了两种解决方案。

第一种方案:修复

在请求过程中request是readOnly的,也就是我们没办法在请求过程中把丢失的属性在HTTPHeader中加上,继续请求。 所以只能是拦截到具体URL,然后重新赋值Cookie和其它参数,执行loadRequest。 但是这样在我们现两个问题行的导航条需求下,会出现两个问题:

  • webView.backForwardList.backList始终不会为空,导致如果点击返回退出控制器,需要
    手动加逻辑处理。
  • 当在a.com/下不跳转的情况下,对页面进行操作,界面变更之后。进入b.com/然后使用此
    方法重新loadRequest链接a.com/,页面恢复初始化,之前操作丢失。

第二种方案:新打开一个webView控制器

这种方案的顾虑如果a.com/要从b.com/页面中获取返回数据,会导致无法拿到数据。但是从公司H5开发小哥那里分享到经验,一般H5不会这么做,取数据只是在同一个页面中。那这样就很简单了。

和后台约定在http://b.com链接后边加上自定义标识,比如说OPenNewVC=1。那么此时我们在a.com/中点击某个按钮触发的跳转链接就是http://b.com?OPenNewVC=1,然后在decidePolicyForNavigationAction方法中拦截,然后打开新控制器。

设置重定向

在WKWebView中,网页如果有重定向的行为,会直接回调didReceiveServerRedirectForProvisionalNavigation。但是在实际测试中发现,有的网页虽然进入了这个方法,但是不需要我们手动干预,就可以重新跳转到重定向后的页面,你手动干预了反而导致请求不成功。但是有的网页,就需要自己重新loadRequest一下才可以。

- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
    if (webView.isLoading
        && ![webView.URL.absoluteString containsString:@".a.com"]
        ) {
        [self loadRequestURL:webView.URL.absoluteString];
    }
}

白屏问题

在《腾讯Bugly: WKWebView 那些坑》的关于白屏问题的描述是这样的:

"WKWebView 自诩拥有更快的加载速度,更低的内存占用,但实际上 WKWebView 是一个多进程组件,
Network Loading 以及 UI Rendering 在其它进程中执行。初次适配 WKWebView 的时候,我们也惊
讶于打开 WKWebView 后,App 进程内存消耗反而大幅下降,但是仔细观察会发现,Other Process
的内存占用会增加。在一些用 webGL 渲染的复杂页面,使用 WKWebView 总体的内存占用(App Pr
ocess Memory + Other Process Memory)不见得比 UIWebView 少很多。

在 UIWebView 上当内存占用太大的时候,App Process 会 crash;而在 WKWebView 上当总体的内
存占用比较大的时候,WebContent Process 会 crash,从而出现白屏现象。"

总之,就是因为某种原因,Web Content Process奔溃了,从而出现白屏现象。

因为我的项目里,暂时没有遇到这个问题。所以大家可以先看一下腾讯的解决方案。

此处等待验证: 需要注意的一点是,我在之前的测试中,发现貌似上边提到的重定向失败也会进入这个方法。但是

处理a标签和_blank

需要通过navigationAction.targetFrame判断目标frame是不是主frame,如果不是主frame,那么就说明是新开一个tab操作。

- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    WKFrameInfo *frameInfo = navigationAction.targetFrame;
    if(frameInfo == nil || frameInfo.isMainFrame == NO){
        [webView loadRequest:[YCWebViewCookieTool fixRequest:navigationAction.request]];
    }
    return nil;
}

处理Alert弹框

WKWebView把WebView调用native弹框的处理也交给我们,我们可以根据自己的需要进行定制。注意alertView点击之后需要调用一下代理方法中的block。

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }];
    [alert addAction:action1];
    [self presentViewController:alert animated:YES completion:NULL];
    
}


- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"删除" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }];
    
    UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }];
    [alert addAction:action1];
    [alert addAction:action2];
    [self presentViewController:alert animated:YES completion:NULL];
}


- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:defaultText message:@"JS调用输入框" preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor redColor];
    }];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler([[alert.textFields lastObject] text]);
    }]];
    
    [self presentViewController:alert animated:YES completion:NULL];
}

Https请求的证书验证

WKWebView中提供了didReceiveAuthenticationChallenge:方法来判断。我们可以弹个alert让用户选择是否信任,也可以默认直接设置信任。 以下的处理方式朋友分享的一个,源头可能来自《wkwebview下的https请求》:

/**
 https 请求会进这个方法,在里面进行https证书校验、白名单域名判断等操作
 */
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    
    /*
     NSURLSessionAuthChallengeUseCredential = 0,                     使用证书
     NSURLSessionAuthChallengePerformDefaultHandling = 1,            忽略证书(默认的处理方式)
     NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,     忽略书证, 并取消这次请求
     NSURLSessionAuthChallengeRejectProtectionSpace = 3,            拒绝当前这一次, 下一次再询问
     */
    
    NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
    
    // 判断服务器返回的证书类型, 是否是服务器信任
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
        
        if (secTrustRef != NULL) {// 信任是否为空
            
            SecTrustResultType result;
            
            OSErr er = SecTrustEvaluate(secTrustRef, &result);
            
            if (er != noErr) {// 是否有错误信息
                
                completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil);
                return;
                
            }else{// 没有错误信息
                
                if (result == kSecTrustResultRecoverableTrustFailure) {// 证书不受信任
                    CFArrayRef secTrustProperties = SecTrustCopyProperties(secTrustRef);

                    NSArray *arr = CFBridgingRelease(secTrustProperties);

                    NSMutableString *errorStr = [NSMutableString string];

                    for (int i=0;i<arr.count;i++){

                        NSDictionary *dic = [arr objectAtIndex:i];

                        if (i != 0 ) {
                            [errorStr appendString:@" "];

                        }

                        [errorStr appendString:(NSString*)dic[@"value"]];

                    }

                    SecCertificateRef certRef = SecTrustGetCertificateAtIndex(secTrustRef, 0);

                    CFStringRef cfCertSummaryRef = SecCertificateCopySubjectSummary(certRef);

                    NSString *certSummary = (NSString *)CFBridgingRelease(cfCertSummaryRef);

                    NSString *title = @"该服务器无法验证";

                    NSString *message = [NSString stringWithFormat:@" 是否通过来自%@标识为 %@证书为%@的验证. \n%@" , @"我的app",webView.URL.host,certSummary, errorStr];

                    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];

                    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"

                                                                        style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {


                                                                            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);


                                                                        }]];

                    [alertController addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {

                        NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];

                        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);

                    }]];

                    // 弹出权限提示框
                    [self presentViewController:alertController animated:YES completion:^{}];
                    return;
                    
                }else{// 证书受信任
                    
                    NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];
                    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
                    return;
                }
            }
            
        }else{//信任不为空
            completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
        }
    }else{//非服务器信任
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

关于在线播放视频

需要注意mediaTypesRequiringUserActionForPlayback这个属性设置哪些媒体资源需要用户手动操作一下才能播放,也就是否自动播放。WKAudiovisualMediaTypeNone代表视频和音频资料都自动播放。

WKWebViewConfiguration * webConfiguration = [[WKWebViewConfiguration alloc]init];
WKUserContentController *contentController = [[WKUserContentController alloc] init];

// 是否允许HTML5页面在线播放视频,否则使用native播放器
webConfiguration.allowsInlineMediaPlayback = YES; 

// 是指不需要用户操作,进入webView页面视频自动播放
if (YCSystemVersionValue > 10.0) {
    webConfiguration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
}
else if (9.0 < YCSystemVersionValue && YCSystemVersionValue < 10.0) {
    webConfiguration.requiresUserActionForMediaPlayback = NO;
}
else if (8.0 < YCSystemVersionValue && YCSystemVersionValue< 9.0) {
    webConfiguration.mediaPlaybackRequiresUserAction = NO;
}

关于selectionGranularity属性

selectionGranularity这个属性是设置了用户拷贝网页内容的时候的粒度。粒度可能很不好理解。我们直接找个新闻网页看下设置之后的效果。当设置为WKSelectionGranularityCharacter, 在iOS9上复制文本没有定位光标。
具体可以看下:


iOS

API,from:《WKWebView详解》

屏幕旋转

// 屏幕旋转
wkWebView.translatesAutoresizingMaskIntoConstraints = NO;
wkWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);

NSURLProtocol

WKWebView中使用NSURLProtocol需要使用私有API,而且用了之后有两个问题。网上的一些方案可以过审,但是考虑到我们项目并非必要这个需求和使用之后的不确定性以及工作量。最终放弃NSURLProtocol。不过先期也了解了一下,提供大家几篇不错的文章参考:

参考

强烈建议你把下边的参考文章也快速看下,作为拓展和补充:

交流


希望能和大家交流技术
Blog:http://www.lilongcnc.cc


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容