之前介绍了HarmonyOS的Web组件的简单使用和离线包方案,本篇文章主要说的是Web组件的白屏检测的方案

APP白屏问题简介

Web白屏问题通常是在客户端上经常遇到的一种常见问题,尤其是对于Android来说,多个版本的碎片化导致用户有多种不同版本的chrome内核,那么对于HarmonyOS来说也是一样的,一般来说Web白屏有以下几种原因

  • Web组件配置问题,对于APP来说,这种可能性很小,主要是开发阶段遇到,比如没有开启网络权限、JavaScript未启用、缓存问题等
  • 网络问题,如果设备网络条件不好,尝试加载网络内容时可能导致白屏,这个可以考虑离线包方案解决可以参考之前那篇文章
  • SSL证书问题 如果WebView加载的内容涉及到HTTPS,SSL证书验证失败可能导致页面无法加载。
  • HTML、JavaScript语法兼容问题,由于前端这几年发展迅速,导致语法变更快,很多框架比如vue、react等在构建的时候,ES语法不兼容在客户端上经常见到,这种大多都是兼容性问题,比如只在某些低版本机型可见,此类问题比较难以感知,只有用户反馈过来才能去响应

总体来说Web白屏问题是没发根治的,只能通过合理的线上监控,去发现解决一些疑难的白屏问题,因此对于客户端APP来说白屏检测基本上是必有功能,既可以去做白屏监控,也能通过检测来统计用户前段加载页面时所需要的白屏时间,为Web秒开优化做有效数据验证。

白屏检测方案

在Android和iOS端白屏检测方案比较成熟,我们参考字节的方案来简单讲一下判断白屏的先觉条件把

字节方案原文如下:
...我们把 WebView 截图的图片进行缩小到原图的 1/6,遍历检测图片的像素点,当非白色的像素点大于 5% 的时候我们就认为是非白屏的情况,可以相对高效检测准确得出详情页是否发生了白屏。

参考 https://www.toutiao.com/article/6871177699826598403/?wid=1702706377614

首先先对Web组件进行截图,但如果按照图片整个大小来遍历像素点,这耗费的时间和性能在客户端上时不太现实的,我们一般把图片缩小,然后在对图片进行抽样检测例,抽样一般都是按照小区域来划分,判断这些小区域的点位的白屏信息,来判断页面的白屏情况,采样点越细,判断的越精确,但同时性能损耗页越大,采样方案 如下图所示

区域采样

HarmonyOS Web组件白屏检测

话不多说,我们开干,

1. 首先,我们先对web组件进行截图,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//给Web 组件设置id 
Web({ src: this.url, controller: this.webviewController })
.layoutWeight(1)
.width("100%")
.id("web-view")
...
...
...
//调用 componentSnapshot 对Web截图
componentSnapshot.get("web-view", (error: Error, pixmap: image.PixelMap) => {
pixmap.getImageInfo().then((info) => {
console.info("image info: width=" + info.size.width + ",height=" + info.size.height + ",density=" + info.density);
})
})

2. 定义白屏采样参数

1
2
3
4
5
6
7
8
declare class CheckParam {  
imageMap: image.PixelMap
compressRatio: number;
threshold: number;
pointNum: number;
regionSize: number;
color: number;
}

参数解释

  • imageMap,采样图片
  • compressRatio,采样图片压缩倍数
  • threshold,白屏采样阈值,单位:百分比
  • pointNum,白屏采样点数 = n*n
  • regionSize,采样区域大小
  • color,采样白屏对比颜色,一般为白色,如果是黑色背景就是黑色,按照Web组件的背景色来判1断

    3. 白屏检测具体实现

    首先定义白屏检测的异步方法,参数是 Promise
1
2
3
function checkWhiteScreen(param: CheckParam): Promise<boolean> {
...
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
then(info => {
let width = info.size.width;
let height = info.size.height;
let size = param.regionSize;
let xStep = Math.floor(width / param.pointNum);
let yStep = Math.floor(height / param.pointNum);
let promiseArr = [] as Promise<ArrayBuffer>[];
for (let i = 0; i < param.pointNum; i++) {
for (let j = 0; j < param.pointNum; 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);
})

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 = whitePointNum / (param.pointNum * param.pointNum * 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;
}
})

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import image from '@ohos.multimedia.image';  

declare class CheckParam {
//白屏采样图片
imageMap: image.PixelMap
//采样图片压缩倍数
compressRatio: number;
//白屏采样阈值,单位:百分比
threshold: number;
//白屏采样点数
pointNum: 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;
let xStep = Math.floor(width / param.pointNum);
let yStep = Math.floor(height / param.pointNum);
let promiseArr = [] as Promise<ArrayBuffer>[];
for (let i = 0; i < param.pointNum; i++) {
for (let j = 0; j < param.pointNum; 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 = whitePointNum / (param.pointNum * param.pointNum * 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;
}
})
}
export {
checkWhiteScreen
}

总结

在本文中,我们深入探讨了HarmonyOS中Web组件的白屏问题及其检测方案。

文章重点介绍了一种基于字节的白屏检测方案,通过对WebView截图、压缩和采样像素点的方式,遍历检测小区域的非白色像素点数量,从而判断是否出现白屏问题。方案考虑了压缩比、采样阈值、采样点数、采样区域大小和颜色等参数,使得检测更加灵活和精准。

在HarmonyOS中的具体实现中,使用了PixelMap和相关的图像处理API,通过异步方法和Promise链式调用的方式,实现了对Web组件的白屏检测。通过该方案,开发者能够在应用中及时发现白屏问题,提高应用的稳定性和用户体验。

总体而言,白屏问题作为移动应用中常见的用户体验难题,需要综合考虑多个因素,结合合适的检测方案。本文所介绍的HarmonyOS Web组件白屏检测方案提供了一种实用的思路和代码实现,对于开发者优化应用性能、提升用户体验具有积极意义。