1. 程式人生 > 程式設計 >iOS WKWebview 白屏檢測實現的示例

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,newsize.width,newsize.height)];
         }];
  }else{
    return image;
  }
}

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

縮放前白屏檢測:

iOS WKWebview 白屏檢測實現的示例

iOS WKWebview 白屏檢測實現的示例

耗時20ms

縮放後白屏檢測:

iOS WKWebview 白屏檢測實現的示例

iOS WKWebview 白屏檢測實現的示例

耗時13ms

注意這裡有個小坑。由於縮圖的尺寸在 原圖寬高*縮放係數後可能不是整數,在佈置畫布重繪時預設向上取整,這就造成畫布比實際縮圖大(混蛋啊 摔!)。在遍歷縮圖畫素時,會將圖外畫布上的畫素納入考慮範圍,導致實際白屏頁 畫素佔比並非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,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,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,newsize.height)];
         }];
  }else{
    return image;
  }
}

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

宣告

作者:BBTime
連結:https://juejin.im/post/6885298718174609415

以上就是iOS WKWebview 白屏檢測實現的示例的詳細內容,更多關於iOS WKWebview 白屏檢測的資料請關注我們其它相關文章!