更新记录
1.0.0(2026-08-27) 下载此版本
第一版初始化
平台兼容性
uni-app x(4.76)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | 12 | × | - |
dyl-file-export
dyl-file-export 是面向 uni-app x 的 Android/iOS UTS 原生插件,用一个 API 完成:
- 下载远程文件
- 获取下载进度
- 推断文件名和 MIME 类型
- 导出到 Android 系统公共目录
- 调起 iOS 系统“文件”保存面板
- 主动取消任务
- 自动清理下载缓存
插件当前版本:1.0.0。
1. 兼容性
| 项目 | 支持情况 |
|---|---|
| uni-app x | 支持 |
| 普通 uni-app | 不支持 |
| App Android | 支持 |
| App iOS | 支持 |
| Web/H5 | 不支持 |
| 微信及其他小程序 | 不支持 |
| HarmonyOS Next | 不支持 |
| Android 最低版本 | Android 5.0,API 21 |
| iOS 最低版本 | iOS 12.0 |
| HBuilderX 清单最低版本 | 4.76 |
| uni-app x 清单最低版本 | 4.76 |
| 当前实际编译验证版本 | HBuilderX 5.24 |
| 标准基座 | 不支持完整原生能力 |
| 自定义基座 | 必须重新制作 |
虽然插件清单声明 HBuilderX/uni-app x 最低版本为 4.76,但当前开发和编译回归使用的是 HBuilderX 5.24。正式项目建议使用 5.24 或更高的稳定版本;如果使用 4.76~5.23,需要自行完成 Android、iOS 真机验证。
2. 安装
将完整插件目录复制到目标 uni-app x 项目的 uni_modules:
目标项目/
└─ uni_modules/
└─ dyl-file-export/
├─ package.json
├─ README.md
└─ utssdk/
插件没有其他 uni_modules 依赖,不需要安装 npm 包。
复制后必须重新制作 Android/iOS 自定义基座。以下内容发生变化时也必须重新制作基座:
- Kotlin 或 Swift 原生代码
AndroidManifest.xml- Android/iOS
config.json - 插件 ID 或插件目录名
仅修改页面调用代码时,通常不需要重新制作基座。
3. 快速开始
import {
downloadAndExport,
type DownloadAndExportFail,
type DownloadAndExportProgress,
type DownloadAndExportResult,
type DownloadExportTask
} from '@/uni_modules/dyl-file-export'
let downloadTask : DownloadExportTask | null = null
downloadTask = downloadAndExport({
url: 'https://example.com/files/music.mp3',
success: (result : DownloadAndExportResult) : void => {
console.log('保存成功:' + result.savedPath)
},
fail: (error : DownloadAndExportFail) : void => {
console.log('保存失败:' + error.errMsg)
},
complete: () : void => {
console.log('任务结束')
downloadTask = null
}
})
downloadTask!.onProgressUpdate((event : DownloadAndExportProgress) : void => {
if (event.progress >= 100) {
console.log('下载完成,正在导出到系统')
} else {
console.log('下载进度:' + event.progress + '%')
}
})
success 只会在系统导出完成后触发,不代表仅仅下载到了应用缓存。
4. API
4.1 downloadAndExport
downloadAndExport(options : DownloadAndExportOptions) : DownloadExportTask
调用后立即返回任务对象。网络下载和系统导出在插件内部完成,调用方不需要再使用 uni.downloadFile。
4.2 DownloadAndExportOptions
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
url |
string |
是 | 无 | HTTP/HTTPS 文件地址 |
fileName |
string |
否 | 自动生成 | 导出文件名,可包含扩展名 |
header |
UTSJSONObject |
否 | 空 | 下载请求头,例如鉴权信息 |
timeout |
number |
否 | 120000 |
请求超时,单位毫秒;非正数使用默认值 |
mimeType |
string |
否 | 自动推断 | 显式指定文件 MIME 类型 |
directoryName |
string |
否 | 宿主 App 显示名称 | Android 公共目录下的子目录;iOS 忽略此项 |
success |
(result) => void |
否 | 无 | 系统导出完成回调 |
fail |
(error) => void |
否 | 无 | 下载、导出或取消失败回调 |
complete |
() => void |
否 | 无 | 最终回调,位于 success 或 fail 之后 |
当前 API 的远程地址字段是 url,不支持以 filePath 传入本地文件。
4.3 DownloadExportTask
export interface DownloadExportTask {
abort() : void
Update(callback : DownloadAndExportProgressCallback) : void
offProgressUpdate(callback : DownloadAndExportProgressCallback) : void
}
abort()
取消当前任务:
- 下载阶段:取消网络请求并删除缓存。
- Android 导出阶段:停止复制,并尽量删除未完成的公共目录文件。
- iOS 导出阶段:关闭系统文件面板并删除缓存。
- 取消后触发
fail,错误码为9301009,随后触发complete。
任务已经结束后再次调用 abort() 不会重复触发回调。
onProgressUpdate(callback)
注册下载进度监听。可以注册多个监听。若注册前已经产生过进度,插件会立即回放最后一次进度事件。
offProgressUpdate(callback)
- Android:移除传入的同一个回调实例。
- iOS:由于 Swift 闭包不支持可靠的相等性比较,调用此方法会清除当前任务的全部进度监听。
任务成功、失败或取消后,插件也会自动清除进度监听。
5. 进度事件
export type DownloadAndExportProgress = {
progress : number
totalBytesWritten : number
totalBytesExpectedToWrite : number
}
| 属性 | 说明 |
|---|---|
progress |
0~100 的整数下载进度 |
totalBytesWritten |
已写入缓存的字节数 |
totalBytesExpectedToWrite |
服务端声明的总字节数 |
注意事项:
- 下载过程中进度最大为 99。
- 文件完整写入缓存后发送 100。
- 100 表示下载完成,此时系统导出可能仍在进行。
- 服务端未返回
Content-Length时,总字节数可能小于或等于 0,进度可能在完成前保持 0,最终仍会发送 100。 - 页面建议在进度达到 100 后显示“正在保存到系统”,直到收到
success或fail。
6. 成功结果
export type DownloadAndExportResult = {
statusCode : number
fileName : string
mimeType : string
size : number
savedPath : string
}
| 属性 | 说明 |
|---|---|
statusCode |
HTTP 响应状态码 |
fileName |
最终使用的文件名 |
mimeType |
最终使用的 MIME 类型 |
size |
下载文件大小,单位字节 |
savedPath |
系统导出位置标识 |
savedPath 的平台差异:
- Android 10+:通常是
content://URI,不是普通文件系统路径。 - Android 9 及以下:通常是
file://URI。 - iOS:系统文件面板返回的目标 URL 字符串。
不要假设 savedPath 一定可以直接传给原生 File 构造器。Android 10+ 读取 content:// 内容时应使用 ContentResolver。
7. 文件名规则
7.1 传入 fileName
- 非法文件名字符会替换为
_。 - Android/iOS 文件名最长截取 180 个字符。
- 文件名已经包含扩展名时直接使用。
- 未包含扩展名时,优先从 URL 推断扩展名,其次根据 MIME 类型补充扩展名。
downloadAndExport({
url: 'https://example.com/audio?id=100',
fileName: '我的音乐.mp3'
})
7.2 未传 fileName
插件会读取 URL 最后一个路径段、执行 URL 解码,并在扩展名前追加毫秒时间戳:
https://example.com/audio/demo.mp3?token=xxx
→ demo_1787800000000.mp3
无法从 URL 获取名称时使用:
download_<毫秒时间戳><推断扩展名>
插件当前不会读取 HTTP Content-Disposition 中的文件名。
8. MIME 类型推断
MIME 类型优先级从高到低为:
- 调用方传入的
mimeType - HTTP 响应
Content-Type - 文件扩展名
application/octet-stream
内置常见映射包括:
- 音频:MP3、WAV、M4A、AAC、FLAC、OGG
- 视频:MP4、MOV
- 图片:JPG/JPEG、PNG、GIF、WebP
- 文档:PDF、TXT、ZIP
如果服务器错误地返回 application/octet-stream,该值仍高于扩展名推断。需要正确分类到 Android Music、Movies 或 Pictures 时,建议显式传入 mimeType。
9. Android 行为
9.1 保存目录
Android 根据最终 MIME 类型选择公共目录:
| MIME 类型 | 公共目录 |
|---|---|
audio/* |
Music/<directoryName> |
video/* |
Movies/<directoryName> |
image/* |
Pictures/<directoryName> |
| 其他 | Download/<directoryName> |
directoryName 未传时读取宿主 App 的显示名称,不会硬编码插件或项目品牌。
9.2 Android 10 及以上
- 使用 MediaStore 导出。
- 不申请旧版运行时存储权限。
savedPath返回 MediaStore 的content://URI。- 文件在写入完成前使用 pending 状态,失败或取消时会尝试删除未完成记录。
9.3 Android 9 及以下
- 使用公共 Music、Movies、Pictures 或 Download 目录。
- 运行时申请
android.permission.WRITE_EXTERNAL_STORAGE。 - 用户拒绝权限时返回
9301003。 - 文件重名时自动生成
文件名(1).扩展名、文件名(2).扩展名等名称。
插件 Manifest 中必须保留:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
不要给该声明添加 android:maxSdkVersion="28"。HBuilderX 的 UTS 插件 Manifest 校验不接受这一属性;插件运行时代码本身已经限制只在 Android 9 及以下请求权限。
10. iOS 行为
- 使用原生
URLSessionDownloadTask下载。 - 下载完成后打开系统文件导出面板。
- 用户可以选择“我的 iPhone”、iCloud Drive 或其他文件提供商支持的位置。
directoryName在 iOS 不生效,最终目录由用户选择。- 用户取消系统面板时返回
9301006。 - 页面或宿主控制器不可用时返回导出失败。
- 缓存会在成功、失败或取消后自动删除。
iOS 使用系统文件面板,不需要照片库读写权限描述。插件最低部署版本为 iOS 12;iOS 14+ 使用新的文件导出初始化方法,iOS 12~13 使用兼容方法。
11. 请求头与鉴权
const requestHeader : UTSJSONObject = {
'Authorization': 'Bearer your-token',
'X-Request-ID': 'request-id'
}
const task = downloadAndExport({
url: 'https://example.com/protected/file.pdf',
header: requestHeader,
timeout: 60000,
fileName: '资料.pdf',
mimeType: 'application/pdf'
})
请求头会转换成原生 HTTP 请求字段。建议使用字符串值,避免不同平台对复杂 JSON 值的字符串化结果不一致。
12. 单任务互斥
插件同一时刻只允许一个下载/导出任务。
如果已有任务未结束,新调用会:
- 立即返回一个可调用
abort()的任务对象。 - 异步触发
fail,错误码为9301004。 - 随后触发
complete。
前一个任务成功、失败或取消后,互斥状态自动释放。
13. 回调顺序
成功流程:
下载进度 → 系统导出完成 → success → complete
失败或取消流程:
下载/导出失败 → fail → complete
保证规则:
success和fail只会触发其中一个。complete在success或fail之后触发。- 所有结束回调最多执行一次。
- 即使
success或fail内部抛出异常,插件仍通过finally尝试调用complete。
14. 错误对象与错误码
export interface DownloadAndExportFail extends IUniError {
errCode : DownloadAndExportErrorCode
stage : 'download' | 'export'
statusCode : number
}
| 错误码 | 默认信息 | 常见原因 |
|---|---|---|
9301001 |
下载参数不合法 | URL 为空、协议不是 HTTP/HTTPS |
9301002 |
无法创建下载缓存 | 缓存目录创建或临时文件移动失败 |
9301003 |
没有文件存储权限 | Android 9 及以下拒绝存储权限 |
9301004 |
已有文件正在下载或导出 | 单任务互斥冲突 |
9301005 |
文件导出失败 | 公共目录写入失败、iOS 文件面板无法打开 |
9301006 |
用户取消导出 | 用户关闭 iOS 文件保存面板 |
9301007 |
当前平台不支持下载导出 | 在 Android/iOS 以外平台调用 |
9301008 |
文件下载失败 | 网络错误、超时、HTTP 非 2xx |
9301009 |
任务已取消 | 调用 abort() |
附加字段:
errMsg:具体错误信息。stage:错误发生在download或export阶段。statusCode:已经收到 HTTP 响应时为实际状态码,否则通常为 0。errSubject:固定为dyl-file-export。
15. 页面生命周期取消
页面销毁时建议取消仍在执行的任务:
import { onUnload } from '@dcloudio/uni-app'
import { downloadAndExport, type DownloadExportTask } from '@/uni_modules/dyl-file-export'
let task : DownloadExportTask | null = null
const startDownload = () : void => {
task = downloadAndExport({
url: 'https://example.com/video.mp4',
complete: () : void => {
task = null
}
})
}
onUnload(() : void => {
if (task != null) {
task!.abort()
task = null
}
})
如果页面使用任务序号防止旧回调更新新页面状态,建议比较数字序号,不要在 iOS UTS 代码中直接比较函数或接口任务对象。
16. 常见问题
16.1 标准基座提示原生配置不能生效
这是正常现象。插件包含 Kotlin、Swift 和 Android Manifest,必须重新制作自定义基座。
16.2 Android 文件管理中找不到文件
检查以下内容:
- 等待
success,不要只判断进度 100。 - 根据 MIME 类型到 Music、Movies、Pictures 或 Download 查找。
- 检查
directoryName,默认是宿主 App 显示名称。 - 服务端若返回错误的
Content-Type,显式传入正确mimeType。
16.3 Android 10+ 的 savedPath 不是磁盘路径
这是 MediaStore 的正常行为。返回值通常是 content:// URI,应通过系统内容解析接口访问。
16.4 iOS 进度 100 后没有立即 success
进度 100 只表示网络下载完成。用户仍需在系统文件面板选择位置并完成保存。
16.5 HTTP 地址失败
iOS ATS 或 Android 网络安全策略可能禁止明文 HTTP。生产环境建议使用 HTTPS;如必须使用 HTTP,需要由宿主项目配置对应的平台网络安全策略。
16.6 HBuilderX 云打包出现旧错误
确认:
- 已保存最新插件源码。
- 自定义基座是重新制作的,不是旧基座。
- 云端上传的插件目录名和
package.jsonID 都是dyl-file-export。 - 查看日志中的第一个
error:,后续警告通常不是根因。
17. 当前限制
- 仅支持 HTTP/HTTPS 下载。
- 不支持本地文件直接导出。
- 不支持断点续传和后台下载恢复。
- 不支持多任务并发。
- 不读取
Content-Disposition文件名。 - iOS 保存位置必须由用户通过系统面板选择。
- iOS
offProgressUpdate()会移除当前任务的全部进度监听。 - Android 10+ 的
savedPath是内容 URI,不保证是可直接访问的磁盘路径。 - 系统导出完成前应用被系统终止时,插件无法保证继续执行。
18. 真机验收建议
发布前建议至少验证:
- 自定义文件名和自动文件名。
- 带查询参数、无扩展名和 URL 编码文件名。
- MP3、MP4、图片、PDF 等不同 MIME 类型。
- 鉴权 header。
- HTTP 非 2xx、超时、断网。
- 已知和未知
Content-Length。 - 进度单调递增并最终达到 100。
- 下载阶段主动取消。
- iOS 文件面板取消。
- 单任务互斥。
success/fail → complete回调顺序。- Android 10+ MediaStore 公共目录。
- Android 9 及以下权限申请及公共目录。
- iOS 12~13 兼容导出以及 iOS 14+ 文件面板导出。
19. 目录结构
dyl-file-export/
├─ package.json
├─ README.md
└─ utssdk/
├─ interface.uts
├─ unierror.uts
├─ index.uts
├─ app-android/
│ ├─ AndroidManifest.xml
│ ├─ config.json
│ ├─ index.uts
│ └─ DYLFileExportAndroidBridge.kt
└─ app-ios/
├─ config.json
├─ index.uts
└─ DYLFileExportIOSBridge.swift
复制整个目录到其他 uni-app x 项目的 uni_modules,重新制作对应平台的自定义基座后即可使用。

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 97
赞赏 0
下载 12537463
赞赏 1947
赞赏
京公网安备:11010802035340号