iOS WKWebview 白屏檢測實現


前言

    自ios8推出wkwebview以來,極大改善了網頁加載速度及內存泄漏問題,逐漸全面取代笨重的UIWebview。盡管高性能、高刷新的WKWebview在混合開發中大放異彩表現優異,但加載網頁過程中出現異常白屏的現象卻仍然屢見不鮮,且現有的api協議處理捕捉不到這種異常case,造成用戶無用等待體驗很差。

    針對業務場景需求,實現加載白屏檢測。考慮采用字節跳動團隊提出的webview優化技術方案。在合適的加載時機對當前webview可視區域截圖,並對此快照進行像素點遍歷,如果非白屏顏色的像素點超過一定的閾值,認定其為非白屏,反之重新加載請求。

獲取快照

    ios官方提供了簡易的獲取webview快照接口,通過異步回調拿到當前可視區域的屏幕截圖。

- (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));

      其中snapshotConfiguration 參數可用於配置快照大小范圍,默認截取當前客戶端整個屏幕區域。由於可能出現導航欄成功加載而內容頁卻空白的特殊情況,導致非白屏像素點數增加對最終判定結果造成影響,考慮將其剔除。

- (void)judgeLoadingStatus:(WKWebView *)webview {
    if (@available(iOS 11.0, *)) {
        if (webView && [webView isKindOfClass:[WKWebView class]]) {

            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //狀態欄高度
            CGFloat navigationHeight =  webView.viewController.navigationController.navigationBar.frame.size.height; //導航欄高度
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, _webView.bounds.size.width, (_webView.bounds.size.height - navigationHeight - statusBarHeight)); //僅截圖檢測導航欄以下部分內容
            [_webView takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                //todo
            }];
        }
    }
}

  縮放快照

    為了提升檢測性能,考慮將快照縮放至1/5,減少像素點總數,從而加快遍歷速度。

- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}  

    縮小前后性能對比(實驗環境:iPhone11同一頁面下):

縮放前白屏檢測:

耗時20ms

縮放后白屏檢測:

耗時13ms

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這有個iOS交流群:642363427,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術!

     注意這里有個小坑。由於縮略圖的尺寸在 原圖寬高*縮放系數后可能不是整數,在布置畫布重繪時默認向上取整,這就造成畫布比實際縮略圖大(混蛋啊 摔!)。在遍歷縮略圖像素時,會將圖外畫布上的像素納入考慮范圍,導致實際白屏頁 像素占比並非100% 如圖所示。因此使用floor將其尺寸大小向下取整

遍歷快照

    遍歷快照縮略圖像素點,對白色像素(R:255 G: 255 B: 255)占比大於95%的頁面,認定其為白屏。

- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每個像素點包含r g b a 四個字節
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);

    int whiteCount = 0;
    int totalCount = 0;

    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
//            UInt8 alpha = *(pt + 3);

            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"當前像素點數:%d,白色像素點數:%d , 占比: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
}  

總結

typedef NS_ENUM(NSUInteger,webviewLoadingStatus) {

    WebViewNormalStatus = 0, //正常

    WebViewErrorStatus, //白屏

    WebViewPendStatus, //待決
};

// 判斷是否白屏
- (void)judgeLoadingStatus:(WKWebview *)webview  withBlock:(void (^)(webviewLoadingStatus status))completionBlock{
    webviewLoadingStatus __block status = WebViewPendStatus;
    if (@available(iOS 11.0, *)) {
        if (webview && [webview isKindOfClass:[WKWebView class]]) {

            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //狀態欄高度
            CGFloat navigationHeight = webview.viewController.navigationController.navigationBar.frame.size.height; //導航欄高度
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, webview.bounds.size.width, (webview.bounds.size.height - navigationHeight - statusBarHeight)); //僅截圖檢測導航欄以下部分內容
            [webview takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                if (snapshotImage) {
                    CGImageRef imageRef = snapshotImage.CGImage;
                    UIImage * scaleImage = [self scaleImage:snapshotImage];
                    BOOL isWhiteScreen = [self searchEveryPixel:scaleImage];
                    if (isWhiteScreen) {
                       status = WebViewErrorStatus;
                    }else{
                       status = WebViewNormalStatus;
                    }
                }
                if (completionBlock) {
                    completionBlock(status);
                }
            }];
        }
    }
}

// 遍歷像素點 白色像素占比大於95%認定為白屏
- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每個像素點包含r g b a 四個字節
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);

    int whiteCount = 0;
    int totalCount = 0;

    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
//            UInt8 alpha = *(pt + 3);

            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"當前像素點數:%d,白色像素點數:%d , 占比: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
}

//縮放圖片
- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}  

    僅需在合適的view生命周期內回調使用該函數方法即可檢測出頁面狀態是否白屏,且性能損耗可忽略不計。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM