鸿蒙中 Web页面的加载速度优化
本文介绍了一套完整的Web性能优化方案,包含从域名级到资源级的全方位加速技术。主要包括:1)预解析与预连接技术,通过提前进行DNS解析和TCP连接减少网络延迟;2)预加载技术提前下载页面资源;3)预获取POST请求优化API调用;4)预编译生成JavaScript字节码缓存;5)离线资源注入避免网络请求。这些技术可分层组合使用,在Wi-Fi环境下可更激进地应用,同时需注意内存管理和网络环境适配。通
本文同步发表于 微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、预解析与预连接(域名级优化)
1. 原理
通过对目标域名提前进行DNS解析和TCP连接建立,减少页面加载时的网络延迟。
2. 核心API
webview.WebviewController.prepareForPageLoad(
url: string, // 要预连接的URL
preconnect: boolean, // true: 预连接;false: 仅DNS预解析
socketCount: number // 预连接socket数量,最多6个
)
3. 使用示例
场景一:在Web组件出现时预连接
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: 'https://www.example.com/',
controller: this.webviewController
})
.onAppear(() => {
// 预连接:DNS解析 + TCP连接建立
webview.WebviewController.prepareForPageLoad(
'https://www.example.com/',
true, // 进行预连接
2 // 预连接2个socket
);
})
}
}
}
场景二:在Ability中提前预连接首页
import { webview } from '@kit.ArkWeb';
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
console.info("EntryAbility onCreate");
// 1. 提前初始化Web内核
webview.WebviewController.initializeWebEngine();
// 2. 预连接首页
webview.WebviewController.prepareForPageLoad(
"https://www.example.com/",
true, // 预连接
2 // 预连接2个socket
);
console.info("EntryAbility onCreate done");
}
}
二、预加载(资源级优化)
1. 原理
提前下载页面所需的所有资源(主资源+子资源),避免阻塞页面渲染,但不执行JavaScript代码。
2. 核心API
webviewController.prefetchPage(url: string)
3. 使用示例
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: 'https://www.example.com/',
controller: this.webviewController
})
.onPageEnd(() => {
// 当前页面加载完成后,预加载下一个页面
this.webviewController.prefetchPage(
'https://www.iana.org/help/example-domains'
);
})
}
}
}
三、预获取POST请求(请求级优化)
1. 核心API
// 预获取资源
webview.WebviewController.prefetchResource(
request: RequestInfo, // 请求信息
headers?: Header[], // 请求头
key?: string, // 资源标识键
expirationTime?: number // 过期时间(秒)
)
// 清除预获取资源
webview.WebviewController.clearPrefetchedResource(keys: string[])
2. 使用场景
场景一:页面加载时预获取POST请求
@Entry
@Component
struct WebComponent {
webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'https://www.example.com/', controller: this.webviewController })
.onAppear(() => {
// 预获取POST请求
webview.WebviewController.prefetchResource(
{
url: 'https://www.example1.com/post?e=f&g=h',
method: 'POST',
formData: 'a=x&b=y',
},
[{
headerKey: 'c',
headerValue: 'z',
}],
'KeyX', // 资源标识
500 // 500秒后过期
);
})
.onPageEnd(() => {
// 清除不再使用的预获取资源
webview.WebviewController.clearPrefetchedResource(['KeyX']);
})
}
}
}
场景二:在Ability中预获取首页POST请求
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
webview.WebviewController.initializeWebEngine();
webview.WebviewController.prefetchResource(
{
url: "https://www.example1.com/post?e=f&g=h",
method: "POST",
formData: "a=x&b=y",
},
[{
headerKey: "c",
headerValue: "z",
}],
"KeyX",
500
);
}
}
四、预编译生成编译缓存
1. 原理
提前将JavaScript脚本编译成字节码缓存,减少运行时编译开销。
2. 核心API
webviewController.precompileJavaScript(
url: string, // 脚本URL
script: Uint8Array, // 脚本内容
options?: CacheOptions // 缓存选项
)
3. 实现示例
步骤1:Ability中存储UIContext
// EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
const localStorage: LocalStorage = new LocalStorage('uiContext');
export default class EntryAbility extends UIAbility {
storage: LocalStorage = localStorage;
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage, (err, data) => {
if (err.code) {
return;
}
// 存储UIContext供后续使用
this.storage.setOrCreate<UIContext>(
"uiContext",
windowStage.getMainWindowSync().getUIContext()
);
});
}
}
步骤2:动态组件代码
// DynamicComponent.ets
import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
export interface BuilderData {
url: string;
controller: WebviewController;
context: UIContext;
}
export class NodeControllerImpl extends NodeController {
private rootNode: BuilderNode<BuilderData[]> | null = null;
private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>, context: UIContext) {
super();
this.wrappedBuilder = wrappedBuilder;
}
makeNode(): FrameNode | null {
return this.rootNode?.getFrameNode() || null;
}
initWeb(url: string, controller: WebviewController) {
// 初始化BuilderNode
this.rootNode = new BuilderNode(uiContext);
this.rootNode.build(this.wrappedBuilder, { url, controller });
}
}
export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
const baseNode = new NodeControllerImpl(wrappedBuilder, data.context);
baseNode.initWeb(data.url, data.controller);
return baseNode;
}
步骤3:预编译组件
// PrecompileWebview.ets
import { BuilderData } from './DynamicComponent';
import { Config, configs } from './PrecompileConfig';
@Builder
function webBuilder(data: BuilderData) {
Web({ src: data.url, controller: data.controller })
.onControllerAttached(() => {
precompile(data.controller, configs, data.context);
})
.fileAccess(true) // 允许访问本地文件
}
export const precompileWebview = wrapBuilder<BuilderData[]>(webBuilder);
// 预编译函数
export const precompile = async (
controller: WebviewController,
configs: Array<Config>,
context: UIContext
) => {
for (const config of configs) {
// 从rawfile读取本地JS文件
let content = await readRawFile(config.localPath, context);
try {
// 执行预编译
await controller.precompileJavaScript(
config.url,
content,
config.options
);
console.error('precompile successfully!');
} catch (err) {
console.error('precompile failed: ' + err);
}
}
}
步骤4:资源配置
// PrecompileConfig.ets
import { webview } from '@kit.ArkWeb'
export interface Config {
url: string, // 资源URL
localPath: string, // 本地资源路径
options: webview.CacheOptions
}
export let configs: Config[] = [
{
url: 'https://www.example.com/example.js',
localPath: 'example.js',
options: {
responseHeaders: [
{ headerKey: 'E-Tag', headerValue: 'aWO42N9P9dG/5xqYQCxsx+vDOoU=' },
{ headerKey: 'Last-Modified', headerValue: 'Wed, 21 Mar 2025 10:38:41 GMT' }
]
}
}
]
步骤5:业务Web组件
// BusinessWebview.ets
import { BuilderData } from './DynamicComponent';
@Builder
function webBuilder(data: BuilderData) {
Web({ src: data.url, controller: data.controller })
.cacheMode(CacheMode.Default) // 使用默认缓存模式
}
export const businessWebview = wrapBuilder<BuilderData[]>(webBuilder);
步骤6:页面中使用
// Index.ets
import { webview } from '@kit.ArkWeb';
import { NodeController } from '@kit.ArkUI';
import { createNode } from './DynamicComponent';
import { precompileWebview } from './PrecompileWebview';
import { businessWebview } from './BusinessWebview';
@Entry
@Component
struct Index {
@State precompileNode: NodeController | undefined = undefined;
precompileController: webview.WebviewController = new webview.WebviewController();
@State businessNode: NodeController | undefined = undefined;
businessController: webview.WebviewController = new webview.WebviewController();
aboutToAppear(): void {
// 初始化预编译组件
this.precompileNode = createNode(precompileWebview, {
url: 'https://www.example.com/empty.html',
controller: this.precompileController,
context: this.getUIContext()
});
}
build() {
Column() {
// 点击按钮加载业务页面
Button('加载页面')
.onClick(() => {
this.businessNode = createNode(businessWebview, {
url: 'https://www.example.com/business.html',
controller: this.businessController,
context: this.getUIContext()
});
})
// 显示业务Web组件
NodeContainer(this.businessNode);
}
}
}
4. 缓存更新机制
要更新已生成的编译字节码,只需修改CacheOptions中responseHeaders的E-Tag或Last-Modified值,再次调用precompileJavaScript接口即可。
五、离线资源免拦截注入
1. 原理
将图片、样式表、脚本等资源提前注入到内核内存缓存中,避免网络请求。
2. 核心API
webviewController.injectOfflineResources(
resources: OfflineResourceMap[]
)
3. 实现示例
步骤1:资源配置
// Resource.ets
import { webview } from '@kit.ArkWeb';
export interface ResourceConfig {
urlList: Array<string>, // 资源URL列表
type: webview.OfflineResourceType, // 资源类型
responseHeaders: Array<Header>, // 响应头
localPath: string // 本地资源路径
}
export const resourceConfigs: ResourceConfig[] = [
{
localPath: 'example.png',
urlList: [
'https://www.example.com/',
'https://www.example.com/path1/example.png',
'https://www.example.com/path2/example.png',
],
type: webview.OfflineResourceType.IMAGE,
responseHeaders: [
{ headerKey: 'Cache-Control', headerValue: 'max-age=1000' },
{ headerKey: 'Content-Type', headerValue: 'image/png' },
]
},
{
localPath: 'example.js',
urlList: ['https://www.example.com/example.js'],
type: webview.OfflineResourceType.CLASSIC_JS,
responseHeaders: [
{ headerKey: 'Cross-Origin', headerValue: 'anonymous' }
]
}
];
步骤2:资源注入组件
// InjectWebview.ets
import { webview } from '@kit.ArkWeb';
import { resourceConfigs } from './Resource';
import { BuilderData } from './DynamicComponent';
@Builder
function webBuilder(data: BuilderData) {
Web({ src: data.url, controller: data.controller })
.onControllerAttached(async () => {
try {
// 注入离线资源
data.controller.injectOfflineResources(
await getData(data.context)
);
} catch (err) {
console.error('error: ' + err.code + ' ' + err.message);
}
})
.fileAccess(true)
}
export const injectWebview = wrapBuilder<BuilderData[]>(webBuilder);
export async function getData(context: UIContext) {
const resourceMapArr: webview.OfflineResourceMap[] = [];
for (let config of resourceConfigs) {
let buf: Uint8Array = new Uint8Array(0);
if (config.localPath) {
// 读取本地文件
buf = await readRawFile(config.localPath, context);
}
resourceMapArr.push({
urlList: config.urlList,
resource: buf,
responseHeaders: config.responseHeaders,
type: config.type,
});
}
return resourceMapArr;
}
六、技术对比
| 优化技术 | 优化级别 | 适用场景 | 执行时机 | 资源消耗 |
|---|---|---|---|---|
| 预解析预连接 | 域名级 | 首次访问、页面跳转 | 页面加载前 | 低 |
| 预加载 | 资源级 | 可预测的下一页 | 当前页面完成后 | 中 |
| 预获取POST | 请求级 | 表单提交、API调用 | 页面加载前后均可 | 中 |
| 预编译缓存 | 脚本级 | JS密集型页面 | 应用启动时 | 中高 |
| 离线资源注入 | 资源级 | 静态资源较多页面 | 应用启动时 | 高 |
七、建议
1. 分层优化
-
第一层:对首页使用
prepareForPageLoad()进行预连接 -
第二层:对核心JS使用
precompileJavaScript()预编译 -
第三层:对静态资源使用
injectOfflineResources()注入 -
第四层:对下一页使用
prefetchPage()预加载
2. 内存管理
-
及时使用
clearPrefetchedResource()清理不再需要的预获取资源 -
监控内存使用,避免过度预加载导致内存压力
3. 网络环境适配
-
在Wi-Fi环境下更激进地使用预加载和预获取
-
在移动网络下适当减少预加载量
从域名级到资源级的全方位加速方案:
-
预解析预连接:解决DNS查询和TCP握手延迟
-
预加载:提前获取页面资源,避免渲染阻塞
-
预获取POST请求:优化API调用性能
-
预编译缓存:减少JavaScript编译时间
-
离线资源注入:完全避免网络请求
通过合理组合使用这些技术,可以显著提升Web页面的加载速度,特别是在网络条件较差或页面资源较多的场景下。
更多推荐


所有评论(0)