uni-app openDocument 文件打开能力全解析:uni-openDocument UTS 插件原理与实战
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
uni-openDocument 是 uni-app 官方开源仓库中负责「打开文件」能力的 UTS 插件,它通过uni.openDocument()接口在 App(Android/iOS/鸿蒙)与微信小程序等平台上唤起系统或宿主应用预览/打开本地文档。本文将以仓库内 src/uni_modules/uni-openDocument 为核心,结合其完整源码与官方示例页,系统讲解 API 参数、错误码规范、各平台底层实现原理(Android Intent/FileProvider、iOS QuickLook、鸿蒙 Want 拉起)、UTS 插件机制与实战用法,帮助开发者正确集成并深度理解这一能力的跨端工作方式。
一、插件定位:一个用 UTS 封装原生能力的最小 uni_modules 插件
uni-openDocument 在仓库中的完整形态是一个标准的 uni_modules UTS 插件,目录结构如下(全部源码均在仓库内):
src/uni_modules/uni-openDocument/ ├── readme.md # 插件说明(本文关联文档) ├── package.json # 插件元数据、平台声明、依赖声明 ├── changelog.md # 版本变更记录 └── utssdk/ ├── interface.uts # API 类型定义:参数、回调、错误码 ├── protocol.uts # 常量:API 名称 'openDocument' ├── unierror.uts # 统一错误对象实现(UniError 子类) ├── app-android/ # Android 平台实现(UTS/Kotlin) │ ├── config.json # hooksClass 声明 │ └── index.uts ├── app-ios/ # iOS 平台实现(UTS/Swift) │ └── index.uts └── app-harmony/ # 鸿蒙平台实现(UTS/ArkTS) └── index.uts从 package.json 可以看到它如何被注册为 uni-app 扩展 API:
{ "id": "uni-openDocument", "version": "1.0.0", "engines": { "HBuilderX": "^3.6.8" }, "uni_modules": { "dependencies": ["uni-fileSystemManager"], "uni-ext-api": { "uni": { "openDocument": { "name": "openDocument", "app": { "js": false, "kotlin": true, "swift": true, "arkts": true } } } } } }其中uni-ext-api声明了该插件向全局uni对象注入名为openDocument的扩展 API,并指明 App 端分别在 Kotlin(Android)、Swift(iOS)、ArkTS(鸿蒙)三个编译目标上提供实现,而js: false表示它并非纯 JS 实现;dependencies声明依赖 uni-fileSystemManager(Android 端复制文件时会用到文件系统能力,见下文源码分析)。
二、API 快速上手:参数与回调
插件的对外类型定义全部集中在 utssdk/interface.uts,其中OpenDocumentOptions(interface.uts 第 371-712 行)定义了完整参数:
| 参数 | 类型 | 必填 | 说明 | | -- | -- | -- | -- | |filePath|string| 是 | 文件路径,仅支持本地路径(临时路径、静态资源路径、缓存目录路径等) | |fileType|string \| null| 否 | 文件类型(扩展名),如doc、pdf。微信小程序仅支持doc, xls, ppt, pdf, docx, xlsx, pptx;App 端由系统打开,原则上可打开任意文件 | |success|(res: OpenDocumentSuccess) => void| 否 | 调用成功回调,成功时res为空对象{}| |fail|(res: OpenDocumentFail) => void| 否 | 调用失败回调,携带errCode/errMsg| |complete|(res: any) => void| 否 | 调用结束回调,成功、失败都会执行 |
Uni接口中的方法声明(interface.uts 第 715-788 行)明确了openDocument(options?)的调用形态,并标注其支持 Vue2 与 Vue3 两种工程。最简单的调用方式:
uni.openDocument({ filePath: '/static/hello.pdf', success: () => { console.log('打开文档成功') }, fail: (err) => { console.log('打开文档失败', err.errCode, err.errMsg) } })OpenDocumentSuccess被定义为空对象类型(interface.uts 第 4 行),即打开动作本身没有业务返回值,成功与否由回调与错误码体现。
三、错误码规范:1300601~1300604 的完整语义
插件定义了统一错误码枚举OpenDocumentErrorCode(interface.uts 第 8-280 行),错误文案的集中映射位于 utssdk/unierror.uts:
| 错误码 | 枚举注释 | 源码中的 errMsg | 触发场景 | | -- | -- | -- | -- | |1300601| 路径无效 |Invalid file path| 传入路径无法被识别为有效本地文件(如 iOS 端非 fileURL 的路径) | |1300602| 文件不存在 |File not exist| 本地文件校验失败(文件不存在或读取失败) | |1300603| 不支持该文件类型 |Not support this filetype| 无法解析 MIME/类型,或系统无应用可处理(AndroidstartActivity抛异常);鸿蒙端显式抛出 | |1300604| 其他未知错误 |Unkowned error| 兜底错误(如 Android 端获取不到 Activity、文件复制失败) |
OpenDocumentErrorImpl(unierror.uts 第 15-25 行)继承UniError,并统一设置errSubject = 'uni-openDocument',错误消息从映射表中按错误码取出,未命中时为空字符串:
export class OpenDocumentErrorImpl extends UniError implements IOpenDocumentError { constructor(code: OpenDocumentErrorCode) { super(); this.errSubject = OpenDocumentUniErrorSubject; // 'uni-openDocument' this.errCode = code; this.errMsg = OpenDocumentUniErrors[code] ?? ''; } }各平台实现统一通过该错误类构造fail/complete回调的入参,保证跨端错误语义一致。官方示例页(见下文)在fail中直接读取err.errCode并以 Toast 展示,即此错误码的典型消费方式。
四、实战示例:下载远程文档后打开
仓库自带的官方示例页 src/pages/API/open-document/open-document.uvue 给出了完整可运行的范式:对网络文档先用uni.downloadFile下载为本地临时文件,再调用uni.openDocument;对本地静态资源则直接传入路径:
const openDocument = (item: FileItem) => { if (item.url.startsWith('http')) { uni.showLoading({ title: '下载中', mask: true }) uni.downloadFile({ url: item.url, success: (res) => { uni.openDocument({ filePath: res.tempFilePath, // 下载后的本地临时文件 success: () => { uni.hideLoading() }, fail: (err) => { uni.hideLoading() uni.showToast({ title: '错误码:' + err.errCode.toString(), icon: "error" }) } }) }, fail: (err) => { /* 下载失败处理 */ } }) } else { uni.openDocument({ filePath: item.url, // 静态资源路径,如 '/static/test-image/logo.svg' success: () => { console.log('打开文档成功') }, fail: (err) => { /* 错误码展示 */ } }) } }示例页覆盖了pdf/doc/docx/ppt/pptx/xls/xlsx/zip/br/mp3/mp4/svg等十余种文件类型的打开,展示了 App 端“原则上可以打开任意文件”的能力边界。仓库另一示例页 src/pages/API/get-file-system-manager/filemanage.uvue 中也能看到从文件系统管理器获取路径后直接交给uni.openDocument的调用方式,说明filePath与uni.getFileSystemManager()的本地路径体系天然兼容。
五、各平台底层实现原理(源码级解析)
5.1 Android:ACTION_VIEW + FileProvider + MimeTypeMap
Android 实现位于 utssdk/app-android/index.uts,核心流程:
- 路径归一化:通过
UTSAndroid.convert2AbsFullPath将相对路径转为绝对路径; - 文件有效性校验(
isValidFile,第 75-128 行):区分三类路径——/android_asset前缀:通过activity.getAssets().open(...)校验存在性,若有效则把文件复制到外部缓存目录getExternalCacheDir()/uni-document/下(使用依赖的uni.getFileSystemManager().copyFileSync),因为 FileProvider 无法直接暴露 assets 资源;content://前缀:通过getContentResolver().openInputStream校验可读;- 其余普通路径:
new File(path).exists()校验;
- 构造 URI(第 38-47 行):
content://路径直接解析;Android 7.0(API 24,Build.VERSION_CODES.N)及以上使用FileProvider.getUriForFile(activity, packageName + '.dc.fileprovider', file)生成可共享的文件 URI,以下走兼容分支; - 构造 Intent(第 48-57 行):
ACTION_VIEW+FLAG_ACTIVITY_NEW_TASK+FLAG_GRANT_READ_URI_PERMISSION;若显式传了fileType,则用MimeTypeMap.getSingleton().getMimeTypeFromExtension(...)解析 MIME 并调用setDataAndType,否则只setData; - 拉起与兜底:
activity.startActivity(intent)成功则触发success;抛出Exception说明系统无应用可处理该类型,触发错误码1300603;拿不到UniActivity时触发1300604。
此外,utssdk/app-android/config.json 声明了hooksClass: "uts.sdk.modules.DCloudUniOpenDocument.UniOpenDocumentHookProxy",对应源码第 14-28 行的UniOpenDocumentHookProxy:在应用onCreate时异步清空uni-document/缓存目录中的遗留文件,避免 assets 复制产物无限累积。
5.2 iOS:QuickLook 原生预览控制器
iOS 实现位于 utssdk/app-ios/index.uts,直接使用系统框架QuickLook的QLPreviewController提供文档预览:
openDocument委托给单例DocumentPreviewer.shared.presentDocument(options)(第 14-18 行);presentDocument(第 33-69 行)将filePath用UTSiOS.convert2AbsFullPath归一化后构造URL:tmpUrl.isFileURL == false报1300601(路径无效),FileManager.default.fileExists为 false 报1300602(文件不存在);- 通过
UTSiOS.getCurrentViewController().present(...)以无动画方式弹出QLPreviewController,并实现dataSource/delegate回调(第 71-87 行)提供预览项UniPreviewItem;previewControllerDidDismiss中还处理了关闭后立即重新呈现的边界场景。
注意 iOS 端对http(s)开头的路径不做绝对路径转换(第 36-38 行),说明该端的设计预期仍是传入本地文件。
5.3 鸿蒙:Want 拉起 + uniformTypeDescriptor 类型映射
鸿蒙实现位于 utssdk/app-harmony/index.uts,走系统startAbility拉起文档查看应用:
- 类型解析(
getContentType,第 19-28 行):优先取fileType,否则从filePath取扩展名,经uniformTypeDescriptor.getUniformDataTypeByFilenameExtension与getTypeDescriptor得到首个mimeTypes[0];解析失败或传入了不支持的fileType时显式抛错码1300603; - 路径与存在性校验(第 50-62 行):
UTSHarmony.convert2AbsFullPath归一化后,以/开头的绝对路径用fs.statSync校验存在,否则抛1300602; - 资源目录文件特殊处理(第 64-81 行):若文件位于
getContext().resourceDir下(即打包进应用的只读资源),先通过fs.open/copyFile复制到临时目录TEMP_PATH/openDocumentCache再打开,因为只读资源目录无法直接授权给其他应用; - 拉起应用(第 82-91 行):
fileUri.getUriFromPath生成 URI,构造Want:action: 'ohos.want.action.viewData'、flags携带读写与持久化授权位、uri与type一并传入abilityContext.startAbility(want); - 异步封装(第 94-103 行):通过
defineAsyncApi<OpenDocumentOptions, OpenDocumentSuccess>(API_OPEN_DOCUMENT, ...)注册,_openDocument成功resolve({}),失败以错误码(ErrorWithCode携带的错误码,未知错误兜底1300604)reject,并向下兼容导出IOpenDocumentError、各回调类型等全部类型。
源码第 87 行的注释还记录了一个工程经验:传入type反而可能减少可被调起的应用数量(如 zip 场景),说明鸿蒙端在未指定类型时的“尽力打开”策略是刻意的取舍。
六、UTS 语言与 UTS 插件机制(原文档核心内容)
6.1 uts:可编译为多端原生语言的跨端语言
按插件 readme.md 的定义,uts(uni type script)是一门跨平台、高性能、强类型的现代编程语言,可被编译为不同平台的编程语言:
| 目标平台 | 编译产物语言 | | -- | -- | | Android | Kotlin | | iOS | Swift | | 鸿蒙 OS | ArkTS | | web 平台 / 小程序 | JavaScript |
uts 采用与 TypeScript 基本一致的语法规范,支持绝大部分 ES6 API;为了跨端进行了若干约束和平台特定增补。过去在 JS 引擎下运行支持的语法,大部分在 uts 的处理下也能平滑地在 Kotlin/Swift 中使用,但存在无法抹平的差异,此时需要使用条件编译(如本插件unierror.uts第 16-18 行仅在APP-ANDROID || APP-HARMONY下声明override errCode的做法),在条件编译分支内可调用平台特有扩展语法。
6.2 UTS 插件的组织方式:utssdk 目录按平台分离
UTS 插件是一种特定形态的 uni_modules 插件,核心目的是允许 uni-app/uni-app x 开发者使用 UTS 语法调用扩展 API(封装原生系统 API 或三方 SDK)。实现代码位于utssdk目录并按平台分离,uni-openDocument 即完全遵循这一规范:
| 目录/文件 | 目标平台 | 实现语言 | 作用描述 | | -- | -- | -- | -- | |utssdk/app-android| Android | UTS, Kotlin, Java | UTS 插件在 Android 平台上的具体实现源码 | |utssdk/app-ios| iOS | UTS, Swift | UTS 插件在 iOS 平台上的具体实现源码 | |utssdk/app-harmony| HarmonyOS(鸿蒙) | UTS, ArkTS | UTS 插件在 HarmonyOS 平台上的具体实现源码 | |utssdk/*.uts| 多平台共用 | UTS | 使用 UTS 编写、可供所有平台共用的实现源码(如本插件的interface.uts、unierror.uts) |
对照本插件的源码可以直观看到:interface.uts定义纯类型与接口(跨端共用),protocol.uts定义 API 常量,unierror.uts定义错误模型,而真正的平台行为实现分别在三个app-*目录中通过export const openDocument: OpenDocument = ...提供——编译器会按目标平台选择对应的实现注入uni.openDocument。
七、平台支持范围与使用注意事项
综合 package.json 的platforms声明与 interface.uts 中逐参数标注的@uniPlatform能力注释:
- App 端(Android/iOS):为插件的主要实现目标(Kotlin/Swift 实现标记为
y),参数注释中 Android/iOS 标注unixVer: 4.71起可用; - 鸿蒙端:标注
uniVer: 4.31、unixVer: 4.61、vapor 版本 5.0 起可用; - 微信小程序:宿主与 uni-app 均标记为支持(
hostVer: √、unixVer: 4.41),但fileType仅限doc, xls, ppt, pdf, docx, xlsx, pptx七种; - 百度、QQ、快手小程序:宿主能力标记为可用,但 unix 侧标记为不支持(
unixVer: x),说明这些宿主是否可打开取决于宿主 API 能力; - Web、支付宝、字节、飞书、京东小程序等:标记为不支持(
x),调用不会生效,应在这些平台做好条件编译降级处理。
使用时的关键约束(均可在上述源码与注释中印证):
filePath仅支持本地路径:远程文档必须先经uni.downloadFile落盘(如示例页做法),不可直接传入 http URL;- 类型与 MIME 的关系:App 端传
fileType会被映射为 MIME(Android 用MimeTypeMap、鸿蒙用uniformTypeDescriptor),不传则由系统按文件本身判定;微信端则必须在其白名单类型内; - Android 依赖 FileProvider:
content://与 7.0+ 的 FileProvider 授权是 Android 实现的核心机制,工程需具备对应dc.fileprovider配置(由 HBuilder 运行基座自动注入); - iOS 预览为应用内 QuickLook 面板:打开后由用户手动关闭返回,
success在呈现调用成功后即触发; - 鸿蒙资源目录文件会被复制到临时目录:因此打开后如需长期保留请自行管理拷贝。
八、总结
uni-openDocument 是理解「UTS 插件如何封装原生能力」的一个极佳样本:一份interface.uts类型契约 + 三份平台实现(Android 的 Intent/FileProvider、iOS 的 QuickLook、鸿蒙的 Want),配合统一错误码1300601~1300604,把“打开本地文档”这一系统级能力在 uni-app/uni-app x 中收敛成一个跨端一致的uni.openDocument调用。开发者既可直接使用官方示例页 open-document.uvue 的下载-打开流程落地业务,也可参照本插件的目录结构与实现方式编写自己的 UTS 扩展 API;进一步的 API 规范细节可查阅仓库文档 docs/api/open-document.md,uts 语言与 UTS 插件开发的体系化说明可参考仓库内 docs/uts 与 docs/plugin 目录下的对应文档。
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考