跳转至正文

Capacitor 文件处理指南:读取、写入、选择与分享

Capacitor 文件处理指南:在 iOS 和 Android 上读取和写入文件、选择目录、选取、显示、打开、分享、下载和上传文件。

在 Capacitor 移动应用中读写和共享文件

Capacitor 文件处理归结为几个基本模块: @capacitor/filesystem 在应用目录中读写文件, Capacitor.convertFileSrc 包括用于展示文件的组件、用于导入用户文件的选择器,以及用于下载、上传、打开和共享的原生插件。本指南会通过 Capacitor 8 code 逐一演示,说明 iOS 和 Android 上该用哪个目录,并覆盖导致大多数文件 Bug 的内存与权限陷阱。

文件处理工具箱

任务 插件
在应用存储中读取、写入、列出、复制和删除 @capacitor/filesystem
在以下组件中显示本地文件 <img>, <video>, <iframe> Capacitor.convertFileSrc (核心)
让用户选择文件或文件夹 @capgo/capacitor-file-picker
带进度下载 @capacitor/file-transfer 或 @capgo/capacitor-downloader 用于后台、可暂停的下载
带进度上传 @capacitor/file-transfer 或 @capgo/capacitor-uploader 用于后台上传
分享文件或保存至下载目录 @capgo/capacitor-file-sharer 或 @capacitor/share
在其他应用中打开文件 @capacitor-community/file-opener
压缩图片 @capgo/capacitor-file-compressor
压缩与解压 @capgo/capacitor-zip

请先安装核心插件:

bun add @capacitor/filesystem
bunx cap sync

选择正确的目录

每次 Filesystem 调用都需要一个目录 directory。选错目录是导致文件丢失和应用被拒的最常见原因。

Directory iOS 路径 Android 位置 已备份 用于
Data Documents 应用文件目录 (/data/data/<pkg>/files) iOS iCloud、Android 自动备份 应用拥有的用户数据
Documents Documents (与 Data) 公开 Documents 文件夹 是 在 Android 11+ 上请避免使用,详见下文
Library Library 应用文件目录 iOS 支持 应用支持文件、数据库
LibraryNoCloud Library/NoCloud 应用文件目录 否 大型、可重新下载的内容
Cache Library/Caches 应用缓存目录 否 临时文件,可能会被系统清理
External Documents 应用外部文件目录 否 Android 上的大型应用文件,卸载时删除
ExternalStorage Documents 共享存储根目录 不适用 仅限旧版,Android 11+ 无法访问

经验法则:

  • 大型可重新下载的媒体 (离线视频、地图瓦片)存放于此 LibraryNoCloud。Apple 会拒绝将大型可重新下载内容放入 iCloud 备份文件夹的应用,详见其 iOS 数据存储指南。
  • 临时文件 (你共享一次的导出文件、缩略图)应放在 Cache。切勿将用户数据的唯一副本存储在那里。
  • 用户需要在应用外稍后找到的文件 根本不属于应用目录。请通过共享面板、MediaStore 下载或系统文件选择器保存它们。参见我们的指南 在 Capacitor 中实现 Android 分区存储.

写入文件

import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';

// Text file
await Filesystem.writeFile({
  path: 'notes/today.txt',
  data: 'Hello from Capacitor',
  directory: Directory.Data,
  encoding: Encoding.UTF8,
  recursive: true, // create notes/ if missing
});

// JSON
await Filesystem.writeFile({
  path: 'settings.json',
  data: JSON.stringify({ theme: 'dark' }),
  directory: Directory.Data,
  encoding: Encoding.UTF8,
});

// Binary file: pass base64 and no encoding
await Filesystem.writeFile({
  path: 'images/logo.png',
  data: base64Png,
  directory: Directory.Data,
  recursive: true,
});

本指南中介绍的 encoding 选项决定 data 如何被解释。使用 Encoding.UTF8时,字符串作为文本写入。否则,原生平台会将 data 解码为 base64 并写入原始字节。忘记这一点是经典的“我的 PDF 损坏了”错误。

若要追加到现有文件(如日志),请使用 appendFile 使用相同的选项。

读取文件

const { data } = await Filesystem.readFile({
  path: 'settings.json',
  directory: Directory.Data,
  encoding: Encoding.UTF8,
});
const settings = JSON.parse(data as string);

// Binary: returns base64 on native, a Blob on web
const image = await Filesystem.readFile({
  path: 'images/logo.png',
  directory: Directory.Data,
});

使用 stat,当文件不存在时会被拒绝:

async function exists(path: string) {
  try {
    await Filesystem.stat({ path, directory: Directory.Data });
    return true;
  } catch {
    return false;
  }
}

分块读取大文件

readFile 将整个文件作为一个 base64 字符串返回。对于一个 200 MB 的视频,这意味着 JS 内存中会产生数百 MB 的字符串。 readFileInChunks 改用流式传输:

