这是咱们 01~10 连载的收尾篇。前面 9 篇,我们逐个拆解 ArkWeb 的独立能力:生命周期、JS 通信、本地资源、调试缓存、请求拦截、窗口新开、文件上传、权限接管、内存优化。

但在真实项目开发里,不可能每次新建页面都复制一大段 ArkWeb 样板代码。复制代码容易漏写销毁逻辑、忘记权限回调、各个页面写法不统一,后期维护非常痛苦。
这一篇,我们把前面所有知识点整合,封装一个可复用、开箱即用、生产稳定的 ArkWeb 公共 Web 容器组件。一处封装,全项目多处直接调用。

我之前见过不少团队,ArkWeb 代码散在各个页面,改一个公共逻辑(比如统一拦截请求、统一权限处理),要去十几个页面挨个改。封装公共组件之后,只需要改这一处,所有页面自动生效。

核心误区:简单套一层自定义组件不等于封装。必须把控制器实例管理、生命周期销毁、回调统一注册全部封装在内,否则依然会出现内存泄漏、回调错乱。

在这里插入图片描述

核心概念一句话讲清

基于 ArkWeb 封装自定义通用组件,把请求拦截、window.open、文件选择、权限申请、内存销毁这些通用逻辑全部内置到组件内部。业务页面只需要传入 url,不用重复写一大堆回调代码。

类比:原生 ArkWeb 是毛坯房,我们封装的 Web 容器,是装好水电门窗的精装房,业务直接拎包入住。

前置条件:HarmonyOS7,DevEco Studio,基于前面章节所有能力整合。

第一步:封装通用 Web 容器组件 WebContainer.ets

import { ArkWeb, ArkWebController, WebResourceRequest, WebResourceResponse, WebNewWindowRequest, FileSelectorResult, WebPermissionRequest, WebPermissionType } from '@kit.ArkWeb';
import { pick } from '@kit.FilePicker';
import { permissionManager } from '@kit.AbilityKit';

@Component
export struct WebContainer {
  // 外部传入参数:网页地址
  @Param webUrl: string;
  // 内部控制器,组件私有,不暴露给外部
  private controller: ArkWebController = new ArkWebController();
  // 弹窗新页面状态,用于window.open
  @State showNewWeb: boolean = false;
  @State newPageUrl: string = "";
  private newWebCtrl: ArkWebController = new ArkWebController();

  aboutToAppear() {
    const ctrl = this.controller;
    ctrl.javascriptEnabled = true;
    // 统一开启调试,打包上线时可通过参数控制关闭
    ctrl.webDebuggingAccess = true;

    // 1. 请求拦截(全局统一接口mock、资源劫持)
    ctrl.onInterceptRequest = (request: WebResourceRequest): WebResourceResponse | null => {
      const url = request.requestUrl;
      console.info("公共容器拦截请求:", url);
      return null;
    }

    // 2. window.open 新开窗口接管
    ctrl.onNewWindow = (req: WebNewWindowRequest): boolean => {
      this.newPageUrl = req.requestUrl;
      this.showNewWeb = true;
      return true;
    }

    // 3. H5文件上传选择器
    ctrl.onShowFileSelector = async (): Promise<FileSelectorResult> => {
      const res = await pick({ MIMEType: pick.MIMEType.IMAGE, maxSelectNumber: 1 });
      return { uris: res.uris ?? [] }
    }

    // 4. H5相机/定位权限统一接管
    ctrl.onPermissionRequest = async (req: WebPermissionRequest) => {
      for (const permType of req.permissionTypes) {
        if (permType === WebPermissionType.CAMERA) {
          const result = await permissionManager.requestPermissions("EntryAbility", ["ohos.permission.CAMERA"]);
          result.grantResults[0] === 0 ? req.grant(permType) : req.deny(permType);
        } else if (permType === WebPermissionType.GEOLOCATION) {
          const result = await permissionManager.requestPermissions("EntryAbility", ["ohos.permission.LOCATION"]);
          result.grantResults[0] === 0 ? req.grant(permType) : req.deny(permType);
        }
      }
    }
  }

