41- WKWebView項(xiàng)目實(shí)踐分享(六)- 項(xiàng)目實(shí)踐:User Agent、跨域、白屏、重定向及其它

系列文章:

設(shè)置User Agent

User Agent百度百科釋義是。中文名為用戶代理,簡(jiǎn)稱 UA,它是一個(gè)特殊字符串頭,使得服務(wù)器能夠識(shí)別客戶使用的操作系統(tǒng)及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語(yǔ)言、瀏覽器插件等。
簡(jiǎn)單理解就是一句話:讓服務(wù)器知道C端設(shè)備的信息,白話點(diǎn)就是, 你得讓H5后臺(tái)知道打開(kāi)這個(gè)網(wǎng)頁(yè)是從你們公司app上打開(kāi)的,這對(duì)大數(shù)據(jù)和廣告統(tǒng)計(jì)非常關(guān)鍵。

User Agent

設(shè)置方式兩種,一種是iOS8.0開(kāi)始都使用NSUserDefaults。另一種是從iOS9.0開(kāi)始,使用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之前設(shè)置才有效
**2. 后期項(xiàng)目使用中的過(guò)程中,遇到了一個(gè)關(guān)于User Agent的坑, 特別注意, 設(shè)置的時(shí)候不要覆蓋手機(jī)原生User Agent, 我們要把我們自己公司的自定義User Agent字段追加到原生后邊可以。否則會(huì)發(fā)生一些意想不到的錯(cuò)誤。
具體看《42- WKWebView(6) - 補(bǔ)充: 實(shí)踐中的坑》
**

跨域問(wèn)題

跨域分成兩種:

  1. 一個(gè)是在相同請(qǐng)求協(xié)議下,host不同。比如說(shuō),在http://www.a.com/點(diǎn)擊一個(gè)按鈕跳轉(zhuǎn)到了http://www.b.com/頁(yè)面是這個(gè)就叫做跨域。
  2. 直接請(qǐng)求協(xié)議就不同,這也是跨域。比如說(shuō):http://www.a.com/https://www.a.com/

跨域?qū)kWebView有什么影響呢?基于上一篇Cookie的方案,經(jīng)過(guò)實(shí)踐發(fā)現(xiàn),在iOS11.0以下,WKWebView中HTTPS 對(duì) HTTPS、HTTP 對(duì) HTTP 的跨域是能載入的。但是沒(méi)辦法跨域用document.cookie設(shè)置cookie,也就是前一頁(yè)面document.cookie中的cookie帶不過(guò)去。 在iOS11.0以上,使用WKHttpCookieStore,從b.com頁(yè)面執(zhí)行g(shù)oBack()方法返回到上一頁(yè)a.com時(shí),a.com的request Header中額外添加設(shè)置的appver和devised兩個(gè)屬性丟失,但是Cookie還在。

跨域問(wèn)題的出現(xiàn)是因?yàn)閃ebKit框架對(duì)跨域進(jìn)行了安全性檢查限制,不允許跨域。那么怎么解決呢?我一共試驗(yàn)了兩種解決方案。

第一種方案:修復(fù)

在請(qǐng)求過(guò)程中request是readOnly的,也就是我們沒(méi)辦法在請(qǐng)求過(guò)程中把丟失的屬性在HTTPHeader中加上,繼續(xù)請(qǐng)求。 所以只能是攔截到具體URL,然后重新賦值Cookie和其它參數(shù),執(zhí)行l(wèi)oadRequest。 但是這樣在我們現(xiàn)兩個(gè)問(wèn)題行的導(dǎo)航條需求下,會(huì)出現(xiàn)兩個(gè)問(wèn)題:

  • webView.backForwardList.backList始終不會(huì)為空,導(dǎo)致如果點(diǎn)擊返回退出控制器,需要
    手動(dòng)加邏輯處理。
  • 當(dāng)在a.com/下不跳轉(zhuǎn)的情況下,對(duì)頁(yè)面進(jìn)行操作,界面變更之后。進(jìn)入b.com/然后使用此
    方法重新loadRequest鏈接a.com/,頁(yè)面恢復(fù)初始化,之前操作丟失。