await Filesystem.readFileInChunks(
  { path: 'videos/clip.mp4', directory: Directory.Data, chunkSize: 1024 * 1024 },
  (chunk, err) => {
    if (err) return console.error(err);
    if (chunk === null) return console.log('done');
    processChunk(chunk.data); // base64 chunk
  },
);

更好的做法是完全不让字节经过 JS。大多数“读取大文件”的任务其实是“上传”或“显示”,下面都有对应的原生方案。

列出、复制、移动和删除

const { files } = await Filesystem.readdir({ path: 'notes', directory: Directory.Data });
for (const f of files) console.log(f.name, f.type, f.size, f.mtime);

await Filesystem.mkdir({ path: 'archive', directory: Directory.Data, recursive: true });

await Filesystem.copy({
  from: 'notes/today.txt',
  to: 'archive/today.txt',
  directory: Directory.Data,
});

await Filesystem.rename({
  from: 'archive/today.txt',
  to: 'archive/2026-10-01.txt',
  directory: Directory.Data,
});

await Filesystem.deleteFile({ path: 'notes/today.txt', directory: Directory.Data });
await Filesystem.rmdir({ path: 'notes', directory: Directory.Data, recursive: true });

copy 以及 rename 接受 toDirectory 在目录之间移动,例如从 Cache 改为 Data 在下载完成后。它们也接受绝对 file:// 省略时使用的路径 directory,这就是导入其他插件返回文件的方式。

在 WebView 中显示文件

尽量避免将图片或视频加载为 base64 数据 URL。请将原生路径转换为由 Capacitor 本地服务器提供的 URL:

import { Capacitor } from '@capacitor/core';

const { uri } = await Filesystem.getUri({ path: 'images/logo.png', directory: Directory.Data });
img.src = Capacitor.convertFileSrc(uri);

在 iOS 上,结果看起来像 capacitor://localhost/_capacitor_file_/...,在 Android 上 https://localhost/_capacitor_file_/...。它对 <img>, <video>, <audio>,以及 fetch()有效,这意味着你也可以将文件读取为 ArrayBuffer ,而无需使用 base64:

const buffer = await fetch(Capacitor.convertFileSrc(uri)).then((r) => r.arrayBuffer());

存储路径,而非转换后的 URL。转换后的形式仅在当前应用会话中有效。

从设备选择文件

系统选择器让用户拥有控制权,且在 Android 13+ 或 iOS 上无需存储权限:

bun add @capgo/capacitor-file-picker
bunx cap sync
import { CapgoFilePicker } from '@capgo/capacitor-file-picker';

const { files } = await CapgoFilePicker.pickFiles({
  types: ['application/pdf', 'image/*'],
  limit: 5,
  readData: false, // keep false for anything bigger than a few MB
});

for (const file of files) {
  console.log(file.name, file.mimeType, file.size, file.path);
}

还有 pickImages, pickVideos, pickMedia 以及 pickDirectory,以及 convertHeicToJpeg 在 iOS 上。在 iOS 上, types 只接受 MIME 类型,不接受扩展名。

选中的文件可能位于临时位置,或通过一个 content:// 仅在一段时间内可读的 URI 访问。如果之后需要该文件,请立即用以下方式将其复制到你自己的目录: Filesystem.copy.

下载文件

带进度:@capacitor/file-transfer

Filesystem.downloadFile 在 Filesystem 8 中已弃用。替代方案会将响应直接流式写入磁盘:

bun add @capacitor/file-transfer
bunx cap sync
import { FileTransfer } from '@capacitor/file-transfer';
import { Directory, Filesystem } from '@capacitor/filesystem';

const { uri } = await Filesystem.getUri({ path: 'downloads/report.pdf', directory: Directory.Data });

const listener = await FileTransfer.addListener('progress', (p) => {
  if (p.lengthComputable) setProgress(p.bytes / p.contentLength);
});

await FileTransfer.downloadFile({
  url: 'https://example.com/report.pdf',
  path: uri,
  progress: true,
  headers: { Authorization: `Bearer ${token}` },
});

await listener.remove();

在后台:@capgo/capacitor-downloader

对于用户离开应用后仍应继续下载、并且可以暂停和恢复的大文件,请使用 Capgo 下载器,它依赖于 DownloadManager 在 Android 上,以及后台 URLSession 在 iOS 上:

import { CapacitorDownloader } from '@capgo/capacitor-downloader';

await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
  console.log(id, progress);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
  console.log('done', id);
});

await CapacitorDownloader.download({
  id: 'course-video-12',
  url: 'https://cdn.example.com/course/12.mp4',
  destination: 'videos/12.mp4',
  network: 'wifi-only',
  notification: 'progress', // Android
});

