前面8篇我们已经打通 ArkWeb 90% 的核心能力:页面渲染、生命周期、JSBridge、SPA路由、请求拦截、离线包、性能优化、内存防崩。

但 H5 表单上传、图片拍照、文件选择 是所有业务系统最后一道硬核门槛,也是鸿蒙混合开发 BUG 最多、兼容性最诡异 的模块。

你如果直接使用 ArkWeb 默认行为,一定会出现以下线上必现问题:

  • H5 的 input[type=file] 点击 完全无反应

  • 无法唤起相机拍照、无法唤起图库

  • 选择图片后 H5 接收不到文件流

  • 部分机型选择图片后页面卡死、白屏

  • 多文件选择失效、表单上传为空

  • 拍照后图片旋转、压缩失真、路径错乱

原因非常简单:HarmonyOS7 的 ArkWeb 禁止 H5 直接调用原生媒体与文件能力,必须由原生主动拦截文件选择弹窗,手动接管文件选择回调。

本篇给出 行业唯一完整可用方案:通过 onShowFileSelector 全局拦截 H5 上传请求,原生接管图库、相机、文件选择,回传给 H5,彻底解决所有上传兼容问题。

本篇结束后,你的混合项目将完全媲美原生APP上传体验。

一、核心原理:为什么H5上传在鸿蒙默认失效?

在安卓/iOS WebView 中,H5 的 input file 可以直接唤起系统弹窗。

但在 HarmonyOS7 ArkWeb 做了严格安全限制:

所有H5文件请求、媒体请求、拍照请求全部拦截,不会默认弹出任何系统弹窗。

系统把控制权完全交给原生,需要开发者手动实现:

  • 拦截 H5 上传触发事件

  • 原生自定义弹窗:拍照/图库/文件

  • 原生选取文件、拿到文件路径与buffer

  • 回传给 Web 内核,填充到 H5 文件表单

核心API:onShowFileSelector

这是整篇文章的核心,也是企业级混合开发的上传标准方案。

二、权限前置配置(缺一直接失效)

上传功能需要相机、图库、文件读写权限,必须在 module.json5 配置:

"requestPermissions": [
  { "name": "ohos.permission.READ_MEDIA" },
  { "name": "ohos.permission.CAMERA" },
  { "name": "ohos.permission.WRITE_MEDIA" }
]

同时代码中需要动态申请权限,否则低端机直接无响应。

三、核心实战:onShowFileSelector 完整拦截结构

当 H5 点击 input[type=file] 时,会触发该回调,携带关键参数:

  • accept:H5 限制的文件类型(image/*、video/*、*/*)

  • isMultiple:是否支持多文件选择

  • resultCallback:最重要,把原生选中的文件回传给H5

只要不调用 resultCallback,H5 永远处于加载卡死状态。

四、完整可上线代码:图片上传(图库+拍照)双模式

下面是可直接上线的 ArkWeb+H5图片上传完整代码,兼容单图、多图、拍照、图库。

import { Web, WebController } from '@kit.ArkWeb';
import { picker } from '@kit.MediaPickerKit';
import { camera } from '@kit.CameraKit';
import { fileIo } from '@kit.BasicServicesKit';
import { JsBridgeModel, SpaRouteItem } from './JsBridgeModel';
import { router } from '@ohos/router';

@Entry
@Component
struct ArkWebUploadPage {
  private webController: WebController = new WebController();
  private jsBridge: JsBridgeModel = new JsBridgeModel();
  @State spaRouteStack: SpaRouteItem[] = [];
  @State loadStatus: string = "页面待加载";

  aboutToAppear() {
    this.jsBridge.onSpaRouteChange = (route) => {
      if (route.isReplace) this.spaRouteStack.pop();
      this.spaRouteStack.push(route);
    };
  }

  onPageShow() { this.webController.resume(); }
  onPageHide() { this.webController.pause(); }

  aboutToDisappear() {
    this.jsBridge.h5Callback = null;
    this.jsBridge.onSpaRouteChange = null;
    this.spaRouteStack = [];
    this.webController.destroy();
  }

  private handleSpaBack() {
    this.spaRouteStack.length > 1 ? (this.spaRouteStack.pop(), this.webController.runJavaScript(`history.back()`)) : router.back();
  }

  // 唤起图库选图
  async openGallery(isMultiple: boolean, callback) {
    try {
      let result = await picker.selectPhotos({
        MIMEType: picker.PhotoMIMEType.IMAGE,
        isMultiSelect: isMultiple,
        maxSelectNumber: isMultiple ? 9 : 1
      });
      // 将选中文件路径回传给Web内核
      callback(result.photoUris);
    } catch (e) {
      callback([]);
    }
  }