第二種方案:新打開(kāi)一個(gè)webView控制器

這種方案的顧慮如果a.com/要從b.com/頁(yè)面中獲取返回?cái)?shù)據(jù),會(huì)導(dǎo)致無(wú)法拿到數(shù)據(jù)。但是從公司H5開(kāi)發(fā)小哥那里分享到經(jīng)驗(yàn),一般H5不會(huì)這么做,取數(shù)據(jù)只是在同一個(gè)頁(yè)面中。那這樣就很簡(jiǎn)單了。

和后臺(tái)約定在http://b.com鏈接后邊加上自定義標(biāo)識(shí),比如說(shuō)OPenNewVC=1。那么此時(shí)我們?cè)?code>a.com/中點(diǎn)擊某個(gè)按鈕觸發(fā)的跳轉(zhuǎn)鏈接就是http://b.com?OPenNewVC=1,然后在decidePolicyForNavigationAction方法中攔截,然后打開(kāi)新控制器。

設(shè)置重定向

在WKWebView中,網(wǎng)頁(yè)如果有重定向的行為,會(huì)直接回調(diào)didReceiveServerRedirectForProvisionalNavigation。但是在實(shí)際測(cè)試中發(fā)現(xiàn),有的網(wǎng)頁(yè)雖然進(jìn)入了這個(gè)方法,但是不需要我們手動(dòng)干預(yù),就可以重新跳轉(zhuǎn)到重定向后的頁(yè)面,你手動(dòng)干預(yù)了反而導(dǎo)致請(qǐng)求不成功。但是有的網(wǎng)頁(yè),就需要自己重新loadRequest一下才可以。

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

白屏問(wèn)題

在《騰訊Bugly: WKWebView 那些坑》的關(guān)于白屏問(wèn)題的描述是這樣的:

"WKWebView 自詡擁有更快的加載速度,更低的內(nèi)存占用,但實(shí)際上 WKWebView 是一個(gè)多進(jìn)程組件,
Network Loading 以及 UI Rendering 在其它進(jìn)程中執(zhí)行。初次適配 WKWebView 的時(shí)候,我們也驚
訝于打開(kāi) WKWebView 后,App 進(jìn)程內(nèi)存消耗反而大幅下降,但是仔細(xì)觀察會(huì)發(fā)現(xiàn),Other Process
的內(nèi)存占用會(huì)增加。在一些用 webGL 渲染的復(fù)雜頁(yè)面,使用 WKWebView 總體的內(nèi)存占用(App Pr
ocess Memory + Other Process Memory)不見(jiàn)得比 UIWebView 少很多。

在 UIWebView 上當(dāng)內(nèi)存占用太大的時(shí)候,App Process 會(huì) crash;而在 WKWebView 上當(dāng)總體的內(nèi)
存占用比較大的時(shí)候,WebContent Process 會(huì) crash,從而出現(xiàn)白屏現(xiàn)象。"

總之,就是因?yàn)槟撤N原因,Web Content Process奔潰了,從而出現(xiàn)白屏現(xiàn)象。

因?yàn)槲业捻?xiàng)目里,暫時(shí)沒(méi)有遇到這個(gè)問(wèn)題。所以大家可以先看一下騰訊的解決方案。

此處等待驗(yàn)證: 需要注意的一點(diǎn)是,我在之前的測(cè)試中,發(fā)現(xiàn)貌似上邊提到的重定向失敗也會(huì)進(jìn)入這個(gè)方法。但是

處理a標(biāo)簽和_blank

需要通過(guò)navigationAction.targetFrame判斷目標(biāo)frame是不是主frame,如果不是主frame,那么就說(shuō)明是新開(kāi)一個(gè)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調(diào)用native彈框的處理也交給我們,我們可以根據(jù)自己的需要進(jìn)行定制。注意alertView點(diǎn)擊之后需要調(diào)用一下代理方法中的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調(diào)用輸入框" 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請(qǐng)求的證書驗(yàn)證

