---

目录


一、概述

在 HarmonyOS 的 Stage 模型架构下,ArkWeb 作为原生支持的 Web 组件,已成为构建混合应用界面的核心引擎。它不仅承载前端页面渲染,更深度集成于 Native 生命周期中,实现 JS 与 Native 的双向通信、资源调度与安全隔离。

ArkWeb 的核心价值体现在三个层面:

  • 生态兼容:完整兼容标准 HTML5 / CSS3 / JavaScript 规范,现有 Web 应用可低成本迁移至 HarmonyOS 生态
  • 原生融合:深度绑定 Ability 生命周期与系统能力,Web 页面可直接调用相机、定位、文件系统等原生服务
  • 安全隔离:运行于独立沙箱进程,通过明确的桥接接口与 Native 交互,从架构层面防范越权访问与注入攻击

本指南系统性整合了 Web 组件全链路核心能力,可直接落地为生产级 ArkWeb 应用开发方案。


二、ArkWeb 生命周期:与 Ability 无缝协同的完整生命周期链

ArkWeb 并非独立运行的浏览器标签页,而是作为 Page Ability 的 UI 组件嵌入应用上下文。其生命周期与宿主 Ability 完全绑定,形成「Ability → Web 组件」的嵌套控制流。正确理解和管理生命周期是构建稳定、高效混合应用的基础。

2.1 生命周期状态流转

Ability 创建
    ↓
onCreate() → Web 实例初始化、基础配置
    ↓
onForeground() → 恢复渲染、恢复定时器、重建连接
    ↓
[ 用户交互阶段 ]
    ↓
onBackground() → 暂停渲染、释放非必要资源、关闭长连接
    ↓
onDestroy() → 清理事件监听、取消网络请求、释放内存
    ↓
Ability 销毁

2.2 各阶段最佳实践

onCreate() — 初始化阶段

当 Page Ability 被创建时,ArkWeb 实例同步初始化。此时应完成基础配置,确保 Web 组件具备运行所需的全部环境。

import web from '@ohos.web';
import web_webview from '@ohos.web.webview';

@Entry
@Component
struct WebPage {
  private webController: web_webview.WebviewController = new web_webview.WebviewController();

  aboutToAppear() {
    // 初始化 Web 配置
    const config = new web_webview.WebConfig();
    config.setJavaScriptEnabled(true);
    config.setDomStorageEnabled(true);
    config.setDatabaseEnabled(true);
    config.setFileAccessEnabled(true);
    config.setAllowFileAccess(true);
    this.webController.setWebConfig(config);
  }

  build() {
    Column() {
      Web({ src: 'https://example.com', controller: this.webController })
        .width('100%')
        .height('100%');
    }
  }
}

关键配置项清单:

配置项 推荐值 说明
javascriptEnabled true 业务需要动态交互时开启,纯展示类页面建议关闭以提升安全性
domStorageEnabled true 启用 localStorage / sessionStorage,支持前端状态持久化
databaseEnabled true 启用 WebSQL / IndexedDB,适用于离线数据存储场景
fileAccessEnabled 按需 仅在需要文件上传或本地资源访问时开启
mixedContentMode MIXED_CONTENT_NEVER_ALLOW 生产环境建议严格禁止混合内容,避免安全风险
onForeground() — 前台恢复

页面进入前台时触发。此时应恢复 Web 内容的动画、重新连接 WebSocket、恢复定时器,确保用户交互体验连续。

onPageShow() {
  // 恢复 Web 渲染与脚本执行
  this.webController.resume();

  // 通知前端页面恢复活跃状态
  this.webController.runJavaScript(
    'document.dispatchEvent(new CustomEvent("appForeground"));'
  );

  // 重建 WebSocket 连接(如需要)
  this.reconnectWebSocket();

  // 恢复视频播放
  this.webController.runJavaScript(
    'document.querySelectorAll("video").forEach(v => v.play());'
  );
}
onBackground() — 后台暂停

页面退至后台时触发。应暂停非必要资源,如停止视频播放、关闭长连接、释放内存缓存,降低系统负载。

onPageHide() {
  // 暂停 Web 渲染与脚本执行
  this.webController.pause();

  // 通知前端页面进入后台
  this.webController.runJavaScript(
    'document.dispatchEvent(new CustomEvent("appBackground"));'
  );

  // 暂停所有媒体播放
  this.webController.runJavaScript(`
    document.querySelectorAll("video, audio").forEach(el => el.pause());
  `);

  // 关闭 WebSocket 长连接
  this.closeWebSocket();

  // 清理内存缓存
  this.webController.clearCache(true);
}
onDestroy() — 销毁回收

Ability 销毁时,ArkWeb 实例被彻底回收。此时需手动清理所有 JS 端事件监听、取消网络请求、释放本地存储引用,避免内存泄漏。

aboutToDisappear() {
  // 移除所有 JS 桥接接口
  this.webController.removeJavaScriptInterface('NativeBridge');

  // 停止当前页面加载
  this.webController.stop();

  // 清除历史记录
  this.webController.clearHistory();

  // 清除缓存(根据业务需要)
  this.webController.clearCache(true);

  // 移除所有事件监听
  // 注意:ArkUI 组件销毁时会自动解绑,但自定义引用需手动清理
  this.webController = null;
}

2.3 生命周期管理的关键实践

onBackground() 中调用 webController.pause(),在 onForeground() 中调用 webController.resume(),可显式控制 Web 内容的渲染与脚本执行状态,大幅提升系统资源利用率。

后台行为对照表:

资源类型 pause() 自动处理 建议手动处理
JavaScript 执行 暂停定时器与动画 保存页面状态、暂停业务逻辑
视频 / 音频播放 暂停渲染 释放媒体解码器资源
WebSocket 连接 可能断开 主动关闭并保存重连信息
Canvas / WebGL 暂停渲染循环 保存绘制状态
内存缓存 部分释放 根据策略清理缓存数据

三、基本属性与核心事件:精准控制 Web 内容行为

ArkWeb 提供分层的属性与事件机制,支持开发者实现对 Web 页面的细粒度全流程管控。

3.1 核心配置属性

