1. 为什么选 onShowFileSelector 而不是默认的 Web 上传
鸿蒙的 Web 组件(web_webview)加载 H5 页面时,默认对 <input type="file"> 是有内建响应的——系统会自动处理文件选择器,不需要你写一行原生代码。听起来很方便,但实际项目里一旦踩到下面几个场景,默认行为就完全不够用了:
- 上传的文件需要走自己的鉴权逻辑,不能在 H5 里直接经 Web 组件默认通道提交;
- 需要限制用户只能选择图片,或者只能选 PDF、压缩包等特定类型;
- 需要拿到用户选中的文件后先做压缩、重命名、加密等预处理,再传给前端表单;
- H5 使用的第三方上传组件在鸿蒙 Web 组件里默认唤起文件选择器时出现白屏、闪退或无法返回的问题。
onShowFileSelector 就是鸿蒙 Web 组件对外暴露的一个回调,它的作用是把“H5 内触发文件选择”这件事从 Web 组件默认的筐里接出来,交给你自己实现。你可以在这个回调里拦截本次选择请求,拿到文件类型、是否多选等参数,再调用系统相册、文件管理器或你自己的自定义选择器,最后把选中文件通过接口回调传回 Web。
用一个生活化的类比:默认行为相当于你去餐厅吃饭,后厨做什么你吃什么;onShowFileSelector 则是你把后厨包下来,指定菜单、控制食材、检查出锅,但上菜流程还是走餐厅的传菜口。H5 端无感知,前端依然走 <input> 触发,但选文件的动作已经被原生侧接管了。
这个回调是鸿蒙 ArkWeb 提供的文件上传拦截能力,对应的声明在 onShowFileSelector,通常配合 WebAttribute 或 WebviewController 一起用。Api 版本上建议以 9+ 为基准,部分参数行为在 10、11、12 上略有差异,后续我会专门说。
适用人群:正在做鸿蒙应用里 Hybrid H5 上传功能、遇到默认选择器不够用、或者想统一原生与 Web 文件选择体验的开发者。这篇文章会从事件触发链路讲起,再给完整代码实现,最后补充我实际测试中遇到的各类边界情况和踩坑记录。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 事件触发链路:H5 点击到原生回调之间发生了什么
要真正玩明白 onShowFileSelector,首先得搞清楚一条链路:H5 页面里的 <input type="file"> 被用户点击后,是怎么一步步传到鸿蒙原生侧的。
2.1 触发源:H5 的 input 元素与文件接受类型
H5 侧最常见的就是这一段代码:
html复制<input type="file" accept="image/*" multiple />
当用户点击该元素,浏览器内核(鸿蒙 Web 组件基于 Chromium 内核)会发起一次文件选择器请求。请求里包含三个关键信息:accept 指定的 MIME 类型、multiple 是否多选、capture 是否存在。
鸿蒙 Web 组件捕获到这次请求后,先检查自己内部是否配置了自定义拦截。如果没有,就走系统默认的文件选择 UI;如果有,就序列化请求参数,回调到 Native 层,也就是你要实现的 onShowFileSelector。
这里的 accept 参数很关键,很多新手以为它只是给前端做限制的,实际上在 onShowFileSelector 回调里,它是你判断“该唤起相册还是文件管理器”的唯一依据。比如 accept="image/*" 时你应该优先调系统相册;accept=".pdf,.doc" 时应该直接进文件管理器。
2.2 回调参数解析:FileSelectorParam 里有什么
onShowFileSelector 回调的完整签名一般是:
typescript复制onShowFileSelector(
event: () => void,
callback: (
fileSelector: FileSelectorParam
) => FileSelectorResult
): WebAttribute
实际使用中,核心是拿到 FileSelectorParam 对象后处理这些属性:
| 属性 | 类型 | 作用 |
|---|---|---|
accept |
Array<string> |
允许选择的文件类型,如 image/*、application/pdf |
multiple |
boolean |
是否允许多选 |
isCapture |
boolean |
是否要求通过摄像头拍摄 |
capture |
string |
capture 属性值,如 "user" 表示前置摄像头 |
title |
string |
H5 侧自定义的文件选择器标题(部分版本支持) |
mode |
FileSelectorMode |
选择模式标识,可据此区分普通文件还是媒体文件 |
拿到这些参数后,你就可以写自己的选择逻辑。比如:
typescript复制if (fileSelector.accept.includes('image/*')) {
// 唤起系统相册
} else {
// 唤起文件管理器
}
这一逻辑就是整个自定义选择器的决策入口,后面所有文件类型校验、UI 跳转都从这里分叉。
2.3 回调返回:FileSelectorResult 与文件内容回传
当用户在原生文件选择器里选完文件,你需要把结果封装成 FileSelectorResult 返回给 Web 组件。这里有一个很容易踩的坑:FileSelectorResult 不是让你直接传原始文件路径,而是要传一个 Array<WebFile> 数组,里面每个元素都包含路径、文件名、类型等描述信息。
基本构造方式如下:
typescript复制let webFile: WebFile = {
path: filePath, // 文件的沙箱内绝对路径,不是 file:// 开头的路径
name: fileName, // 文件名,带后缀
mimeType: mimeType, // MIME 类型,如 image/jpeg
size: fileSize, // 文件大小,单位字节,部分版本不需要
};
let result: FileSelectorResult = {
isSuccess: true,
fileList: [webFile],
};
注意:
path字段在 ArkWeb 里要求的是应用沙箱内的路径。如果你从系统文件选择器拿到的 URI 不是沙箱路径,得先拷贝到沙箱目录下,再回传。直接传一个外部临时目录路径,经常会导致上传到 H5 侧后读不到文件内容。
当 isSuccess 为 true 且有文件列表时,Web 组件的内核会重新构造对应的上传文件项,H5 端拿到的 File 对象只是基于路径的透明封装,前端几乎无感知,就像用系统原生的文件选择器一样。
2.4 中断与取消的处理
还有一种场景:用户点了 <input type="file"> 弹出了你的原生选择器,但用户在原生选择器界面里取消了,或者你的自定义选择器发生了异常。这时候