相对路径 destination 在 Android 上解析为应用的外部文件目录,在 iOS 上解析为 Documents 目录,与 Directory.External.

上传文件

不要为了发送文件而将其读取为 base64。两种上传选项都直接从磁盘原生读取:

import { FileTransfer } from '@capacitor/file-transfer';

const result = await FileTransfer.uploadFile({
  url: 'https://api.example.com/upload',
  path: uri,          // full file:// path
  fileKey: 'file',
  mimeType: 'application/pdf',
  headers: { Authorization: `Bearer ${token}` },
  progress: true,
});
console.log(result.responseCode, result.bytesSent);

对于需要在应用进入后台后仍能继续的上传(如视频、批量照片上传),请使用 @capgo/capacitor-uploader,它在后台 URLSession 在 iOS 上,以及在 Android 上带有进度通知的上传服务:

import { Uploader } from '@capgo/capacitor-uploader';

const { id } = await Uploader.startUpload({
  filePath: uri,
  serverUrl: 'https://api.example.com/upload',
  method: 'PUT', // binary body, ideal for presigned S3 or R2 URLs
  mimeType: 'video/mp4',
});

预签名 PUT 用于对象存储的预签名 URL 是处理大文件最可靠的模式:由 API 对 URL 进行签名,设备直接上传到存储。

分享或导出文件

若要允许用户将文件发送到其他应用或保存到可找到的位置:

import { FileSharer } from '@capgo/capacitor-file-sharer';

await FileSharer.share({
  filename: 'report.pdf',
  path: uri,
  contentType: 'application/pdf',
});

// Android: write to Downloads via MediaStore. iOS: opens the share sheet ("Save to Files")
await FileSharer.save({
  filename: 'report.pdf',
  path: uri,
  contentType: 'application/pdf',
});

Capgo 文件共享器会将文件复制到其自身的缓存文件夹,并通过专用的 FileProvider提供,因此您无需编辑 file_paths.xml。使用官方的 @capacitor/share,除非您将其他文件夹添加到 android/app/src/main/res/xml/file_paths.xml.

在其他应用中打开文件

bun add @capacitor-community/file-opener
bunx cap sync
import { FileOpener } from '@capacitor-community/file-opener';

await FileOpener.open({ filePath: uri, contentType: 'application/pdf' });

在 iOS 上,这会打开 Quick Look 预览。在 Android 上,它会打开该 MIME 类型的默认应用或显示选择器。如果没有应用可以处理该类型,调用将被拒绝,因此请捕获异常并改为提供共享选项。

常用文件操作示例

生成 PDF:使用 @capgo/capacitor-pdf-generator 或在 JS 中构建 pdf-lib,然后写入 base64 输出 writeFile。我们的 扫描成 PDF 教程 展示了完整的流程。

上传前清除照片的 GPS 信息: @capgo/capacitor-file-compressor 在所有平台上调整尺寸时移除 EXIF 元数据。

调整图片尺寸或转换格式:同一个压缩器可处理宽度、高度、质量和输出格式,包括 HEIC 转 JPEG。

打包多个文件:用以下方式压缩文件夹 @capgo/capacitor-zip 从而只需上传一个压缩包,而非发起数百次请求。

Web 端支持

@capacitor/filesystem 在 Web 端通过 IndexedDB 工作,因此你的 code 在开发期间可在浏览器中运行。需要注意的区别:二进制读取会返回一个 Blob,文件仅存在于浏览器配置文件中,且存储配额要低得多。对于 Web build,建议优先使用 URL.createObjectURL(blob) 用于显示。

故障排除

“文件不存在”:你混用了目录,或者把绝对路径和相对路径一起传入 directory。请二选一使用。

二进制文件损坏:你用 base64 写入了 Encoding.UTF8。请移除编码。

大文件导致 App 崩溃:通过 bridge 传输了 base64。请使用 chunks、file-transfer 或 fetch(convertFileSrc(uri)).

Android 上图片无法显示:你使用了 file:// 直接使用 URL。用以下方式转换: Capacitor.convertFileSrc.

Failed to find configured root 在 Android 上分享时:文件位于以下路径之外 file_paths.xml。请使用 Capgo 文件分享器或复制到 Cache 首先。

iOS 上的文件会消失:它们原本位于 Cache,iOS 会在存储空间紧张时清理该目录。

因使用 iCloud 存储被 App Store 拒绝:大量可下载内容位于 Data 或 Documents。请将其移动到 LibraryNoCloud.

后续步骤

大多数文件逻辑,例如存储位置、重试规则和命名,都位于 JavaScript 中,并可通过 Capgo 实时更新 在发布后进行调整。对于 Android 端的共享存储和权限,请继续阅读我们的 作用域存储指南,并查看 文件选择器文档 适用于每个选择器选项。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。