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 端的共享存储和权限,请继续阅读我们的 作用域存储指南,并查看 文件选择器文档 适用于每个选择器选项。