更新记录
1.0.1(2026-10-22) 下载此版本
修复 iOS 端失败回调可能闪退的问题,并统一三端错误处理与临时文件清理行为。
- 修复(重要):iOS 端
getFileInfo/prepareFile失败时可能直接闪退——内部把回调参数做了联合类型断言,编译成 Swift 的强制类型转换后一旦类型不符就会使 App 崩溃。现三端统一改为按具体回调分发,失败一律走fail回调。 - iOS:副本注册表按 uri 去重并限制 100 条,避免重复副本与临时文件残留。
- 鸿蒙:用户取消改按
BusinessError.code判定,中文环境下不再把正常取消误报为 9010002。 - 三端:
removeTempFile在目录前缀校验外增加..校验,防止越界删除。 - 三端:
mimeTypes中无法识别的类型改为忽略并输出告警,不再导致"选不到任何文件"(鸿蒙端不再返回 9010008);全部无法识别时退化为不限类型。 - 鸿蒙:
mimeType兜底为空字符串(此前可能返回null,业务侧取.length会崩)。 - Android:
getFileInfo增加选择时的元数据缓存,URI 读权限丢失后仍可查询。 - 文档:补充失败分支的自测方法,修正大文件复制的耗时说明,错误码表标注保留码。
1.0.0(2026-10-21) 下载此版本
1.0.0
首次发布。跨平台文件选择器 UTS 插件,为环信 IM 发文件消息场景设计,已在 HBuilderX 5.07 / 5.26 完成 Android、iOS、HarmonyOS Next 三端真机验证。
- 三端系统文档选择器(SAF / UIDocumentPicker / @ohos.file.picker):选择文件、查询元数据、复制为本地缓存副本、清理临时文件,零存储权限。
- 统一 API:
chooseFile/getFileInfo/prepareFile/removeTempFile,三端回调结构与错误码一致;用户取消走success且cancelled: true。 - 环境要求:HBuilderX / uni-app / uni-app-x >= 5.07;Android 7.0+(minSdk 24)、iOS 16+、HarmonyOS Next API 14+。
平台行为要点:
- iOS:picker 暂存文件在选择回调内即复制到沙箱(系统回收暂存文件后 URI 会失效),
prepareFile直接返回该副本;所有终止回调推迟到下一 main tick,可在回调中同步调用其他插件 API。 - Android:自动对可持久化 URI 调用
takePersistableUriPermission,避免重启后 URI 失效。 - HarmonyOS Next:
prepareFile采用 fd 级复制,兼容file://docs/...跨沙箱 URI;并发选择守卫(重复调用返回 9010002);mimeTypes仅支持内置映射表中的具体类型。
平台兼容性
uni-app(5.07)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | × | × | √ | - | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uni-app x(5.07)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | √ | √ | √ | × |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
easemob-file-picker
跨平台文件选择器 UTS 插件,为环信(Easemob)IM 发文件消息场景设计。
解决的痛点:系统文件选择器返回的是 provider URI(content:// / file://docs/...),而环信 IM SDK 发送文件消息需要真实本地路径。本插件把「选文件 → 读元数据 → 复制为本地副本 → 用完清理」整条链路封装成 4 个 API,三端行为完全一致。
- 插件类型:UTS 插件(uni_modules)
- 支持平台:Android / iOS / HarmonyOS Next(uni-app 与 uni-app x)
- 仓库位置:easemob-uts-tools →
packages/easemob-file-picker
特性
- 零存储权限:三端均走系统文档选择器(SAF / UIDocumentPicker / @ohos.file.picker),不申请任何存储权限,满足各平台隐私合规与上架审核要求
- 三端统一:同一套 TypeScript 调用代码,回调结构、错误码完全一致
- 安全复制:
prepareFile生成带随机名的缓存副本;HarmonyOS 端采用 fd 级复制,兼容file://docs/...跨沙箱 URI - 防误删:
removeTempFile只删应用缓存 / 临时目录内的文件,且拒绝包含..的路径,越界路径返回removed: false而非报错
平台要求
| 平台 | 最低版本 | 实现方式 | 存储权限 |
|---|---|---|---|
| Android | 7.0(minSdk 24) | SAF(ACTION_OPEN_DOCUMENT) |
无需 |
| iOS | 16 | UIDocumentPickerViewController + UTType |
无需 |
| HarmonyOS Next | API 14+ | @ohos.file.picker |
无需 |
开发环境:HBuilderX >= 5.07(uni-app / uni-app x 同版本线)。已在 HBuilderX 5.07 与 5.26 完成 Android / iOS / HarmonyOS Next 三端真机验证。
快速开始
把本目录整个拷贝到项目的 uni_modules/ 下即可,无需原生配置。
完整流程:选文件 → 复制到本地 → 环信发送 → 清理
import { chooseFile, prepareFile, removeTempFile } from '@/uni_modules/easemob-file-picker'
chooseFile({
mimeTypes: ['image/*', 'application/pdf'], // 不传或空数组 = 不限类型(鸿蒙端限制见「平台行为说明」)
multiple: false,
success: (result) => {
// 注意:用户取消是正常结果,走 success 且 cancelled = true
if (result.cancelled || result.files.length == 0) return
prepareFile({
uri: result.files[0].uri,
success: (file) => {
// file.path 为本地缓存路径,可直接传给环信 SDK 发送文件消息
// (以下为示意代码,字段以环信 SDK 文档为准)
// WebIM.message.create({ type: 'file', url: file.path, filename: file.name, ... })
// 发送完成后清理临时文件
removeTempFile({
path: file.path!,
success: (res) => console.log('临时文件清理:', res.removed)
})
},
fail: (err) => console.error('文件复制失败', err.errCode, err.errMsg)
})
},
fail: (err) => console.error('选择文件失败', err.errCode, err.errMsg)
})
只查文件信息(不复制)
import { getFileInfo } from '@/uni_modules/easemob-file-picker'
getFileInfo({
uri: 'content://media/external/file/12345',
success: (info) => console.log(info.name, info.size, info.mimeType)
})
API 参考
| 方法 | 说明 |
|---|---|
chooseFile(options) |
打开系统文件选择器,返回选中文件的元数据 |
getFileInfo(options) |
根据 URI 查询文件元数据(不复制文件) |
prepareFile(options) |
把 provider URI 复制到应用缓存目录,返回真实本地路径 |
removeTempFile(options) |
删除 prepareFile 创建的临时文件 |
chooseFile(options)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mimeTypes | string[] | 否 | 限制可选 MIME 类型,如 ['application/pdf']、['image/*'];不传或空数组表示不限。无法识别的具体 MIME 会被忽略(不报错),全部无法识别时同样退化为不限;鸿蒙端支持范围见「平台行为说明」 |
| multiple | boolean | 否 | 是否多选,默认 false。仅 HarmonyOS 端限制最多 50 个;Android / iOS 不设插件层上限,由系统选择器决定 |
| success | (res: ChooseFileResult) => void | 否 | 成功回调(用户取消也走 success) |
| fail | (err: FilePickerError) => void | 否 | 失败回调 |
| complete | (res: any) => void | 否 | 完成回调(成功失败都触发,以下各 API 同) |
ChooseFileResult:{ files: FileInfo[], cancelled: boolean },取消时 files 为空数组。
getFileInfo(options) / prepareFile(options)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uri | string | 是 | chooseFile 返回的 uri,或 content:// / file:// 路径 |
| success | (res: FileInfo) => void | 否 | prepareFile 的返回中 path 有值且 isTemporary: true |
| fail / complete | - | 否 | 同上 |
removeTempFile(options)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | prepareFile 返回的 path |
| success | (res: { removed: boolean }) => void | 否 | 路径不在应用缓存 / 临时目录内(或含 ..)时 removed: false,不报错 |
| fail / complete | - | 否 | 同上 |
FileInfo 结构
| 字段 | 类型 | 说明 |
|---|---|---|
| uri | string | 原始 URI |
| name | string | 文件名 |
| size | number | 文件大小(字节) |
| mimeType | string | MIME 类型,查询失败时为空字符串 |
| extension | string | 小写扩展名(不含点),无扩展名时为空字符串 |
| path | string | null | 本地路径,仅 prepareFile 返回时有值 |
| isTemporary | boolean | 是否为插件创建的临时副本 |
错误码
fail 回调返回 FilePickerError(实现 IUniError),errSubject 固定为 easemob-file-picker:
| errCode | 说明 | 典型场景 |
|---|---|---|
| 9010001 | 文件选择失败 | 保留码,当前版本不会主动返回 |
| 9010002 | 无法打开文件选择器 | Activity / 页面不可用、已有进行中的选择操作 |
| 9010003 | 文件访问权限被拒 | 保留码,当前版本不会主动返回 |
| 9010004 | URI 无效或不可用 | getFileInfo 的 URI 已失效;iOS 端 prepareFile 的 URI 已失效 |
| 9010005 | 文件元数据不可用 | 选中文件全部无法读取元数据 |
| 9010006 | 无法读取或复制文件 | prepareFile 复制失败、IO 异常;Android / 鸿蒙端 prepareFile 的 URI 失效也返回此码 |
| 9010007 | 存储空间不足 | 保留码,当前版本不会主动返回 |
| 9010008 | 当前平台不支持该操作 | 保留码(预留给后续 Web 等平台实现),当前三端不会主动返回 |
9010001 / 9010003 / 9010007 / 9010008 为保留码:业务侧无需为它们编写分支,判断失败原因只需关注 9010002 / 9010004 / 9010005 / 9010006。
平台行为说明
Android
- 使用 SAF,targetSdk 30+ 无需
READ_EXTERNAL_STORAGE等权限 - 单 MIME 类型走
Intent.setType,多类型走EXTRA_MIME_TYPES;image/*等通配符由系统选择器解释 - 自动对可持久化 URI 调用
takePersistableUriPermission,避免重启后 URI 失效 - 选择时的文件元数据会缓存(最多 20 条),URI 读权限丢失后
getFileInfo仍能返回元数据;prepareFile需要真实读取,URI 失权后会返回 9010006,此时需重新选择
iOS
- MIME 通过
UTType(mimeType:)映射;image/*、video/*、audio/*、text/*通配映射到预置 UTType,无法识别的具体 MIME 忽略并输出告警日志(不会阻断选择) - 通过安全作用域资源(Security-Scoped Resource)访问选中文件
- 选择回调内即完成沙箱复制:picker 返回的是系统 File Provider Storage 暂存文件,选择结束且访问权释放后系统会回收,延迟重放 URI 不可靠。因此 iOS 端在选中时立即复制到沙箱 tmp 并内部注册,
prepareFile直接返回该副本(对外契约不变:chooseFile仍只返回元数据)。该复制走 APFS 写时复制(clonefile),同卷内耗时与文件大小无关(700MB 实测瞬时完成),也不额外占用磁盘空间 success回调推迟到下一 main tick 触发:可在success中直接同步调用prepareFile等其他插件 API,无需自行 setTimeout- 副本注册表上限 100 条(按 uri 去重),超限自动删除最旧副本,避免只选不发场景下 tmp 文件堆积;单次选择超过 100 个文件时,最早的文件需重新选择
- 同时只能进行一次选择操作,重复调用返回 9010002
HarmonyOS Next
- 使用
@ohos.file.picker的 DocumentViewPicker - picker 授予的临时读权限挂在 fd 上,
prepareFile内部采用 fd 级copyFileSync,兼容file://docs/storage/...等跨应用公共目录 URI - 临时文件以
easemob-file-前缀存放于应用 cacheDir - 同时只能进行一次选择操作,重复调用返回 9010002;用户取消按
BusinessError.code判定(不依赖 message 文案,中文环境同样正确) - mimeTypes 通过内置映射表(pdf / txt / zip / json / office 文档 / 常见图片音视频等 19 种)转换为后缀过滤:映射表外的具体 MIME 会被忽略并输出告警日志,不会报错
- mimeTypes 中任一
*/*或xxx/*通配符都会使整组过滤失效,展示全部文件(通配符无法映射为后缀过滤)
注意事项
- 用户取消不是错误:取消走
success且cancelled: true,不要用fail判断取消 - 临时文件生命周期:
prepareFile的副本由调用方通过removeTempFile清理(存放位置:Android 应用 cacheDir、iOS NSTemporaryDirectory、鸿蒙应用 cacheDir);系统可能随时清缓存,不要长期持有path - 大文件复制:Android / 鸿蒙为真实字节复制,耗时与体积线性相关,建议 UI 加 loading;iOS 端副本走 APFS 写时复制,同卷内耗时与文件大小无关(700MB 实测瞬时完成),无需额外 loading。若所选文件来自需要联网下载的云盘提供方,耗时发生在系统选择器阶段,不在插件内
- 失败分支建议自测一次:
prepareFile({ uri: 'file:///not-exist/a.pdf', fail: ... })应回调 9010004(iOS)/ 9010006(Android、鸿蒙),不应无响应或崩溃 - Web / 小程序不支持:仅支持 App 三端;这些平台未实现该插件,无法调用
- 鸿蒙 MIME 限制:
mimeTypes尽量使用内置映射表中的类型或通配符,映射表外的类型会被忽略(详见「平台行为说明」)
目录结构
easemob-file-picker/
├── package.json # 插件清单(平台支持声明)
├── changelog.md
├── readme.md
└── utssdk/
├── interface.uts # 公共类型定义(FileInfo / Options / Result / 错误码)
├── unierror.uts # FilePickerError 实现
├── app-android/ # Android 实现(Kotlin)
├── app-ios/ # iOS 实现(Swift,deploymentTarget 16)
└── app-harmony/ # HarmonyOS 实现(ArkTS)

收藏人数:
https://github.com/Easemob-Community/easemob-uts-tools
下载插件并导入HBuilder
赞赏(0)
下载 4
赞赏 0
下载 12703634
赞赏 1957
赞赏
京公网安备:11010802035340号