鸿蒙应用开发-webview 组件白屏检测

1,469 阅读4分钟

白屏异常问题介绍

Web 页面白屏是在客户端中比较常见的,Android 以及iOS 不同手机不同版本内核 会出现不同的差异问题,对于HarmonyOS Web 也会出现这种异常问题。出现白屏的原因可能由以下原因:

  • 网络问题 网络异常或者设备网络条件不好,尝试加载网络内容时可能导致白屏
  • 系统兼容问题 在线上负责多元化的设备中可能会出现系统兼容报错导致web加载异常。
  • HTML、JavaScript语法兼容问题 前端语法报错或者前端框架兼容异常导致异常

对于白屏异常问题 ,我们应该合理的监控,及时更正并解决这个异常问题。

白屏检测方案

Android 以及iOS端` 我们都知道有成熟的白屏检测方案:

  • 截取当前屏幕的内容,获得Bitmap
  • 对图片进行分片抽样检测,判断Bitmap 是否为白色图片
  • 针对白屏做相应的处理

HarmonyOS 也是一样的原理 可以对图片进行缩小分片,按照小区域划分,判断这些小区域的点位的白屏信息,进行决策。

具体实现

  • web 页面截屏

componentSnapshot.get

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
idstring目标组件的标识
callbackAsyncCallback<image.PixelMap>截图返回结果的回调。

截屏事例:

Web({ src: 'https://www.baidu.com/', controller: this.webviewController })  
  .width("100%")  
  .id("id_web")
componentSnapshot.get("id_web", (error: Error, pixmap: image.PixelMap) => {  
  pixmap.getImageInfo().then((info) => {  
    console.info("image info: width=" + info.size.width + ",height=" + info.size.height + ",density=" + info.density);  
  })  
})

  • 定义白屏采样参数
declare class CheckParam {
  //白屏采样图片
  imageMap: image.PixelMap
  //采样图片压缩倍数
  compressRatio: number;
  //白屏采样阈值,单位:百分比
  threshold: number;
  //白屏采样比例,单位:百分比
  density: number;
  //采样区域大小
  regionSize: number;
  //采样对比颜色
  color: number;
}

  • 白屏检测具体实现

首先定义白屏检测的异步方法,返回是 Promise

function checkWhiteScreen(param: CheckParam): Promise<boolean> {
...
}

按照param.compressRatio 的,来压缩图片的大小再通过getImageInfo解析图片的具体信息

param.imageMap.scale(1 / param.compressRatio, 1 / param.compressRatio)  
  .then(() => {  
    return param.imageMap.getImageInfo();  
  })

根据上一步得到的info信息来把图片按照采样区域生成ArrayBuffer数组

then(info => {
    let width = info.size.width;
    let height = info.size.height;
    let size = param.regionSize;
    // 根据采样比例计算采样图片 X 轴的采样点数量
    let pointNumX = Math.floor(width * param.density / 100 / size);
    // 根据采样比例计算采样图片 Y 轴的采样点数量
    let pointNumY = Math.floor(height * param.density / 100 / size);
    // 根据采样点数量计算采样图片 X 轴的采样步长
    let xStep = Math.floor(width / pointNumX);
    // 根据采样点数量计算采样图片 Y 轴的采样步长
    let yStep = Math.floor(height / pointNumY);
    console.info("采样点数量:pointNumX =" + pointNumX + "pointNumY=" + pointNumY + "total=" + pointNumX * pointNumY);
    console.info("采样步长:" + xStep + "," + yStep);

    let promiseArr = [] as Promise<ArrayBuffer>[];
    for (let i = 0; i < pointNumX; i++) {
      for (let j = 0; j < pointNumY; j++) {
        let x = i * xStep;
        let y = j * yStep;
        //buffer大小,取值为:height * width *4
        const buffer = new ArrayBuffer(size * size * 4);
        promiseArr.push(param.imageMap.readPixels({ pixels: buffer,
          offset: 0,
          stride: size * 4,
          region: { x: x, y: y, size: { width: size, height: size } } }).then(() => {
          return buffer
        }));
      }
    }
    return Promise.all(promiseArr);
})

遍历数组然后判断每一个像素点的信息统计后判断白屏率

then(buffers=>{  
  let whitePointNum = 0;  
    let cr = param.color >> 16 & 0xff;  
    let cg = param.color >> 8 & 0xff;  
    let cb = param.color & 0xff;  
    let ca = param.color >> 24 & 0xff;  
  for (let i = 0; i < buffers.length; i++) {  
    let buffer = buffers[i];  
    let dataView = new DataView(buffer);  
    for (let j = 0; j < buffer.byteLength; j += 4) {  
      let r = dataView.getUint8(j);  
      let g = dataView.getUint8(j + 1);  
      let b = dataView.getUint8(j + 2);  
      let a = dataView.getUint8(j + 3);  
      if (r === cr && g === cg && b === cb && a === ca) {  
        whitePointNum++;  
      }  
    }  }  
    let whitePointPercent = Math.floor(whitePointNum / (buffers.length * param.regionSize * param.regionSize) * 100);
  console.info("白屏检测耗时:" + (Date.now() - time) + "ms");  
  if (whitePointPercent >= param.threshold) {  
    console.info("白屏" + whitePointPercent + "%");  
    return true;  
  } else {  
    console.info("非白屏" + whitePointPercent + "%");  
    return false;  
  }  
})

完整代码如下

//白屏采样参数定义
import image from '@ohos.multimedia.image';

declare class CheckParam {
  //白屏采样图片
  imageMap: image.PixelMap
  //采样图片压缩倍数
  compressRatio: number;
  //白屏采样阈值,单位:百分比
  threshold: number;
  //白屏采样比例,单位:百分比
  density: number;
  //采样区域大小
  regionSize: number;
  //采样对比颜色
  color: number;
}

function checkWhiteScreen(param: CheckParam): Promise<boolean> {
  const time = Date.now();
  //压缩图片
  return param.imageMap.scale(1 / param.compressRatio, 1 / param.compressRatio)
    .then(() => {
      return param.imageMap.getImageInfo();
    })
    .then(info => {
      let width = info.size.width;
      let height = info.size.height;
      let size = param.regionSize;
      // 根据采样比例计算采样图片 X 轴的采样点数量
      let pointNumX = Math.floor(width * param.density / 100 / size);
      // 根据采样比例计算采样图片 Y 轴的采样点数量
      let pointNumY = Math.floor(height * param.density / 100 / size);
      // 根据采样点数量计算采样图片 X 轴的采样步长
      let xStep = Math.floor(width / pointNumX);
      // 根据采样点数量计算采样图片 Y 轴的采样步长
      let yStep = Math.floor(height / pointNumY);
      console.info("采样点数量:pointNumX =" + pointNumX + "pointNumY=" + pointNumY + "total=" + pointNumX * pointNumY);
      console.info("采样步长:" + xStep + "," + yStep);

      let promiseArr = [] as Promise<ArrayBuffer>[];
      for (let i = 0; i < pointNumX; i++) {
        for (let j = 0; j < pointNumY; j++) {
          let x = i * xStep;
          let y = j * yStep;
          //buffer大小,取值为:height * width *4
          const buffer = new ArrayBuffer(size * size * 4);
          promiseArr.push(param.imageMap.readPixels({ pixels: buffer,
            offset: 0,
            stride: size * 4,
            region: { x: x, y: y, size: { width: size, height: size } } }).then(() => {
            return buffer
          }));
        }
      }

      return Promise.all(promiseArr);
    })
    .then(buffers => {
      let whitePointNum = 0;
      let cr = param.color >> 16 & 0xff;
      let cg = param.color >> 8 & 0xff;
      let cb = param.color & 0xff;
      let ca = param.color >> 24 & 0xff;
      for (let i = 0; i < buffers.length; i++) {
        let buffer = buffers[i];
        let dataView = new DataView(buffer);
        for (let j = 0; j < buffer.byteLength; j += 4) {
          let r = dataView.getUint8(j);
          let g = dataView.getUint8(j + 1);
          let b = dataView.getUint8(j + 2);
          let a = dataView.getUint8(j + 3);
          if (r === cr && g === cg && b === cb && a === ca) {
            whitePointNum++;
          }
        }
      }
      let whitePointPercent = Math.floor(whitePointNum / (buffers.length * param.regionSize * param.regionSize) * 100);
      console.info("白屏检测耗时:" + (Date.now() - time) + "ms");
      if (whitePointPercent >= param.threshold) {
        console.info("白屏" + whitePointPercent + "%");
        return true;
      } else {
        console.info("非白屏" + whitePointPercent + "%");
        return false;
      }
    })
}

function logPicInfo(imageMap: image.PixelMap) {
  imageMap.getImageInfo().then((info) => {
    console.info("image info: width=" + info.size.width + ",height=" + info.size.height + ",density=" + info.density);
  })
}

export {
  checkWhiteScreen
}

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