  aboutToDisappear() {
    // 统一内置销毁逻辑,杜绝内存泄漏
    this.controller.stop();
    this.controller.clearCache(false);
    this.controller.destroy();
    // 新页面控制器同步销毁
    this.newWebCtrl.stop();
    this.newWebCtrl.destroy();
  }

  build() {
    Column() {
      ArkWeb({
        src: this.webUrl,
        controller: this.controller
      })
      .width('100%')
      .layoutWeight(1)

      if (this.showNewWeb) {
        Column() {
          Row() {
            Button("关闭新页面")
              .onClick(() => this.showNewWeb = false)
          }
          ArkWeb({
            src: this.newPageUrl,
            controller: this.newWebCtrl
          })
          .width('100%')
          .layoutWeight(1)
        }
        .width('90%')
        .height('80%')
        .border({ width: 1, color: "#cccccc" })
      }
    }
  }
}

在这里插入图片描述

第二步:业务页面直接引用封装好的组件

业务页面代码变得极度简洁,只需要传 url,不用写一堆回调:

import { WebContainer } from './WebContainer.ets'

@Entry
@Component
struct BusinessWebPage {
  build() {
    WebContainer({ webUrl: $rawfile("index.html") })
  }
}

关键说明:

  1. 所有复杂回调全部藏在 WebContainer 组件内部,业务层不用关心底层 ArkWeb 细节。
  2. 控制器定义在组件内部,每个 WebContainer 实例独立控制器,不会全局复用造成内存问题。
  3. aboutToDisappear 内置完整销毁流程,业务页面退出自动释放内核,业务开发不会忘记写 destroy。

在这里插入图片描述

第三步:扩展组件,增加可配置参数(按需开关能力)

直接硬编码写死所有能力不够灵活,增加 @Param 参数,外部控制调试开关、缓存模式:

@Component
export struct WebContainer {
  @Param webUrl: string;
  @Param enableDebug: boolean = false;
  @Param cacheMode: WebCacheMode = WebCacheMode.NORMAL;
  // ...其余代码省略

  aboutToAppear() {
    const ctrl = this.controller;
    ctrl.webDebuggingAccess = this.enableDebug;
    ctrl.cacheMode = this.cacheMode;
  }
}

业务调用时自定义配置示例:

// 开发环境开启调试,禁用缓存
WebContainer({ webUrl: "https://xxx.test.com", enableDebug:true, cacheMode:WebCacheMode.NO_CACHE })

在这里插入图片描述

高频踩坑清单(封装时最容易踩的坑)

  1. ❌ 把 ArkWebController 定义成全局单例,多个 WebContainer 共用
    ✅ 控制器写在组件内部,组件实例销毁,控制器跟着销毁,避免多个页面互相干扰。
  2. ❌ 封装组件不写 aboutToDisappear 销毁逻辑
    ✅ 封装最大意义就是统一内存释放,漏掉销毁,封装毫无价值,依然泄漏。
  3. ❌ 在组件回调里向外传递 this 引用,外部长期持有 controller
    ✅ 尽量不要把 controller 暴露给业务页面,外部直接操作 controller 容易打乱生命周期。
  4. ❌ 多个回调之间互相依赖,修改一处逻辑导致其他功能失效
    ✅ 每次修改公共容器,一定要测试:文件上传、权限、window.open、请求拦截全部流程。

在这里插入图片描述

全系列总结

到这里,ArkWeb 混合开发手记 01~10 正式完结。
整套连载从最基础页面加载、生命周期起步,一路覆盖 JS 通信、rawfile 资源、调试缓存、网络拦截、新开窗口、文件上传、系统权限、内存性能,最后落地到项目工程化封装。
从零到生产可用,一套完整鸿蒙混合 H5 开发全链路实战。
你可以直接基于这个 WebContainer 容器,开发自己的鸿蒙混合应用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