之前介绍了HarmonyOS的Web组件的简单使用和离线包方案,本篇文章主要说的是Web组件的白屏检测的方案
APP白屏问题简介
Web
白屏问题通常是在客户端上经常遇到的一种常见问题,尤其是对于Android来说,多个版本的碎片化导致用户有多种不同版本的chrome内核,那么对于HarmonyOS来说也是一样的,一般来说Web
白屏有以下几种原因
- Web组件配置问题,对于APP来说,这种可能性很小,主要是开发阶段遇到,比如没有开启网络权限、JavaScript未启用、缓存问题等
- 网络问题 ,如果设备网络条件不好,尝试加载网络内容时可能导致白屏,这个可以考虑离线包方案解决可以参考之前那篇文章 鸿蒙HarmonyOS:Web组件初体验与离线包方案探索
- SSL证书问题 如果WebView加载的内容涉及到HTTPS,SSL证书验证失败可能导致页面无法加载。
- HTML、JavaScript语法兼容问题,由于前端这几年发展迅速,导致语法变更快,很多框架比如vue、react等在构建的时候,ES语法不兼容在客户端上经常见到,这种大多都是兼容性问题,比如只在某些低版本机型可见,此类问题比较难以感知,只有用户反馈过来才能去响应
总体来说Web白屏问题是没发根治的,只能通过合理的线上监控,去发现解决一些疑难的白屏问题,因此对于客户端APP来说白屏检测基本上是必有功能,既可以去做白屏监控,也能通过检测来统计用户前段加载页面时所需要的白屏时间,为Web秒开优化做有效数据验证。
白屏检测方案
在Android和iOS端白屏检测方案比较成熟,我们参考字节的方案来简单讲一下判断白屏的先觉条件把
字节方案原文如下: ...我们把 WebView 截图的图片进行缩小到原图的 1/6,遍历检测图片的像素点,当非白色的像素点大于 5% 的时候我们就认为是非白屏的情况,可以相对高效检测准确得出详情页是否发生了白屏。
参考 www.toutiao.com/article/687...
首先先对Web组件进行截图,但如果按照图片整个大小来遍历像素点,这耗费的时间和性能在客户端上时不太现实的,我们一般把图片缩小,然后在对图片进行抽样检测例,抽样一般都是按照小区域来划分,判断这些小区域的点位的白屏信息,来判断页面的白屏情况,采样点越细,判断的越精确,但同时性能损耗页越大,采样方案 如下图所示
HarmonyOS Web组件白屏检测
话不多说,我们开干,
1. 首先,我们先对web组件进行截图,
js
//给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. 定义白屏采样参数
js
declare class CheckParam {
//白屏采样图片
imageMap: image.PixelMap
//采样图片压缩倍数
compressRatio: number;
//白屏采样阈值,单位:百分比
threshold: number;
//白屏采样比例,单位:百分比
density: number;
//采样区域大小
regionSize: number;
//采样对比颜色
color: number;
}
参数解释
- imageMap,采样图片
- compressRatio,采样图片压缩倍数
- threshold,白屏采样阈值,单位:百分比
- density,白屏采样比例,单位:百分比
- regionSize,采样区域大小
- color,采样白屏对比颜色,一般为白色,如果是黑色背景就是黑色,按照Web组件的背景色来判1断
3. 白屏检测具体实现
首先定义白屏检测的异步方法,返回是 Promise
js
function checkWhiteScreen(param: CheckParam): Promise<boolean> {
...
}
按照param.compressRatio 的,来压缩图片的大小再通过getImageInfo解析图片的具体信息
scss
param.imageMap.scale(1 / param.compressRatio, 1 / param.compressRatio)
.then(() => {
return param.imageMap.getImageInfo();
})
根据上一步得到的info信息来把图片按照采样区域生成ArrayBuffer数组
js
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);
})
遍历数组然后判断每一个像素点的信息统计后判断白屏率
js
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;
}
})
完整代码如下
js
//白屏采样参数定义
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
}
效果展示
待优化
采样调优
对于白屏采样来说,有着多种方式,本文的采样将页面均匀采样,精度可能准确些但是性能上有很大的提升空间,
比如减少采样点的十字形采样:
也可以对采样点的颜色判断优化成区域判断,比如在 250~255 之间的都是白色
多种方式一起判断
前端白屏像素点采集统计只是其中的一种方法,白屏判断可以多种方式结合,像素点采集为其中一种权重,可以和前端结合,js检测根节点是否渲染,检测窗口高度等多种方式综合判断
无法精准匹配,无法适应复杂业务场景
像素采集方式还是基于统计能力,前端有骨架屏、错误页、复杂背景色等会影响检测结果,像素分析不具有通用性。
后续希望拓展一下端智能的方式,通过 AI 算法来去做精准的白屏统计
总结
在本文中,我们深入探讨了HarmonyOS中Web组件的白屏问题及其检测方案。
文章重点介绍了一种基于字节的白屏检测方案,通过对WebView截图、压缩和采样像素点的方式,遍历检测小区域的非白色像素点数量,从而判断是否出现白屏问题。方案考虑了压缩比、采样阈值、采样点数、采样区域大小和颜色等参数,使得检测更加灵活和精准。
在HarmonyOS中的具体实现中,使用了PixelMap和相关的图像处理API,通过异步方法和Promise链式调用的方式,实现了对Web组件的白屏检测。通过该方案,开发者能够在应用中及时发现白屏问题,提高应用的稳定性和用户体验。
但是面对复杂的前端业务场景,像素采样的方式还是比较单薄,后续希望拓展段智能的方式,让 AI 能力扩展到HarmonyOS。
总体而言,白屏问题作为移动应用中常见的用户体验难题,需要综合考虑多个因素,结合合适的检测方案。本文所介绍的HarmonyOS Web组件白屏检测方案提供了一种实用的思路和代码实现,对于开发者优化应用性能、提升用户体验具有积极意义。
鸣谢
感谢ChatGPT 对本文的写作帮助
感谢Github Copilot 对本文代码帮助