鸿蒙5.0 APP开发案例分析:Image白块问题解决方案
在通过Image组件加载网络图片时,通常会经历四个关键阶段:组件创建、图片资源下载、图片解码和刷新。当加载的图片资源过大时,Image组件会在图片数据下载和解码完成后才刷新图片。这一过程中,由于图片下载较耗时,未成功加载的图片常常表现为空白或占位图(一般为白色或淡色),这可能引发“Image 白块”现象。为了提升用户体验并提高性能,应尽量避免这种情况。图1Image加载网络图片两种方式对比。
往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)
✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✏️ 记录一场鸿蒙开发岗位面试经历~
✏️ 持续更新中……
概述
在通过Image组件加载网络图片时,通常会经历四个关键阶段:组件创建、图片资源下载、图片解码和刷新。当加载的图片资源过大时,Image组件会在图片数据下载和解码完成后才刷新图片。这一过程中,由于图片下载较耗时,未成功加载的图片常常表现为空白或占位图(一般为白色或淡色),这可能引发“Image 白块”现象。为了提升用户体验并提高性能,应尽量避免这种情况。
图1 Image加载网络图片两种方式对比

为了减少白块的出现,开发者可以采用预下载的方式,可以将网络图片通过 应用沙箱 的方式进行提前缓存,将图片下载解码提前到组件创建之前执行,当Image组件加载时从应用沙箱中获取缓存数据。非首次请求时会判断应用沙箱里是否存在资源,如存在直接从缓存里获取,不再重复下载,减少Image加载大的网络图片时白屏或白块出现时长较长的问题,提升用户体验。
说明
1. 开发者在使用Image加载较大的网络图片时,网络下载推荐使用 HTTP 工具提前预下载。
2. 在预下载之后,开发者可根据业务自行选择数据处理方式,如将预下载后得到的ArrayBuffer转成BASE64、使用应用沙箱提前缓存、直接转PixelMap、或是业务上自行处理ArrayBuffer等多种方式灵活处理数据后,传给Image组件。
使用场景
当子页面需要加载很大的网络图片时,可以在父页面提前将网络数据预下载到应用沙箱中,子组件加载时从沙箱中读取,减少白块出现时长。
场景案例
开发者使用Navigation组件时,通常会在主页引入子页面组件,在按钮中添加方法实现跳转子页面组件。当子页面中需展示一张较大的网络图片时,而Image未设置占位图时,会出现点击按钮后,子组件的Image组件位置出现长时间的Image白块现象。
本文将以应用沙箱提前缓存举例,给出减少Image白块出现时长的一种优化方案。
【优化前】:使用Image组件直接加载网络地址
以下为部分示例代码:
@Builder
export function PageOneBuilder(name: string) {
PageOne()
}
@Component
export struct PageOne {
pageInfo: NavPathStack = new NavPathStack();
@State name: string = 'pageOne';
build() {
NavDestination() {
Row() {
// 不推荐用法:使用Image直接加载网络图片的方式,受到图片下载与解析的耗时影响,极易出现白块。
Image("https://www.example.com/xxx.png") // 此处请填写一个具体的网络图片地址。
.objectFit(ImageFit.Auto)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title(this.name)
}
}
说明
- 使用Image直接加载网络图片时,可以使用.alt()的方式,在网络图片加载成功前使用占位图,避免白块出现时长过长,优化用户体验。
- 使用网络图片时,需要申请权限ohos.permission.INTERNET。
【优化后】:子页面PageOne中需展示一张较大的网络图片,在父组件的aboutToAppear()中提前发起网络请求,并做判断文件是否存在,已下载的不再重复请求,存储在应用沙箱中。当父页面点击按钮跳转子页面PageOne,此时触发pixMap请求读取应用沙箱中已缓存解码的网络图片并存储在LocalStorage中,通过在子页面的Image中传入被@StorageLink修饰的变量ImageData进行数据刷新,图片送显。
图2 使用预下载的方式,由开发者灵活地处理网络图片,减少白块出现时长。

以下为关键示例代码:
- 在父组件里aboutToAppear()中提前发起网络请求,当父页面点击按钮跳转子页面PageOne,此时触发pixMap请求读取应用沙箱中已缓存解码的网络图片并存储在localStorage中。非首次点击时,不再重复调用getPixMap(),避免每次点击都从沙箱里读取文件。
import { fileIo as fs } from '@kit.CoreFileKit';
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
import { httpRequest } from '../utils/NetRequest';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
let fileUrl = filesDir + '/xxx.png'; // 当使用实际网络地址时,需填入实际地址的后缀。
let para: Record<string, PixelMap | undefined> = { 'imageData': undefined };
let localStorage: LocalStorage = new LocalStorage(para);
@Entry(localStorage)
@Component
struct MainPage {
@State childNavStack: NavPathStack = new NavPathStack();
@LocalStorageLink('imageData') imageData: PixelMap | undefined = undefined;
getPixMap() { // 从应用沙箱里读取文件
try {
let file = fs.openSync(fileUrl, fs.OpenMode.READ_WRITE); // 以同步方法打开文件
const imageSource: image.ImageSource = image.createImageSource(file.fd);
const options: image.InitializationOptions = {
'alphaType': 0, // 透明度
'editable': false, // 是否可编辑
'pixelFormat': 3, // 像素格式
'scaleMode': 1, // 缩略值
'size': { height: 100, width: 100 }
};
fs.close(file);
imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
this.imageData = pixelMap;
});
} catch (e) {
console.error('资源加载错误,文件或不存在!');
}
}
aboutToAppear(): void {
httpRequest(); // 在父组件提前发起网络请求
}
build() {
Navigation(this.childNavStack) {
Column() {
Button('push Path to pageOne', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin({ bottom: '36vp' })
.onClick(() => {
if (!localStorage.get('imageData')) { // 非首次点击,不再重复调用getPixMap(),避免每次点击都从沙箱里读取文件。
this.getPixMap();
}
this.childNavStack.pushPath({ name: 'pageOne' });
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.End)
}
.backgroundColor(Color.Transparent)
.title('ParentNavigation')
}
}
- 在NetRequest.ets中定义网络请求httpRequest(),通过fs.access()检查文件是否存在,当文件存在时不再重复请求,并写入沙箱中。
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
let fileUrl = filesDir + '/xxx.png'; // 当使用实际网络地址时,需填入实际地址的后缀。
export async function httpRequest() {
fs.access(fileUrl, fs.AccessModeType.READ).then((res) => { // 检查文件是否存在
if (!res) { // 如沙箱里不存在地址,重新请求网络图片资源
http.createHttp()
.request('https://www.example.com/xxx.png', // 此处请填写一个具体的网络图片地址。
(error: BusinessError, data: http.HttpResponse) => {
if (error) {
// 下载失败时不执行后续逻辑
return;
}
// 处理网络请求返回的数据
if (http.ResponseCode.OK === data.responseCode) {
const imageData: ArrayBuffer = data.result as ArrayBuffer;
// 保存图片到应用沙箱
readWriteFileWithStream(imageData);
}
}
)
}
})
}
// 写入到沙箱
async function readWriteFileWithStream(imageData: ArrayBuffer): Promise<void> {
let outputStream = fs.createStreamSync(fileUrl, 'w+');
await outputStream.write(imageData);
outputStream.closeSync();
}
- 在子组件中通过在子页面的Image中传入被@StorageLink修饰的变量ImageData进行数据刷新,图片送显。
@Builder
export function PageOneBuilder(name: string,param: Object) {
PageOne()
}
@Component
export struct PageOne {
pageInfo: NavPathStack = new NavPathStack();
@State name: string = 'pageOne';
@LocalStorageLink('imageData') imageData: PixelMap | undefined = undefined;
build() {
NavDestination() {
Row() {
Image(this.imageData) // 正例:此时Image拿到已提前加载好的网络图片,减少了白块出现时长
.objectFit(ImageFit.Auto)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title(this.name)
}
}
性能分析
下面,使用trace对优化前后性能进行对比分析。
【优化前】
分析阶段的起点为父页面点击按钮开始计时即trace的H:DispatchTouchEvent,结束点为子页面图片渲染的首帧出现即H:CreateImagePixelMap标签后的第一个Vsync,记录白块出现时间为1.3s,其中以H:HttpRequestInner的标签起始为起点到H:DownloadImageSuccess标签结束为终点记录时间,即为网络下载耗时1.2s,因此使用Image直接加载网络图片时,出现长时间Image白块,其原因是需要等待网络下载资源完成。
图3 直接使用Image加载网络数据

【优化后】
分析阶段的起点为父页面点击按钮开始计时即trace的H:DispatchTouchEvent,结束点为子页面图片渲染的首帧出现即H:CreateImagePixelMap标签后的第一个Vsync,记录白块出现时间为32.6ms,其中记录H:HttpRequestInner的标签耗时即为提前网络下载的耗时1.16s,对比白块时长可知提前预下载可以减少白块出现时长。
图4 使用预下载的方式

说明
网络下载耗时实际受到网络波动影响,优化前后的网络下载耗时数据总体差异在1s内,提供的性能数值仅供参考。
效果对比
- (优化前)直接使用Image加载网络数据,未使用预下载

- (优化后)使用预下载

性能对比
对比数据如下:
| 方案 | 白块出现时长(毫秒) | 白块出现时长 |
|---|---|---|
| (优化前)直接使用Image加载网络数据,未使用预下载 | 1300 | 图片位置白块出现时间较长 |
| (优化后)使用预下载 | 32.6 | 图片位置白块出现时间较短 |
说明
1.测试数据仅限于示例程序,不同设备特性和具体应用场景的多样性,所获得的性能数据存在差异,提供的数值仅供参考。
2.由于该方案仅将下载解码网络图片的步骤提前,不会影响内存等应用数据。开发者可自行管理解码后的PixelMap,主动实现图片的复用和缓存。
由此可见,加载网络图片时,使用预下载,提前处理网络请求并从应用沙箱中读取缓存数据的方式,可以减少用户可见Image白屏或白块出现时长,提升用户体验。
更多推荐



所有评论(0)