更新记录

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 最终回调,位于 successfail 之后

当前 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 后显示“正在保存到系统”,直到收到 successfail

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 类型优先级从高到低为:

  1. 调用方传入的 mimeType
  2. HTTP 响应 Content-Type
  3. 文件扩展名
  4. 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

保证规则:

  • successfail 只会触发其中一个。
  • completesuccessfail 之后触发。
  • 所有结束回调最多执行一次。
  • 即使 successfail 内部抛出异常,插件仍通过 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:错误发生在 downloadexport 阶段。
  • 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 文件管理中找不到文件

检查以下内容:

  1. 等待 success,不要只判断进度 100。
  2. 根据 MIME 类型到 Music、Movies、Pictures 或 Download 查找。
  3. 检查 directoryName,默认是宿主 App 显示名称。
  4. 服务端若返回错误的 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 云打包出现旧错误

确认:

  1. 已保存最新插件源码。
  2. 自定义基座是重新制作的,不是旧基座。
  3. 云端上传的插件目录名和 package.json ID 都是 dyl-file-export
  4. 查看日志中的第一个 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,重新制作对应平台的自定义基座后即可使用。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。