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

注意这里有个小坑。由于缩略图的尺寸在 原图宽高*缩放系数后可能不是整数,在布置画布重绘时默认向上取整,这就造成画布比实际缩略图大(混蛋啊 摔!)。在遍历缩略图像素时,会将图外画布上的像素纳入考虑范围,导致实际白屏页 像素占比并非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生命周期内回调使用该函数方法即可检测出页面状态是否白屏,且性能损耗可忽略不计。

声明

作者:BBTime
链接:https://juejin.im/post/6885298718174609415

以上就是iOS WKWebview 白屏检测实现的示例的详细内容,更多关于iOS WKWebview 白屏检测的资料请关注我们其它相关文章!

时间: 2020-10-20

iOs迁至WKWebView跨过的一些坑

前言 在iOS中有两种网页视图可以加载网页除了系统的那个控制器.一种是UIWebView,另一种是WKWebView,其实WKWebView就是想替代UIWebView的,因为我们都知道UIWebView非常占内存等一些问题,但是现在很多人还在使用UIWebView这是为啥呢?而且官方也宣布在iOS12中废弃了UIWebView让我们尽快使用WKWebView.其实也就是这些东西:**页面尺寸问题.JS交互.请求拦截.cookie带不上的问题.**所以有时想要迁移还得解决这些问题,所以还是很烦的

iOS中wkwebView内存泄漏与循环引用问题详解