属性 类型 默认值 说明 适用场景
src string - 设置加载的 URL 或本地 HTML 路径 加载远程网页或本地 www/index.html
javascriptEnabled boolean true 是否允许执行 JavaScript 启用动态交互功能,关闭可显著提升安全性
domStorageEnabled boolean false 是否启用 localStorage / sessionStorage 持久化保存用户偏好、免登录状态
databaseEnabled boolean false 是否启用 WebSQL / IndexedDB 数据库 离线应用、本地数据缓存
fileAccessEnabled boolean false 是否允许访问本地文件系统 支持网页端文件上传、离线资源加载
allowFileAccess boolean true 是否允许加载 file:// 协议资源 本地开发调试、加载预先打包的静态本地页面
mixedContentMode string MIXED_CONTENT_NEVER_ALLOW 混合内容兼容策略 兼容 HTTPS 主页面加载非加密 HTTP 子资源
textZoomRatio number 100 文字缩放比例(%) 适配不同视力需求、阅读模式
cacheMode string LOAD_DEFAULT 缓存模式 离线阅读、强制刷新等场景
userAgent string 系统默认 自定义 User-Agent 字符串 统计分析、兼容特定站点

缓存模式说明:

模式 行为
LOAD_DEFAULT 默认策略,有缓存且未过期则使用缓存,否则从网络加载
LOAD_CACHE_ELSE_NETWORK 只要有缓存就使用,即使过期也使用;无缓存才从网络加载
LOAD_NO_CACHE 不使用缓存,全部从网络加载
LOAD_CACHE_ONLY 只使用缓存,不访问网络(完全离线模式)

3.2 核心事件与处理方案

事件 触发时机 最优处理逻辑
onPageBegin 页面发起加载请求时 启动全局加载动画,禁用操作类按钮避免重复提交
onPageEnd 页面完全加载渲染完成 隐藏加载动画,恢复交互权限,注入自定义 JS 增强能力
onErrorReceive 页面加载发生底层错误 跳转至自定义离线页面,内置重试机制同时上报埋点日志
onTitleReceive 页面标题标签更新时 实时同步更新应用顶部导航栏标题
onHttpErrorReceive 服务端返回非 200 状态码 展示适配应用设计风格的专属错误页
onJsAlert / onJsConfirm / onJsPrompt 网页调用原生弹窗 API 时 拦截替换为应用自研弹窗组件,保证全场景 UI 体验一致性
onProgressChange 页面加载进度更新时 更新进度条状态,给用户明确的加载反馈
onRefreshAccessedHistory 历史记录变化时 更新前进/后退按钮的可用状态

3.3 完整配置示例

import web_webview from '@ohos.web.webview';

@Entry
@Component
struct WebPage {
  private webController: web_webview.WebviewController = new web_webview.WebviewController();
  @State isLoading: boolean = false;
  @State pageTitle: string = '';
  @State canGoBack: boolean = false;
  @State canGoForward: boolean = false;

  build() {
    Column() {
      // 顶部导航栏
      Row() {
        Button('←')
          .enabled(this.canGoBack)
          .onClick(() => this.webController.back());
        Text(this.pageTitle)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis });
        Button('→')
          .enabled(this.canGoForward)
          .onClick(() => this.webController.forward());
      }
      .width('100%')
      .height(48)
      .padding({ left: 12, right: 12 });

      // 加载进度条
      if (this.isLoading) {
        Progress({ value: 50, total: 100, type: ProgressType.Linear })
          .width('100%')
          .height(2);
      }

      // Web 组件
      Web({ src: 'https://example.com', controller: this.webController })
        .width('100%')
        .layoutWeight(1)
        .javaScriptEnable(true)
        .domStorageAccess(true)
        .fileAccess(true)
        .mixedMode(MixedMode.All)
        .onPageBegin(() => {
          this.isLoading = true;
        })
        .onPageEnd(() => {
          this.isLoading = false;
          // 注入全局样式脚本统一页面基础风格
          this.webController.runJavaScript(`
            document.body.style.backgroundColor = '#f5f5f5';
            document.body.style.fontFamily = '-apple-system, sans-serif';
          `);
          // 更新导航状态
          this.updateNavigationState();
        })
        .onErrorReceive((error) => {
          console.error('Web 加载错误:', error.errorCode, error.errorMsg);
          this.isLoading = false;
          // 加载本地预存的离线错误页面
          this.webController.loadUrl('file:///data/storage/el2/distributedfiles/error.html');
        })
        .onTitleReceive((event) => {
          this.pageTitle = event.title;
        })
        .onHttpErrorReceive((event) => {
          console.warn('HTTP 错误:', event.statusCode, event.requestUrl);
          if (event.statusCode === 404) {
            this.showCustomErrorPage('404 页面不存在');
          } else if (event.statusCode >= 500) {
            this.showCustomErrorPage('服务器异常,请稍后重试');
          }
        })
        .onProgressChange((event) => {
          // 更新加载进度(如使用自定义进度条)
          console.debug('加载进度:', event.newProgress + '%');
        })
        .onRefreshAccessedHistory(() => {
          this.updateNavigationState();
        })
        .onJsAlert((event) => {
          // 拦截 JS Alert,替换为原生弹窗
          AlertDialog.show({
            title: '提示',
            message: event.message,
            confirm: {
              value: '确定',
              action: () => event.result.handleConfirm()
            }
          });
          return true; // 表示已处理,不使用默认弹窗
        })
        .onJsConfirm((event) => {
          AlertDialog.show({
            title: '确认',
            message: event.message,
            primaryButton: {
              value: '取消',
              action: () => event.result.handleCancel()
            },
            secondaryButton: {
              value: '确定',
              action: () => event.result.handleConfirm()
            }
          });
          return true;
        });
    }
    .width('100%')
    .height('100%');
  }

  private updateNavigationState(): void {
    this.canGoBack = this.webController.accessBackward();
    this.canGoForward = this.webController.accessForward();
  }

  private showCustomErrorPage(message: string): void {
    // 自定义错误页展示逻辑
  }
}

关于示例域名的说明https://example.com 仅用于技术文档示例场景,无需额外申请使用权限,请勿在正式生产业务中直接部署运行。


四、Web 渲染与布局:适配多设备的响应式设计策略

ArkWeb 搭载系统内置 WebKit 渲染引擎,布局行为完全兼容标准 Web 规范,为适配 HarmonyOS 覆盖的手机、平板、智慧屏等全品类设备,需遵循核心开发原则。

4.1 强制声明视口元标签

在 HTML 的 <head> 内添加视口规则,避免页面自动缩放,确保页面以原始比例渲染:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">

关键参数说明:

参数 作用 推荐值
width=device-width 视口宽度等于设备宽度 必设
initial-scale=1.0 初始缩放比例为 1:1 必设
maximum-scale=1.0 禁止用户放大 业务需要时设为 1.0
user-scalable=no 禁止用户手动缩放 应用内页面建议禁用
viewport-fit=cover 适配刘海屏/挖孔屏的安全区域 全面屏设备必设

4.2 优先使用弹性布局方案

全量采用 Flexbox + CSS Grid 实现页面结构,避免硬编码固定像素值,保证内容自适应任意尺寸屏幕。

/* 推荐:弹性布局 */
.container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.content {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  padding: 16px;
}

/* 避免:固定像素布局 */
/* ❌
.box { width: 320px; height: 200px; margin-left: 20px; }
*/

4.3 分级媒体查询适配

针对不同设备形态定制专属样式,确保在各尺寸下都有最优体验:

/* 基础样式:手机端优先 */
.container {
  padding: 12px;
  font-size: 14px;
}

.btn {
  padding: 10px 16px;
  font-size: 14px;
  min-height: 44px; /* 触控目标最小尺寸 */
}

/* 平板及中大屏设备 */
@media (min-width: 768px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 24px;
    font-size: 16px;
  }

  .grid-layout {
    grid-template-columns: repeat(3, 1fr);
    gap: 24px;
  }
}

/* 智慧屏超大屏设备,优化远距离触控交互 */
@media (min-width: 1920px) {
  .container {
    max-width: 1600px;
    padding: 40px;
    font-size: 20px;
  }

  .btn {
    font-size: 24px;
    padding: 20px 32px;
    min-height: 64px;
    border-radius: 12px;
  }

  /* 增大触控热区 */
  .clickable {
    min-width: 80px;
    min-height: 80px;
  }
}

/* 横屏适配 */
@media (orientation: landscape) and (max-height: 500px) {
  /* 矮屏横向布局调整 */
  .container {
    padding: 8px;
  }
}

4.4 规避兼容性坑点

position: fixed 兼容性问题:

部分低版本 HarmonyOS 设备对 position: fixed 支持存在缺陷,尤其在虚拟键盘弹出时可能出现布局偏移。推荐使用 position: absolute 配合外层滚动容器实现悬浮效果:

/* 不推荐:直接使用 fixed */
/* ❌
.header { position: fixed; top: 0; left: 0; right: 0; }
*/

/* 推荐:外层滚动容器 + absolute 定位 */
.page-wrapper {
  position: relative;
  height: 100vh;
  overflow: hidden;
}

.scroll-content {
  height: 100%;
  overflow-y: auto;
  padding-top: 56px; /* 头部高度 */
  -webkit-overflow-scrolling: touch; /* 流畅滚动 */
}

.fixed-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 56px;
  z-index: 100;
}

其他常见兼容性问题:

问题 现象 解决方案
1px 边框模糊 高清屏下边框显示模糊 使用 0.5px 或 CSS transform: scaleY(0.5)
点击高亮 点击元素出现蓝色/灰色遮罩 设置 -webkit-tap-highlight-color: transparent
滚动卡顿 页面滚动不流畅 添加 -webkit-overflow-scrolling: touch
输入框默认样式 表单元素样式不一致 使用 appearance: none 重置后自定义
字体渲染差异 不同设备字体粗细不一致 明确指定 font-familyfont-weight

4.5 鸿蒙系统特性适配

暗色模式支持:

/* 跟随系统暗色模式 */
@media (prefers-color-scheme: dark) {
  body {
    background-color: #1a1a1a;
    color: #ffffff;
  }
}

/* 也可通过 JS 桥接主动获取系统主题 */

安全区域适配:

/* 使用 CSS 环境变量适配刘海屏、底部横条等 */
.page {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

五、JavaScript 双向通信:打通前端与原生能力

ArkWeb 原生支持双向跨层调用,打通前端生态与系统原生能力,实现能力互补。这是混合开发模式的核心技术基础。

5.1 Native → Web:主动注入控制脚本

onPageEnd 回调中注入全局方法,实现原生层对网页的动态管控。

基础用法:

// 注入主题切换方法
this.webController.runJavaScript(`
  window.NativeBridge = window.NativeBridge || {};
  window.NativeBridge.setTheme = function(color) {
    document.body.style.backgroundColor = color;
    document.documentElement.setAttribute('data-theme', color);
  };
`).then((result) => {
  console.info('JS 注入结果:', result);
});

后续调用方式:

// 原生层直接调用,实时切换网页全局主题
this.webController.runJavaScript('window.NativeBridge.setTheme("#1a1a1a");');

注入时机与策略:

注入时机 适用场景 注意事项
onPageBegin 需尽早执行的基础脚本(如全局配置) 此时 DOM 可能未就绪,避免操作 DOM
onPageEnd 操作 DOM、绑定事件的脚本 确保页面已加载完成
业务触发时 按需注入功能模块 避免重复注入,做好幂等判断

批量注入工具封装:

class JsInjector {
  private injected: Set<string> = new Set();

  constructor(private controller: web_webview.WebviewController) {}

  async injectOnce(key: string, script: string): Promise<void> {
    if (this.injected.has(key)) return;
    await this.controller.runJavaScript(script);
    this.injected.add(key);
  }

  async injectFunction(name: string, body: string): Promise<void> {
    const script = `
      if (!window.${name}) {
        window.${name} = ${body};
      }
    `;
    return this.injectOnce(name, script);
  }

  reset(): void {
    this.injected.clear();
  }
}

// 使用
const injector = new JsInjector(this.webController);
await injector.injectFunction('setTheme', `function(color) {
  document.body.style.backgroundColor = color;
}`);

5.2 Web → Native:注册可调用 JS 桥接口

在 Native 层自定义注册专属桥接对象,前端页面即可直接调用系统原生能力。

基础注册方式:

// 定义桥接对象
class NativeBridge {
  // 显示原生 Toast
  showToast(msg: string): string {
    promptAction.showToast({ message: msg, duration: 2000 });
    return 'success';
  }

  // 获取设备信息
  getDeviceInfo(): string {
    return JSON.stringify({
      platform: 'HarmonyOS',
      version: DeviceInfo.getOSVersion(),
      brand: DeviceInfo.getBrand()
    });
  }

  // 异步方法:打开文件选择器
  async pickFile(mimeType: string): Promise<string> {
    try {
      const picker = new picker.PhotoViewPicker();
      const result = await picker.select({
        MIMEType: mimeType ? [mimeType] : ['image/*']
      });
      return result.photoUris?.[0] || '';
    } catch (e) {
      console.error('选择文件失败:', e);
      return '';
    }
  }
}

// 注册桥接对象
this.webController.registerJavaScriptProxy(
  new NativeBridge(),
  'NativeBridge',
  ['showToast', 'getDeviceInfo', 'pickFile']
);

前端调用方式:

// 同步方法直接调用
const result = NativeBridge.showToast('文件上传中...');
console.log(result); // "success"

// 异步方法返回 Promise(自动转换)
NativeBridge.pickFile('image/jpeg').then((uri) => {
  console.log('选择的文件 URI:', uri);
});

// 或使用 async/await
async function uploadImage() {
  const uri = await NativeBridge.pickFile('image/jpeg');
  if (uri) {
    // 处理上传逻辑
  }
}

5.3 双向通信架构设计

推荐的桥接层架构:

┌─────────────────────────────────────────────────┐
│  前端应用 (Web)                                  │
│  ┌─────────────┐    ┌─────────────────────┐    │
│  │ 业务逻辑层  │ ←→ │  Bridge 适配层       │    │
│  └─────────────┘    │  (参数校验/类型转换) │    │
│                      └─────────┬───────────┘    │
└────────────────────────────────┼────────────────┘
                                 │  JS Bridge
┌────────────────────────────────┼────────────────┐
│  Native 层 (ArkTS)             │                │
│  ┌─────────────────────────────▼──────────────┐ │
│  │  Bridge 注册与调度层                        │ │
│  │  (权限校验/参数合法性检查/埋点上报)         │ │
│  └─────────────┬──────────────────────────────┘ │
│                │                                │
│  ┌─────────────▼──────────────┐                 │
│  │  系统能力封装层             │                 │
│  │  (文件/相机/定位/存储等)   │                 │
│  └────────────────────────────┘                 │
└─────────────────────────────────────────────────┘

带安全校验的桥接实现:

class SecureNativeBridge {
  // 允许的来源域名白名单
  private allowedOrigins: string[] = [
    'https://yourdomain.com',
    'https://m.yourdomain.com'
  ];

  // 校验调用来源
  private validateOrigin(origin: string): boolean {
    return this.allowedOrigins.some(allowed => 
      origin === allowed || origin.startsWith(allowed + '/')
    );
  }

  // 安全的桥接方法
  async securePickFile(params: string): Promise<string> {
    try {
      const { mimeType, origin } = JSON.parse(params);

      // 来源校验
      if (!this.validateOrigin(origin)) {
        console.warn('非法调用来源:', origin);
        return JSON.stringify({ code: -1, message: 'Permission denied' });
      }

      // 参数校验
      if (!mimeType || typeof mimeType !== 'string') {
        return JSON.stringify({ code: -2, message: 'Invalid mimeType' });
      }

      // 执行实际操作
      const result = await this.doPickFile(mimeType);
      return JSON.stringify({ code: 0, data: result });
    } catch (e) {
      return JSON.stringify({ code: -3, message: 'Internal error' });
    }
  }

  private async doPickFile(mimeType: string): Promise<string> {
    // 实际文件选择逻辑
    return '';
  }
}

5.4 通信性能优化

批量数据传输:

避免高频次小数据量调用,尽量合并为批量操作:

// ❌ 不推荐:多次单独调用
// NativeBridge.setA(1);
// NativeBridge.setB(2);
// NativeBridge.setC(3);

// ✅ 推荐:批量调用
// NativeBridge.batchUpdate({ a: 1, b: 2, c: 3 });

异步回调模式:

对于耗时操作,使用回调模式而非轮询:

// Native 端
class NativeBridge {
  private callbacks: Map<string, Function> = new Map();

  // 注册回调
  on(event: string, callbackId: string): void {
    // 保存回调标识,事件触发时通过 runJavaScript 通知前端
    this.callbacks.set(event + ':' + callbackId, () => {});
  }

  // 触发事件
  emit(event: string, data: any): void {
    this.webController.runJavaScript(`
      window.NativeBridge.emit("${event}", ${JSON.stringify(data)});
    `);
  }
}

// 前端封装
class BridgeClient {
  private listeners: Map<string, Function[]> = new Map();

  on(event: string, callback: Function): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(callback);
  }

  emit(event: string, data: any): void {
    const callbacks = this.listeners.get(event) || [];
    callbacks.forEach(cb => cb(data));
  }
}

六、网页导航与浏览管理:构建流畅的导航体验

ArkWeb 提供完整的浏览器级导航控制能力,可快速定制符合应用业务逻辑的浏览体验。

6.1 基础导航控制

// 后退
if (this.webController.accessBackward()) {
  this.webController.back();
}

// 前进
if (this.webController.accessForward()) {
  this.webController.forward();
}

// 跳转到指定历史记录步长
this.webController.goBackOrForward(-2); // 后退两步

// 刷新当前页面
this.webController.refresh();

// 强制刷新(忽略缓存)
this.webController.refresh(true);

// 停止加载
this.webController.stop();

6.2 历史记录管理

获取历史列表:

// 获取历史列表
const historyList = this.webController.getHistoryList();
historyList.forEach((item, index) => {
  console.log(`历史记录[${index}]:`, item.title, item.url);
});

// 获取当前索引
const currentIndex = this.webController.getHistoryIndex();
console.log('当前页面索引:', currentIndex);

清除历史记录:

// 清除全部历史
this.webController.clearHistory();

6.3 自定义协议拦截

监听页面加载事件,拦截业务专属自定义协议,实现从网页跳转到本地原生页面的能力。

Web({ src: this.initialUrl, controller: this.webController })
  .onPageBegin((event) => {
    const url = event.url;

    // 拦截登录专属协议
    if (url.startsWith('myapp://auth')) {
      this.handleAuthProtocol(url);
      this.webController.stop(); // 终止网页默认加载流程
      return;
    }

    // 拦截商品详情协议
    if (url.startsWith('myapp://product')) {
      this.openProductDetail(url);
      this.webController.stop();
      return;
    }

    // 拦截外部链接,使用系统浏览器打开
    if (this.isExternalLink(url)) {
      this.openInSystemBrowser(url);
      this.webController.stop();
      return;
    }
  })
  .onLoadIntercept((event) => {
    // 更底层的请求拦截,可拦截 iframe、图片等资源加载
    const url = event.data.getRequestUrl();
    if (url.startsWith('myapp://')) {
      // 返回 true 表示拦截该请求
      return true;
    }
    return false;
  });

协议解析工具:

class ProtocolRouter {
  private routes: Map<string, (params: URLSearchParams) => void> = new Map();