WKWebView中提供了didReceiveAuthenticationChallenge:方法來(lái)判斷。我們可以彈個(gè)alert讓用戶選擇是否信任,也可以默認(rèn)直接設(shè)置信任。 以下的處理方式朋友分享的一個(gè),源頭可能來(lái)自《wkwebview下的https請(qǐng)求》:

/**
 https 請(qǐng)求會(huì)進(jìn)這個(gè)方法,在里面進(jìn)行https證書校驗(yàn)、白名單域名判斷等操作
 */
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    
    /*
     NSURLSessionAuthChallengeUseCredential = 0,                     使用證書
     NSURLSessionAuthChallengePerformDefaultHandling = 1,            忽略證書(默認(rèn)的處理方式)
     NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,     忽略書證, 并取消這次請(qǐng)求
     NSURLSessionAuthChallengeRejectProtectionSpace = 3,            拒絕當(dāng)前這一次, 下一次再詢問(wèn)
     */
    
    NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
    
    // 判斷服務(wù)器返回的證書類型, 是否是服務(wù)器信任
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
        
        if (secTrustRef != NULL) {// 信任是否為空
            
            SecTrustResultType result;
            
            OSErr er = SecTrustEvaluate(secTrustRef, &result);
            
            if (er != noErr) {// 是否有錯(cuò)誤信息
                
                completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil);
                return;
                
            }else{// 沒(méi)有錯(cuò)誤信息
                
                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 = @"該服務(wù)器無(wú)法驗(yàn)證";

                    NSString *message = [NSString stringWithFormat:@" 是否通過(guò)來(lái)自%@標(biāo)識(shí)為 %@證書為%@的驗(yàn)證. \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);

                    }]];

                    // 彈出權(quán)限提示框
                    [self presentViewController:alertController animated:YES completion:^{}];
                    return;
                    
                }else{// 證書受信任
                    
                    NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];
                    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
                    return;
                }
            }
            
        }else{//信任不為空
            completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
        }
    }else{//非服務(wù)器信任
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

關(guān)于在線播放視頻

需要注意mediaTypesRequiringUserActionForPlayback這個(gè)屬性設(shè)置哪些媒體資源需要用戶手動(dòng)操作一下才能播放,也就是否自動(dòng)播放。WKAudiovisualMediaTypeNone代表視頻和音頻資料都自動(dòng)播放。

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

// 是否允許HTML5頁(yè)面在線播放視頻,否則使用native播放器
webConfiguration.allowsInlineMediaPlayback = YES; 

// 是指不需要用戶操作,進(jìn)入webView頁(yè)面視頻自動(dòng)播放
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;
}

關(guān)于selectionGranularity屬性

selectionGranularity這個(gè)屬性是設(shè)置了用戶拷貝網(wǎng)頁(yè)內(nèi)容的時(shí)候的粒度。粒度可能很不好理解。我們直接找個(gè)新聞網(wǎng)頁(yè)看下設(shè)置之后的效果。當(dāng)設(shè)置為WKSelectionGranularityCharacter, 在iOS9上復(fù)制文本沒(méi)有定位光標(biāo)。
具體可以看下:


iOS

API,from:《WKWebView詳解》

屏幕旋轉(zhuǎn)

// 屏幕旋轉(zhuǎn)
wkWebView.translatesAutoresizingMaskIntoConstraints = NO;
wkWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);

NSURLProtocol

WKWebView中使用NSURLProtocol需要使用私有API,而且用了之后有兩個(gè)問(wèn)題。網(wǎng)上的一些方案可以過(guò)審,但是考慮到我們項(xiàng)目并非必要這個(gè)需求和使用之后的不確定性以及工作量。最終放棄NSURLProtocol。不過(guò)先期也了解了一下,提供大家?guī)灼诲e(cuò)的文章參考:

參考

強(qiáng)烈建議你把下邊的參考文章也快速看下,作為拓展和補(bǔ)充:

交流


希望能和大家交流技術(shù)
Blog:http://www.lilongcnc.cc


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容