前言 现在大多数网络也面加载都会用到wkwebview,之前在使用wkwebview的时候,网上很多的基础教程使用很多只是说了怎么添加Message Handler 但是并没有告诉到家有这个内存泄漏的风险,如果你只是也没内的数据调用你压根都不会发现这个问题.没存泄漏这个问题说大不大,说小不小,严重的话话直接到时app闪退,所以还是得重视起.好下面说一下怎么解决,话不多说了,来一起看看详细的介绍吧 解决方法 1,在做网页端js交互的时候 我们都会这样去添加js [self.customWebVie

iOS中WKWebView白屏问题的分析与解决

前言 随着WKWebView的推出, 解决了很多UIWebView 的问题.比如加载速度慢,内存泄露等问题.WKWebView是在iOS 8 推出,前段时间正好把项目也适配到iOS 8 以上了,终于可以把项目中的UIWebView 替换成WKWebView. WKWebView的特点: 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 但是发现在使用的时候还是有很

iOS开发教程之WKWebView与JS的交互

前言 iOS8以后,Apple公司推出了WKWebView,对比之前的UIWebView不论是处理速度还是内存性能,都有了大幅度的提升! 那么下面我就分享一下WKWebView与JS的交互. 首先使用WKWebView.你需要导入WebKit #import 然后初始化一个WKWebView,设置代理,并且执行代理的方法.在网页加载成功的时候,我们会调用一些JS代码对网页进行设置. WKWebView的代理一共有三个:WKUIDelegate,WKNavigationDelegate,WKScr

iOS中WKWebView的一些特殊使用总结

前言 现在大部分的app只支持iOS8以上的系统了,在接入H5时可以只管最新的WKWebView了. WKWebView的优势 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 本文将给大家总结下iOS中WKWebView的一些特殊使用,下面话不多说了,来一起看看详细的介绍吧 WKWebView 加载本地网页的方式 1.直接加载字符串 - (void)loadHT

iOS中WKWebView仿微信加载进度条

本文实例为大家分享了WKWebView仿微信加载进度条的具体代码,供大家参考,具体内容如下 WKWebView添加了estimatedProgress属性(double类型),我们可以利用该属性来设置UIProgressView github代码仓库上存放的Demo 为页面添加UIProgressView属性 @property (nonatomic, strong) WKWebView *mywebView; @property (nonatomic, strong) UIProgressVi

简单说说iOS之WKWebView的用法小结

WKWebView的优势 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 1.Xcode新建My.html文件,自定义html内容,主要代码如下: (1)标签为UI样式(写了简单的JS代码,目的用于讲解交互) (2)onClick为JS事件,当JS想给OC传递参数时,采用如下代码:window.webkit.messageHandlers.<方法名>.post

iOS使用WKWebView加载HTML5不显示屏幕宽度的问题解决

最近在项目中我们的商品详情页是一个后台返回的图片标签.需要我们自己去写一个HTML5标签进行整合,(相当于重新写了一个HTML页面) :ok_hand:那就没办法了,我就自己写一个标签咯,应该不难吧.嘻嘻嘻嘻~~~~~ dispatch_async(dispatch_get_main_queue(), ^{ if(self.detailModel.details){ //这里是自己写的简单的加载H5 NSString *header =@"<head><meta name=\v

iOS WKWebView适配实战篇

一.Cookie适配 1.现状 WKWebView适配中最麻烦的就是cookie同步问题 WKWebView采用了独立存储控件,因此和以往的UIWebView并不互通 虽然iOS11以后,iOS开放了WKHTTPCookieStore让开发者去同步,但是还是需要考虑低版本的 同步问题,本章节从各个角度切入考虑cookie同步问题 2.同步cookie(NSHTTPCookieStorage->WKHTTPCookieStore) iOS11+ 可以直接使用WKHTTPCookieStore遍历方

微信小程序iOS下拉白屏晃动问题解决方案

这篇文章主要介绍了微信小程序iOS下拉白屏晃动问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 问题 感觉ios的小程序每个页面都可以下拉出现白屏 有时页面带有滑动的属性会跟着晃动,体验不是很好 解决办法: 先禁止页面下拉 <config> { navigationBarTitleText: "购物车", disableScroll:true } </config> 这样的话页面整个都拉不动了,下面溢

vue 项目 iOS WKWebView 加载

1.首先让前端的同事打一个包(index.html,static文件包含css.资源文件.js等)导入项目: :warning: 注意: 把index.html放入项目根目录下,command+n创建一个资源文件.bundle,资源文件里也的包含一份 index.html 下面开始代码: 懒加载WKWebView 引入#import <WebKit/WebKit.h> #import <WebKit/WKWebView.h> 继承 WKNavigationDelegate,WKUI

iOS和JS交互教程之WKWebView-协议拦截详解

前言 由于Xcode8发布之后,编译器开始不支持iOS 7了,这样我们的app也改为最低支持iOS 8.0,既然需要与web交互,那自然也就选择使用了 iOS 8.0之后 才推出的新控件 WKWebView. 相比与 UIWebView, WKWebView 存在很多优势: 支持更多的HTML5的特性 高达60fps滚动刷新频率与内置手势 与Safari相容的JavaScript引擎 在性能.稳定性方面有很大提升占用内存更少 协议方法及功能都更细致 可获取加载进度等. 先解释下标题:"iOS与J

Zend Framework入门教程之Zend_Session会话操作详解

本文实例讲述了Zend Framework入门教程之Zend_Session会话操作.分享给大家供大家参考,具体如下: 会话命名空间 实现会话 代码: <?php require_once "Zend/Session/Namespace.php"; $myNamespace = new Zend_Session_Namespace('Myspace'); if(isset($myNamespace->numberOfPageRequests)) { $myNamespace

Zend Framework教程之Zend_Layout布局助手详解

本文实例讲述了Zend Framework教程之Zend_Layout布局助手.分享给大家供大家参考,具体如下: 一.作用 布局的作用和模版的作用类似.可以认为是把网站通用.公共的部分拿出来作为通用的页面框架.例如一个基本的web页面,可能页面的头和尾都是一样,不一样的可能只是内容body部分不一样,可以把公共的部分做成模版.不仅可以提高开发效率,也为后期的维护带来方便. 二.使用 这里举一个简单的例子. 首先用zend studio创建一个基本的zend framework项目:layout_

Zend Framework教程之Application用法实例详解

本文实例讲述了Zend Framework教程之Application用法.分享给大家供大家参考,具体如下: Zend_Application是Zend Framework的核心组件.Zend_Application为Zend Framework应用程序提供基本功能,是程序的入口点.它的主要功能有两个:装载配置PHP环境(包括自动加载),并引导应用程序. 通常情况下,通过配置选项配置Zend_Application构造器,但也可以完全使用自定义方法配置.以下是两个使用用例. Zend_Appli

Zend Framework教程之Zend_Controller_Plugin插件用法详解

本文实例讲述了Zend Framework教程之Zend_Controller_Plugin插件用法.分享给大家供大家参考,具体如下: 通过Zend_Controller_Plugin可以向前端控制器增加附加的功能.便于w一些特殊功能.以下是Zend_Controller_Plugin的简单介绍. Zend_Controller_Plugin的基本实现 ├── Plugin │   ├── Abstract.php │   ├── ActionStack.php │   ├── Broker.p

Angular4学习教程之DOM属性绑定详解

前言 DOM 元素触发的一些事件通过 DOM 层级结构传播,事件首先由最内层的元素开始,然后传播到外部元素,直到它们到根元素,这种传播过程称为事件冒泡.本文主要介绍了关于Angular4 DOM属性绑定的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 简介 使用插值表达式将一个表达式的值显示在模版上 <img src="{{imgUrl}}" alt=""> <h1>{{productTitle}}</h1&

iOS实用教程之Https双向认证详解

前言 年前的时候,关于苹果要强制https的传言四起,虽然结果只是一个"谣言",但是很明显的这是迟早会到来的,间接上加速了各公司加紧上https的节奏,对于iOS客户端来说,上https需不需要改变一些东西取决于---------对,就是公司有没有钱.土豪公司直接买买买,iOS开发者只需要把http改成https完事.然而很不幸,我们在没钱的公司,选择了自签证书.虽然网上很多关于https的适配,然而很多都是已过时的,这里我们主要是讲一下https双向认证. [证书选择]自签 [网络请

iOS学习教程之UIView中坐标转换详解

本文主要介绍的是关于iOS UIView坐标转换的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 在开发中我们经常会需要判断两个控件是否包含重叠,此时如果控件A和B的坐标原点如果不确定的话,那么肯定会导致比较不正确发生错误 判断包含重叠的代码如下: CGRectContainsRect(<#CGRect rect1#>, <#CGRect rect2#>) CGRectContainsPoint(<#CGRect rect#>, <#CGPoint

AngularJS 入门教程之HTML DOM实例详解

AngularJS HTML DOM AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令. ng-disabled 指令 ng-disabled 指令直接绑定应用程序数据到 HTML 的 disabled 属性. AngularJS 实例 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://apps.bd

AngularJS入门教程之Select(选择框)详解

AngularJS Select(选择框) AngularJS 可以使用数组或对象创建一个下拉列表选项. 使用 ng-options 创建选择框 在 AngularJS 中我们可以使用 ng-option 指令来创建一个下拉列表,列表项通过对象和数组循环输出,如下实例: 实例 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://a