  register(scheme: string, path: string, handler: (params: URLSearchParams) => void): void {
    this.routes.set(`${scheme}://${path}`, handler);
  }

  handle(url: string): boolean {
    try {
      const urlObj = new URL(url);
      const key = `${urlObj.protocol}//${urlObj.hostname}${urlObj.pathname}`;
      const handler = this.routes.get(key);
      if (handler) {
        handler(urlObj.searchParams);
        return true;
      }
      return false;
    } catch {
      return false;
    }
  }
}

// 使用
const router = new ProtocolRouter();
router.register('myapp', 'auth', (params) => {
  const redirectUrl = params.get('redirect');
  // 打开登录页
});
router.register('myapp', 'product', (params) => {
  const productId = params.get('id');
  // 打开商品详情
});

6.4 URL 加载策略

加载不同来源的内容:

// 加载远程 URL
this.webController.loadUrl('https://example.com/page');

// 加载本地资源
this.webController.loadUrl('file:///data/storage/el2/distributedfiles/index.html');

// 加载原始 HTML 内容
const htmlContent = `
  <!DOCTYPE html>
  <html>
    <head><title>本地页面</title></head>
    <body><h1>Hello ArkWeb</h1></body>
  </html>
`;
this.webController.loadData(htmlContent, 'text/html', 'utf-8');

// 加载带 base URL 的 HTML(解决相对路径问题)
this.webController.loadDataWithBaseURL(
  'https://example.com/',
  htmlContent,
  'text/html',
  'utf-8',
  ''
);

// Post 请求加载
this.webController.postUrl(
  'https://example.com/submit',
  new Uint8Array(Buffer.from('name=value&key=123'))
);

七、文件上传与下载:原生级安全控制

ArkWeb 提供完全可定制的文件传输能力,既兼容标准 Web 规范,又能接入 HarmonyOS 系统原生文件能力。

7.1 文件上传配置

基础配置:

ArkWeb 原生支持网页 <input type="file"> 标签的文件选择逻辑,只需提前将文件访问权限设置为 true 即可启用基础上传能力。

Web({ src: url, controller: this.webController })
  .fileAccess(true)        // 启用文件访问
  .allowFileAccess(true)   // 允许 file:// 协议

自定义文件选择:

如果要实现更高阶的业务体验,可以通过桥接接口自定义上传逻辑,直接调用系统原生文件选择器:

import picker from '@ohos.file.picker';

class NativeBridge {
  constructor(private webController: web_webview.WebviewController) {}

  // 打开文件选择器
  async openFilePicker(mimeType: string): Promise<string> {
    try {
      const documentPicker = new picker.DocumentViewPicker();
      const result = await documentPicker.select({
        MIMEType: mimeType ? [mimeType] : ['*/*']
      });
      return JSON.stringify({
        success: true,
        uris: result.documentUris || []
      });
    } catch (e) {
      console.error('文件选择失败:', e);
      return JSON.stringify({ success: false, error: e.message });
    }
  }

  // 选择图片
  async pickImage(maxCount: number): Promise<string> {
    try {
      const photoPicker = new picker.PhotoViewPicker();
      const result = await photoPicker.select({
        MIMEType: ['image/*'],
        maxSelectNumber: maxCount || 1
      });
      return JSON.stringify({
        success: true,
        uris: result.photoUris || []
      });
    } catch (e) {
      return JSON.stringify({ success: false, error: e.message });
    }
  }

  // 拍照上传
  async takePhoto(): Promise<string> {
    try {
      const image = await camera.takePhoto();
      return JSON.stringify({ success: true, uri: image.uri });
    } catch (e) {
      return JSON.stringify({ success: false, error: e.message });
    }
  }
}

前端集成示例:

<input type="file" id="fileInput" style="display:none">
<button onclick="uploadImage()">选择图片上传</button>

<script>
async function uploadImage() {
  try {
    const result = JSON.parse(await NativeBridge.pickImage(9));
    if (result.success) {
      // 处理选择的图片
      result.uris.forEach(uri => previewImage(uri));
      // 上传到服务器
      await uploadFiles(result.uris);
    }
  } catch (e) {
    console.error('上传失败:', e);
  }
}

function previewImage(uri) {
  const img = document.createElement('img');
  img.src = uri;
  document.body.appendChild(img);
}
</script>

7.2 文件下载管控

ArkWeb 不会自动处理下载任务,必须手动监听下载事件自定义全流程逻辑。

基础下载事件处理:

Web({ src: url, controller: this.webController })
  .onDownloadStart((event) => {
    const { url, userAgent, contentDisposition, mimetype, contentLength } = event;
    
    console.info('下载开始:', {
      url,
      mimetype,
      contentLength,
      filename: this.extractFilename(contentDisposition, url)
    });

    // 自定义下载业务逻辑
    this.handleDownload({
      url,
      filename: this.extractFilename(contentDisposition, url),
      mimeType: mimetype,
      size: contentLength
    });
  })

文件名提取工具:

private extractFilename(contentDisposition: string, url: string): string {
  // 从 Content-Disposition 中提取文件名
  if (contentDisposition) {
    const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
    if (match && match[1]) {
      let filename = match[1].replace(/['"]/g, '');
      // 处理中文文件名编码
      if (filename.startsWith("UTF-8''")) {
        filename = decodeURIComponent(filename.slice(7));
      }
      return filename;
    }
  }
  // 从 URL 中提取文件名
  const urlPath = new URL(url).pathname;
  return urlPath.split('/').pop() || 'download';
}

完整下载管理器实现:

import request from '@ohos.request';
import fs from '@ohos.file.fs';

class DownloadManager {
  private downloadTasks: Map<string, request.DownloadTask> = new Map();

  async startDownload(options: {
    url: string;
    filename: string;
    mimeType: string;
  }): Promise<string> {
    // 获取下载目录
    const context = getContext();
    const downloadDir = context.filesDir + '/Downloads';

    // 确保目录存在
    if (!fs.accessSync(downloadDir)) {
      fs.mkdirSync(downloadDir);
    }

    const filePath = `${downloadDir}/${options.filename}`;

    try {
      const config: request.DownloadConfig = {
        url: options.url,
        filePath: filePath,
        header: {
          'User-Agent': 'ArkWeb-App/1.0'
        }
      };

      const task = request.downloadFile(context, config);
      this.downloadTasks.set(options.url, task);

      // 监听下载进度
      task.on('progress', (receivedSize: number, totalSize: number) => {
        const progress = totalSize > 0 ? (receivedSize / totalSize * 100).toFixed(1) : '0';
        console.info(`下载进度:${progress}%`);
        // 通知前端更新进度
        this.notifyDownloadProgress(options.url, receivedSize, totalSize);
      });

      // 监听完成
      task.on('complete', () => {
        console.info('下载完成:', filePath);
        this.notifyDownloadComplete(options.url, filePath);
        this.downloadTasks.delete(options.url);
      });

      // 监听失败
      task.on('failed', (error) => {
        console.error('下载失败:', error);
        this.notifyDownloadFailed(options.url, error.message);
        this.downloadTasks.delete(options.url);
      });

      return filePath;
    } catch (e) {
      console.error('创建下载任务失败:', e);
      throw e;
    }
  }

  pauseDownload(url: string): void {
    const task = this.downloadTasks.get(url);
    if (task) {
      task.pause();
    }
  }

  resumeDownload(url: string): void {
    const task = this.downloadTasks.get(url);
    if (task) {
      task.resume();
    }
  }

  cancelDownload(url: string): void {
    const task = this.downloadTasks.get(url);
    if (task) {
      task.remove();
      this.downloadTasks.delete(url);
    }
  }

  private notifyDownloadProgress(url: string, received: number, total: number): void {
    // 通过 JS 桥接通知前端
  }

  private notifyDownloadComplete(url: string, path: string): void {
    // 通知前端下载完成
  }

  private notifyDownloadFailed(url: string, error: string): void {
    // 通知前端下载失败
  }
}

7.3 安全存储规范

所有下载文件默认存储在应用私有沙箱目录中,避免越权访问风险,完全符合 HarmonyOS 应用安全开发规范。

存储路径说明:

路径类型 位置 说明
应用文件目录 filesDir 应用私有,外部无法访问
缓存目录 cacheDir 系统空间不足时可能被清理
分布式文件 distributedFilesDir 支持跨设备同步
临时文件 系统临时目录 应用退出后可能被清理

文件权限管理:

// 仅在需要时申请文件读写权限
import picker from '@ohos.file.picker';

// 通过系统选择器访问文件(无需申请存储权限)
async function selectFile() {
  const documentPicker = new picker.DocumentViewPicker();
  const result = await documentPicker.select();
  // 返回的 URI 可直接用于文件读取
  return result.documentUris?.[0];
}

八、性能优化实践:打造极致加载与渲染体验

Web 页面的加载速度和渲染流畅度直接影响用户体验。以下是 ArkWeb 场景下经过验证的性能优化策略。

8.1 页面加载优化

资源预加载:

// 在 Ability 初始化时预创建 Web 实例和预加载资源
class WebPreloader {
  private preloadedWebView: web_webview.WebviewController | null = null;

  preload(): void {
    // 预热 Web 引擎(首次创建 Web 组件成本较高)
    this.preloadedWebView = new web_webview.WebviewController();
    // 预加载常见静态资源
    // this.preloadedWebView.loadUrl('about:blank');
  }

  getPreloaded(): web_webview.WebviewController | null {
    const instance = this.preloadedWebView;
    this.preloadedWebView = null;
    return instance;
  }
}

本地资源离线包:

将核心页面和静态资源打包到应用内,首次加载直接从本地读取,避免网络请求:

项目结构:
src/main/
  resources/
    rawfile/
      www/
        index.html
        css/
          main.css
        js/
          app.js
        images/
          logo.png
// 加载本地页面
const localPath = 'resource://RAWFILE/www/index.html';
this.webController.loadUrl(localPath);

资源缓存策略:

// 配置缓存模式
Web({ src: url, controller: this.webController })
  .cacheMode(web_webview.CacheMode.Default)
  .onPageEnd(() => {
    // 页面加载完成后预加载下一页可能需要的资源
    this.webController.runJavaScript(`
      const preloadLinks = [
        '/css/next-page.css',
        '/js/next-page.js'
      ];
      preloadLinks.forEach(href => {
        const link = document.createElement('link');
        link.rel = 'prefetch';
        link.href = href;
        document.head.appendChild(link);
      });
    `);
  });

8.2 渲染性能优化

减少重排重绘:

/* 使用 transform 和 opacity 实现动画,触发 GPU 加速 */
.element {
  transform: translateX(0);
  transition: transform 0.3s ease;
  will-change: transform;
}

.element.active {
  transform: translateX(100px);
}

/* 避免:使用 top/left 做动画(会触发重排) */
/* ❌
.element { position: relative; left: 0; transition: left 0.3s; }
.element.active { left: 100px; }
*/

图片优化:

<!-- 使用适当尺寸的图片,避免大图缩小显示 -->
<img src="image-320w.jpg" srcset="image-320w.jpg 320w, image-640w.jpg 640w"
     sizes="(max-width: 320px) 280px, 600px" alt="响应式图片">

<!-- 懒加载 -->
<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy" loading="lazy">

<!-- 使用 WebP 等现代格式 -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="图片">
</picture>

虚拟列表:

对于长列表,使用虚拟滚动只渲染可视区域内的元素:

class VirtualList {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
    this.init();
  }

  init() {
    // 设置总高度
    this.scroller = document.createElement('div');
    this.scroller.style.height = this.items.length * this.itemHeight + 'px';
    
    this.content = document.createElement('div');
    this.container.appendChild(this.scroller);
    this.scroller.appendChild(this.content);

    this.container.addEventListener('scroll', () => this.onScroll());
    this.render(0);
  }

  onScroll() {
    const scrollTop = this.container.scrollTop;
    const startIndex = Math.floor(scrollTop / this.itemHeight);
    this.render(startIndex);
  }

  render(startIndex) {
    const endIndex = Math.min(startIndex + this.visibleCount, this.items.length);
    const fragment = document.createDocumentFragment();
    
    for (let i = startIndex; i < endIndex; i++) {
      const item = document.createElement('div');
      item.style.height = this.itemHeight + 'px';
      item.style.transform = `translateY(${i * this.itemHeight}px)`;
      item.textContent = this.items[i];
      fragment.appendChild(item);
    }
    
    this.content.innerHTML = '';
    this.content.appendChild(fragment);
  }
}

8.3 内存优化

及时释放资源:

// 页面切换时清理 Web 资源
onPageHide() {
  // 暂停 Web
  this.webController.pause();
  
  // 清理缓存(根据策略)
  this.webController.clearCache(false); // false 表示不清除磁盘缓存
  
  // 通知前端释放内存
  this.webController.runJavaScript(`
    // 清理定时器
    if (window.__timers) {
      window.__timers.forEach(clearTimeout);
    }
    // 移除事件监听
    // 触发垃圾回收提示
    if (window.gc) window.gc();
  `);
}

图片内存管理:

// 前端:及时回收离屏图片
function releaseImages() {
  const images = document.querySelectorAll('img');
  images.forEach(img => {
    const rect = img.getBoundingClientRect();
    // 图片在视口外很远时释放
    if (rect.top > window.innerHeight * 3 || rect.bottom < -window.innerHeight * 2) {
      const originalSrc = img.src;
      img.dataset.src = originalSrc;
      img.src = 'placeholder.jpg'; // 替换为占位图
    }
  });
}

// 滚动时节流触发
let scrollTimer;
window.addEventListener('scroll', () => {
  clearTimeout(scrollTimer);
  scrollTimer = setTimeout(releaseImages, 200);
});

8.4 性能监控指标

指标 优秀值 良好值 需优化 测量方式
首屏加载时间 < 1s < 2s > 3s performance.timing
DOM 解析完成 < 500ms < 1s > 1.5s domContentLoaded
页面完全加载 < 2s < 3s > 5s load 事件
首帧渲染 < 300ms < 500ms > 1s first-contentful-paint
内存占用 < 100MB < 200MB > 300MB 系统工具
滑动帧率 60fps ≥ 45fps < 30fps DevTools

九、安全最佳实践:构建可信的 Web 运行环境

Web 组件是应用安全的重要边界,不当的配置可能导致数据泄露、越权访问等安全风险。

9.1 基础安全配置

Web({ src: url, controller: this.webController })
  // 按需启用 JavaScript,纯展示页面建议关闭
  .javaScriptEnable(false)
  // 禁用文件访问(不需要时)
  .fileAccess(false)
  .allowFileAccess(false)
  // 严格的混合内容策略
  .mixedMode(MixedMode.None)
  // 禁用数据库(不需要时)
  .databaseEnable(false)
  // 禁用 DOM 存储(不需要时)
  .domStorageAccess(false)
  // 设置内容安全策略
  // 通过 HTTP 响应头或 meta 标签设置 CSP

内容安全策略(CSP):

在服务端或 HTML 中设置严格的 CSP,防范 XSS 攻击:

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline'; 
               style-src 'self' 'unsafe-inline';
               img-src 'self' data: https:;
               connect-src 'self' https://api.yourdomain.com;">

9.2 JS 桥接安全

最小权限原则:

// ❌ 不推荐:直接暴露大量原生能力
// this.webController.registerJavaScriptProxy(fullApi, 'Native', getAllMethods(fullApi));

// ✅ 推荐:只暴露业务必需的最小接口
class MinimalBridge {
  // 仅开放业务必需的方法
  showToast(msg: string): void { /* ... */ }
  getVersion(): string { return '1.0.0'; }
}

输入校验:

class SecureBridge {
  // 所有输入参数做严格校验
  openPage(url: string): string {
    // 校验 URL 格式
    try {
      const urlObj = new URL(url);
      // 校验协议
      if (urlObj.protocol !== 'https:' && urlObj.protocol !== 'http:') {
        return JSON.stringify({ code: -1, message: 'Invalid protocol' });
      }
      // 校验域名白名单
      if (!this.isTrustedDomain(urlObj.hostname)) {
        return JSON.stringify({ code: -2, message: 'Untrusted domain' });
      }
      // 执行操作
      // ...
      return JSON.stringify({ code: 0 });
    } catch {
      return JSON.stringify({ code: -3, message: 'Invalid URL' });
    }
  }

  private isTrustedDomain(host: string): boolean {
    const trusted = ['yourdomain.com', 'api.yourdomain.com'];
    return trusted.some(domain => 
      host === domain || host.endsWith('.' + domain)
    );
  }
}

9.3 数据传输安全

HTTPS 强制:

// 生产环境禁用 HTTP
if (BuildInfo.isRelease()) {
  Web({ src: url, controller: this.webController })
    .mixedMode(MixedMode.None); // 禁止混合内容
}

敏感数据保护:

// 不在 URL 中传递敏感信息
// ❌ https://example.com?token=xxx&userid=yyy

// ✅ 使用 POST 请求或加密存储
// 通过 JS 桥接安全传递
this.webController.runJavaScript(`
  localStorage.setItem('authToken', '${encryptedToken}');
`);

9.4 安全事件响应

Web({ src: url, controller: this.webController })
  .onSslErrorEventReceive((event) => {
    // SSL 证书错误处理
    console.error('SSL 错误:', event.error);
    // 生产环境建议阻止加载
    // event.handler.cancel();
    // 仅在特殊场景下允许继续(不推荐)
    // event.handler.confirm();
  })
  .onSafeBrowsingHit((event) => {
    // 安全浏览检测到恶意网站
    console.warn('安全浏览警告:', event.threatType);
    // 阻止加载
    // event.callback(true);
  })

十、调试与排错:快速定位与解决问题

10.1 远程调试配置

ArkWeb 支持通过 Chrome DevTools 进行远程调试,配置方式如下:

// 开发环境开启调试
if (!BuildInfo.isRelease()) {
  this.webController.setWebDebuggingAccess(true);
}

调试步骤:

  1. 在设备上运行应用并打开 Web 页面
  2. 电脑上打开 Chrome 浏览器,访问 chrome://inspect
  3. 在设备列表中找到对应的 WebView 实例
  4. 点击 inspect 打开 DevTools

10.2 日志输出

Native 端日志:

import hilog from '@ohos.hilog';

const TAG = 'ArkWebDemo';
const DOMAIN = 0x0001;

// 各等级日志输出
hilog.debug(DOMAIN, TAG, 'Web 页面加载开始:%{public}s', url);
hilog.info(DOMAIN, TAG, '页面标题:%{public}s', title);
hilog.warn(DOMAIN, TAG, 'HTTP 错误码:%{public}d', statusCode);
hilog.error(DOMAIN, TAG, '加载失败:%{public}s', errorMsg);

前端日志转发:

将前端 console 日志转发到 Native 日志系统,便于统一排查:

// 注入日志转发脚本
this.webController.runJavaScript(`
  (function() {
    const originalLog = console.log;
    const originalError = console.error;
    const originalWarn = console.warn;
    
    console.log = function(...args) {
      originalLog.apply(console, args);
      if (window.NativeBridge && NativeBridge.log) {
        NativeBridge.log('INFO: ' + args.map(String).join(' '));
      }
    };
    
    console.error = function(...args) {
      originalError.apply(console, args);
      if (window.NativeBridge && NativeBridge.log) {
        NativeBridge.log('ERROR: ' + args.map(String).join(' '));
      }
    };
    
    console.warn = function(...args) {
      originalWarn.apply(console, args);
      if (window.NativeBridge && NativeBridge.log) {
        NativeBridge.log('WARN: ' + args.map(String).join(' '));
      }
    };
    
    // 捕获未处理的 Promise 异常
    window.addEventListener('unhandledrejection', function(e) {
      if (window.NativeBridge && NativeBridge.log) {
        NativeBridge.log('UNHANDLED_PROMISE: ' + e.reason);
      }
    });
    
    // 捕获全局错误
    window.addEventListener('error', function(e) {
      if (window.NativeBridge && NativeBridge.log) {
        NativeBridge.log('GLOBAL_ERROR: ' + e.message + ' at ' + e.filename + ':' + e.lineno);
      }
    });
  })();
`);

10.3 常见错误排查

错误现象 可能原因 排查步骤
页面空白 URL 错误 / 网络问题 / JS 报错 1. 检查 URL 是否正确
2. 查看 onErrorReceive 日志
3. 开启远程调试看 Console
页面加载缓慢 网络慢 / 资源过大 / 缓存未命中 1. 检查网络请求耗时
2. 查看资源大小
3. 验证缓存策略
JS 调用无响应 桥接未注册 / 参数类型错误 1. 确认注册方法名正确
2. 检查参数是否符合要求
3. 查看 Native 端日志
文件上传失败 权限未申请 / 文件路径错误 1. 检查 fileAccessEnabled 配置
2. 确认文件 URI 格式
3. 查看系统权限状态
页面布局错乱 CSS 兼容 / 视口配置 1. 检查 viewport meta 标签
2. 验证 CSS 属性兼容性
3. 检查是否使用了 fixed 定位
内存持续上涨 资源未释放 / 内存泄漏 1. 检查是否清理事件监听
2. 验证页面销毁逻辑
3. 用 Profiler 分析内存快照

10.4 性能调试工具

Chrome DevTools 常用面板:

面板 用途 关键指标
Network 网络请求分析 加载瀑布图、资源大小、请求耗时
Performance 性能分析 FPS、主线程占用、渲染耗时
Memory 内存分析 堆快照、内存泄漏检测、分配时序
Lighthouse 综合评分 性能、可访问性、最佳实践分数
Application 存储与缓存 localStorage、缓存状态、Service Worker

十一、常见问题与解决方案

11.1 页面加载类

Q: 本地 HTML 文件加载失败怎么办?

确保文件路径正确,ArkWeb 支持以下几种本地路径格式:

// 1. 资源文件路径(推荐)
this.webController.loadUrl('resource://RAWFILE/www/index.html');

// 2. 应用沙箱路径
this.webController.loadUrl('file://' + getContext().filesDir + '/index.html');

// 3. 分布式文件路径
this.webController.loadUrl('file://' + getContext().distributedFilesDir + '/index.html');

Q: 如何处理页面重定向?

页面重定向会正常触发 onPageBeginonPageEnd 事件,可通过事件回调中的 URL 判断重定向行为。如需拦截特定重定向,在 onPageBegin 中判断并调用 stop() 即可。

11.2 通信类

Q: JS 调用 Native 方法为什么没有返回值?

可能的原因:

  1. 方法未正确注册到 registerJavaScriptProxy 的方法列表中
  2. 方法是异步的,需要使用 Promise 回调
  3. 参数类型不匹配,导致方法未被正确调用

解决方案:确保注册的方法列表包含所有需要暴露的方法,异步方法返回 Promise,参数使用基本类型或 JSON 字符串传递。

Q: 如何处理大数据量传输?

对于超过 1MB 的数据,建议:

  1. 分片传输,每次传输不超过 256KB
  2. 使用文件中转,将数据写入文件后传递文件路径
  3. 采用 WebSocket 或其他长连接方案

11.3 文件操作类

Q: 为什么 <input type="file"> 点击无反应?

检查以下配置:

  • fileAccessEnabled 是否设置为 true
  • 应用是否有文件读取权限
  • 系统版本是否支持文件选择功能

如仍无法使用,建议使用 JS 桥接自定义文件选择逻辑。

Q: 下载的文件在哪里可以找到?

默认下载目录为应用沙箱内的文件目录,外部应用无法直接访问。如需让其他应用访问,需:

  1. 将文件复制到公共目录(需申请权限)
  2. 使用系统文件分享能力
  3. 通过 FilePicker 让用户主动选择保存位置

11.4 性能类

Q: 首次加载 Web 页面为什么很慢?

Web 引擎首次初始化需要一定时间(通常 500ms-1s),优化建议:

  1. 提前预创建 Web 实例
  2. 使用本地资源离线包
  3. 优化首屏资源体积
  4. 显示骨架屏或加载动画提升感知体验

Q: 滚动页面时卡顿怎么办?

  1. 检查是否有过多的 DOM 元素,考虑虚拟列表
  2. 避免在滚动事件中执行复杂计算
  3. 使用 will-changetransform 优化动画
  4. 减少图片尺寸和数量,使用懒加载

附录

A. 核心 API 速查表

分类 API 说明
加载 loadUrl(url) 加载 URL
加载 loadData(data, mimeType, encoding) 加载原始数据
加载 refresh() 刷新页面
加载 stop() 停止加载
导航 back() 后退
导航 forward() 前进
导航 accessBackward() 是否可后退
导航 accessForward() 是否可前进
导航 clearHistory() 清除历史
生命周期 resume() 恢复 Web
生命周期 pause() 暂停 Web
JS 通信 runJavaScript(script) 执行 JS
JS 通信 registerJavaScriptProxy(obj, name, methods) 注册桥接对象
JS 通信 removeJavaScriptProxy(name) 移除桥接对象
缓存 clearCache(includeDisk) 清除缓存
配置 setWebConfig(config) 设置 Web 配置

B. 版本兼容性

特性 API 10 API 11 API 12+
基础 Web 组件
JS 双向通信
文件上传下载
远程调试
Service Worker
WebGL 支持
多 Web 实例

本指南基于 HarmonyOS NEXT API 12 编写,不同版本间 API 可能存在差异,开发时请以官方文档为准。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