  build() {
    Column() {
      Text(`SPA栈:${this.spaRouteStack.length} | ${this.loadStatus}`).fontSize(14).margin(10)

      Web({
        src: "你的H5线上/离线地址",
        controller: this.webController
      })
      .width('100%').height('88%')
      .javaScriptAccess(true)
      .registerJavaScriptProxy(this.jsBridge, "nativeBridge",
        ["getDeviceInfo", "showNativeToast", "getNativeData", "pushSpaRoute", "syncUserState"])

      // ========== 核心:H5上传拦截钩子 ==========
      .onShowFileSelector((param) => {
        console.info("H5触发文件上传", param.accept, param.isMultiple);
        // 优先打开图库
        this.openGallery(param.isMultiple, (uriList) => {
          // 关键:把原生选中的文件URI回传给H5,表单完成填充
          param.resultCallback(uriList);
        });
        // return true 代表我们原生接管,不使用系统默认行为
        return true;
      })

      .onLoadStatusChange((status) => {
        this.loadStatus = status === 0 ? "加载中" : "加载完成";
      })
    }
    .width('100%').height('100%')
    .onBackPressed(() => { this.handleSpaBack(); return true; })
  }
}

核心关键点解读:

  • return true:拦截H5默认行为,完全由原生接管上传弹窗

  • resultCallback(uriList):必须回调,否则H5弹窗卡死、按钮失效

  • uri 是鸿蒙标准沙箱路径,Web内核可直接识别、可直接回传给H5

写到这里,H5图片上传已经100%可用。

五、进阶:增加「拍照上传」弹窗选择(业务完整版)

真实项目不能只图库,必须支持:弹窗选择:拍照 / 从相册选择 / 取消。

我们封装全局选择弹窗,用户自主选择上传方式。

// 自定义上传方式弹窗
private showUploadDialog(isMultiple: boolean, callback) {
  ActionSheet.show({
    title: "请选择上传方式",
    subtitle: "",
    buttons: [
      { text: "拍照上传", action: () => { this.openCamera(callback); } },
      { text: "从相册选择", action: () => { this.openGallery(isMultiple, callback); } }
    ],
    cancel: () => { callback([]); }
  })
}

// 调用相机拍照
async openCamera(callback) {
  try {
    let photoUri = await camera.takePhoto();
    callback([photoUri]);
  } catch (e) {
    callback([]);
  }
}

然后在 onShowFileSelector 中替换为弹窗模式:

.onShowFileSelector((param) => {
  this.showUploadDialog(param.isMultiple, (uriList) => {
    param.resultCallback(uriList);
  });
  return true;
})

至此,拍照+图库双模式上传彻底完工,和商业APP体验完全一致。

六、通用文件上传(文档/压缩包/视频全覆盖)

H5 上传 Excel、PDF、ZIP、视频等文件,只需修改选择器类型。

// 通用文件选择
async openFile(isMultiple: boolean, callback) {
  let result = await picker.selectFiles({
    isMultiSelect: isMultiple
  });
  callback(result.fileUris);
}

通过 param.accept 判断 H5 需要的文件类型,动态切换图片/视频/文件选择器,实现精准适配。

七、高频致命坑点(全网最全上传踩坑总结)

坑1:不执行 resultCallback,页面卡死

无论用户是否选择文件、是否取消,必须执行回调,不传文件就传空数组,否则 H5 弹窗永久卡死。

坑2:return false 导致拦截失效

接管上传必须 return true,否则系统依旧走默认拦截,弹窗无响应。

坑3:多图选择不生效

必须读取 param.isMultiple,动态传给选择器,不能写死 false。

坑4:权限未动态申请,真机无弹窗、模拟器正常

模拟器权限全开,真机严格校验,必须手动动态申请 CAMERA、READ_MEDIA。

坑5:拍照图片方向旋转、倒置

鸿蒙相机返回图片自带 orientation 信息,H5 不识别,需要原生压缩矫正后再回传。

坑6:快速连续点击上传,多次触发回调

需要加防抖锁,防止多次弹窗、多次回调导致表单错乱。

八、防抖优化 + 防重复点击(上线必备)

上传属于高频误操作区域,必须加防抖锁。

private isUploading: boolean = false;

.onShowFileSelector((param) => {
  if(this.isUploading){
    param.resultCallback([]);
    return true;
  }
  this.isUploading = true;

  this.showUploadDialog(param.isMultiple, (uriList) => {
    param.resultCallback(uriList);
    // 释放锁
    this.isUploading = false;
  });
  return true;
})

九、本篇小结

第9篇彻底解决 ArkWeb 混合开发 最头疼的上传兼容问题。

你现在掌握完整能力:

  • 理解 HarmonyOS7 文件上传拦截底层机制

  • 拦截 H5 input file 所有上传行为

  • 实现拍照上传、图库单图/多图上传

  • 支持文档、视频、压缩包通用文件上传

  • 解决卡死、无响应、表单为空、重复弹窗等线上BUG

  • 添加上传防抖,达到上线稳定性标准

目前整套10篇系列仅剩 最后第10篇终章:安全加固、漏洞修复、权限最小化、应用市场上架规范、审计日志、防篡改、防调试、企业级安全收尾。

Logo

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

更多推荐