跳转至正文

Capacitor 的 cordova-plugin-file-transfer 替代方案

在 Capacitor 应用中替换 cordova-plugin-file-transfer:简单传输用 @capacitor/file-transfer,后台任务用 Capgo 的上传器和下载器。

将 Cordova 应用迁移至 Capacitor 的示意图,用于文件传输插件指南

对于 Capacitor 应用来说,最好的 cordova-plugin-file-transfer 替代方案取决于一个问题:传输是否需要在应用进入后台后继续运行?对于带进度显示的普通上传和下载,请使用官方 @capacitor/file-transfer 插件。对于大文件、不稳定的网络,或用户切换应用时必须继续进行的传输,请使用 @capgo/capacitor-uploader 以及 @capgo/capacitor-downloader,它们运行在原生后台传输 API 上。本指南将对它们进行对比,并映射每一个 FileTransfer 调用。

cordova-plugin-file-transfer 是否仍处于弃用状态?

这段历史比较混乱,以下是事实:

  • 2017 年,Apache Cordova 团队宣布计划弃用该插件,并引导开发者转向 XMLHttpRequest Level 2。
  • 该插件在 npm 上从未被标记为弃用。2.0.0 版本于 2023 年 9 月发布,2.0.1 版本于 2026 年 8 月发布。GitHub 仓库目前仍处于活跃状态。

所以它并没有消亡。但在 Capacitor 应用中,它仍然是一个 Cordova 插件,它会:

  • 依赖于 cordova-plugin-file 及其 cdvfile:// 以及 file:// 路径模型,
  • 不符合 Capacitor 的 Filesystem 目录和 URI 转换,
  • 不支持后台传输,
  • 使用回调式 API。

如果你正在从 Cordova 迁移,用原生 Capacitor 插件替换它,就能消除继续保留 Cordova 的最后几个理由之一 cordova-plugin-file.

为什么“直接用 fetch”在处理大文件时会失效

Apache 建议的替代方案 XHR 适用于小型 JSON 和图片。但在混合应用进行真正的文件传输时,它会失败:

  • 内存。 fetch 而 XHR 会给你一个 Blob 或 ArrayBuffer 在 WebView 中。一个 500 MB 的视频意味着 WebView 内存中要占用 500 MB,然后还要通过 bridge 进行一次拷贝。这会导致中端 Android 设备上的渲染进程崩溃。
  • 挂起。 当用户切换应用时,iOS 会挂起 WebView,导致进行中的请求停滞或失败。
  • 缺乏系统集成。 没有系统级下载通知,没有“仅限 Wi-Fi”选项,连接中断后也无法恢复。

Native plugins 直接在磁盘和网络之间流式传输,让 JavaScript 不介入数据路径。

方案对比

需要 cordova-plugin-file-transfer @capacitor/file-transfer @capgo/capacitor-uploader @capgo/capacitor-downloader
下载到磁盘 是 是 否 是
从本地磁盘上传 是 是 是 否
进度事件 是 可以(progress listener) 可以(events) 可以(downloadProgress)
Multipart 表单数据 是 是(默认) 是 不适用
原始二进制 PUT(预签名 URL) 是 是(设置 Content-Type) 可以(uploadType: 'binary') 不适用
单次请求多个文件 否 否 可以(files) 不适用
取消 abort() 否 removeUpload stop
暂停 / 恢复 否 否 否 pause, resume
重试次数 否 否 maxRetries 否
后台持续运行 否 应用处于活跃状态时 是 是
仅限 Wi-Fi 否 否 否 network: 'wifi-only'
HTTP 错误详情 http_status, body httpStatus, body 事件中的状态 code 事件中的错误字符串
Web 实现方式 否 是 是(基于 fetch) 否

大多数应用最终会选择 @capacitor/file-transfer 用于小型、用户可见的传输,并使用 Capgo 插件处理媒体上传队列和离线内容下载。

方案 1:@capacitor/file-transfer

bun add @capacitor/file-transfer @capacitor/filesystem
bunx cap sync

该插件需要完整的原生路径。请从以下位置获取: @capacitor/filesystem.

下载

import { FileTransfer } from '@capacitor/file-transfer';
import { Filesystem, Directory } from '@capacitor/filesystem';

export async function downloadReport(url: string) {
  const { uri } = await Filesystem.getUri({
    directory: Directory.Data,
    path: 'reports/latest.pdf',
  });

  const progress = await FileTransfer.addListener('progress', (p) => {
    if (p.type === 'download' && p.lengthComputable) {
      console.log(`Downloaded ${Math.round((p.bytes / p.contentLength) * 100)}%`);
    }
  });

  try {
    const result = await FileTransfer.downloadFile({
      url,
      path: uri,
      progress: true,
      headers: { Authorization: 'Bearer <token>' },
    });
    return result.path;
  } catch (error: any) {
    if (error.code === 'OS-PLUG-FLTR-0010') {
      console.error('HTTP error', error.data?.httpStatus, error.data?.body);
    }
    throw error;
  } finally {
    await progress.remove();
  }
}

上传

export async function uploadAvatar(path: string) {
  const result = await FileTransfer.uploadFile({
    url: 'https://api.example.com/v1/avatar',
    path,
    fileKey: 'avatar',
    mimeType: 'image/jpeg',
    headers: { Authorization: 'Bearer <token>' },
    progress: true,
  });
  console.log(result.responseCode, result.response);
}

上传默认使用 multipart 格式。若需原始流(例如上传到预签名 S3 URL),请设置 method: 'PUT' 以及显式的 Content-Type 请求头,例如 application/octet-stream.

错误码遵循 OS-PLUG-FLTR-XXXX 格式。有用的: 0007 文件未找到, 0008 连接失败, 0010 HTTP 错误状态,带有 httpStatus 以及 body 中的 error.data.

方案 2:使用 @capgo/capacitor-uploader 进行后台上传

bun add @capgo/capacitor-uploader
bunx cap sync
import { Uploader } from '@capgo/capacitor-uploader';

await Uploader.addListener('events', async (event) => {
  switch (event.name) {
    case 'uploading':
      console.log(event.id, `${event.payload.percent}%`);
      break;
    case 'completed':
      console.log(event.id, 'done', event.payload.statusCode);
      break;
    case 'failed':
      console.error(event.id, event.payload.error);
      break;
  }
  // Completed and failed events are cached natively until acknowledged
  if (event.eventId) {
    await Uploader.acknowledgeEvent({ eventId: event.eventId });
  }
});

const { id } = await Uploader.startUpload({
  filePath: 'file:///path/to/video.mp4',
  serverUrl: 'https://api.example.com/v1/videos',
  method: 'POST',
  uploadType: 'multipart',
  fileField: 'video',
  parameters: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
  maxRetries: 3,
  notificationTitle: 'Uploading video', // Android only
});

对于预签名 URL,请使用 method: 'PUT',默认使用二进制 body:

await Uploader.startUpload({
  filePath,
  serverUrl: presignedUrl,
  method: 'PUT',
  headers: { 'Content-Type': 'video/mp4' },
  mimeType: 'video/mp4',
});

在一个 multipart 请求中上传多个文件:

await Uploader.startUpload({
  serverUrl: 'https://api.example.com/v1/photos',
  method: 'POST',
  uploadType: 'multipart',
  files: [
    { filePath: photo1, fieldName: 'images[]', mimeType: 'image/jpeg' },
    { filePath: photo2, fieldName: 'images[]', mimeType: 'image/jpeg' },
  ],
  headers: { Authorization: 'Bearer <token>' },
});

使用 Uploader.removeUpload({ id }).

为什么要确认事件? 已完成和失败的事件会存储在持久化原生缓存中,以便在应用关闭或进入后台且尚未处理时重新投递。确认事件会将其移除,防止插件下次初始化时重新广播。

iOS 配置。 上传器使用后台 URLSession。许多应用无需额外配置。如果需要在应用挂起后继续上传,请添加 fetch 改为 UIBackgroundModes。除非其他功能使用 processing ,否则不要添加 BGTaskScheduler,因为 App Store Connect 会拒绝声明了该权限但未实际使用的构建 BGTaskSchedulerPermittedIdentifiers.

方案 3:使用 @capgo/capacitor-downloader 进行后台下载

bun add @capgo/capacitor-downloader
bunx cap sync
import { CapacitorDownloader } from '@capgo/capacitor-downloader';

await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
  console.log(id, `${progress}%`);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
  console.log(id, 'completed');
});
await CapacitorDownloader.addListener('downloadFailed', ({ id, error }) => {
  console.error(id, error);
});

await CapacitorDownloader.download({
  id: 'course-42',
  url: 'https://cdn.example.com/courses/42.zip',
  destination: 'courses/42.zip',
  headers: { Authorization: 'Bearer <token>' },
  network: 'wifi-only',
  priority: 'normal',
  notification: 'progress', // Android: remove the system notification when done
});

// Later
await CapacitorDownloader.pause({ id: 'course-42' });
await CapacitorDownloader.resume({ id: 'course-42' });
const status = await CapacitorDownloader.checkStatus({ id: 'course-42' });
console.log(status.state, status.progress);

相对路径 destination 在 iOS 上解析为应用的 Documents 目录,在 Android 上解析为应用专属的外部文件目录。您也可以传入绝对路径或 file:// URL。使用 getFileInfo({ path }) 在下载后读取文件大小和 MIME 类型,并使用 stop({ id }) 取消并删除部分数据。

从 cordova-plugin-file-transfer 迁移

下载

// Before
const ft = new FileTransfer();
ft.onprogress = (e) => e.lengthComputable && setProgress(e.loaded / e.total);
ft.download(
  encodeURI(url),
  cordova.file.dataDirectory + 'report.pdf',
  (entry) => console.log(entry.toURL()),
  (err) => console.error(err.code, err.http_status),
  false,
  { headers: { Authorization: 'Bearer <token>' } },
);
// After
const { uri } = await Filesystem.getUri({ directory: Directory.Data, path: 'report.pdf' });
await FileTransfer.downloadFile({
  url,
  path: uri,
  progress: true,
  headers: { Authorization: 'Bearer <token>' },
});

上传

// Before
const options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = 'photo.jpg';
options.mimeType = 'image/jpeg';
options.params = { albumId: '7' };
options.headers = { Authorization: 'Bearer <token>' };
new FileTransfer().upload(fileUrl, encodeURI(serverUrl), onSuccess, onError, options);
// After, foreground
await FileTransfer.uploadFile({
  url: serverUrl,
  path: fileUrl,
  fileKey: 'file',
  mimeType: 'image/jpeg',
  params: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
});

// After, background
await Uploader.startUpload({
  filePath: fileUrl,
  serverUrl,
  uploadType: 'multipart',
  fileField: 'file',
  parameters: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
});

请注意 params 中的 @capacitor/file-transfer 是 URL 查询参数。如果您的服务器期望接收表单字段,请使用 Capgo 上传器的 parameters,它们以 multipart 表单字段的形式发送。

映射表

cordova-plugin-file-transfer @capacitor/file-transfer Capgo 插件
download(source, target, ...) downloadFile({ url, path }) CapacitorDownloader.download({ id, url, destination })
upload(file, server, ...) uploadFile({ url, path }) Uploader.startUpload({ filePath, serverUrl })
onprogress addListener('progress') addListener('events') / addListener('downloadProgress')
abort() 不可用 removeUpload({ id }) / stop({ id })
options.fileKey fileKey fileField 或 files[].fieldName
options.params params (查询字符串) parameters (表单字段)
options.httpMethod method method (POST 或 PUT)
options.chunkedMode chunkedMode 原生处理
cordova.file.dataDirectory Filesystem.getUri({ directory: Directory.Data }) 相对路径或 file:// URL
FileTransferError.http_status error.data.httpStatus event.payload.statusCode

路径:迁移中最容易出问题的部分

Cordova code 到处传递路径 cdvfile:// 或 cordova.file.* 在 Capacitor 中:

  • 从以下来源获取原生路径: Filesystem.getUri() 或从创建文件的插件(相机、文件选择器、录音机)获取。
  • 要在 WebView 中显示下载的图片,请用以下方式转换原生路径: Capacitor.convertFileSrc(path).
  • Capgo 有一个 文件插件 以及 文件选择器 如果 @capacitor/filesystem 无法覆盖您的场景。

故障排除

上传在 Android 上正常,但在 iOS 上因文件未找到而失败。 您传入的是 capacitor:// WebView URL,而不是原生 file:// 路径。请使用 Filesystem.getUri().

服务器以 415 拒绝上传。 Multipart 与原始请求体不匹配。对于预签名 URL,请使用 PUT 并显式指定 Content-Type.

iOS 上后台上传会停止。 用户强制退出应用,导致后台会话被取消,或者 fetch 需要时缺少后台模式。

Android 显示卡住的下载通知。 设置 notification: 'progress' 或 'hidden' 在下载器上。

大文件下载时内存崩溃。 你仍在使用 fetch 或通过 bridge 以 base64 读取文件。请切换到原生磁盘下载。

发布迁移

更换原生插件需要新的商店构建,这会 Capgo 构建 可在云端为 iOS 和 Android 运行。之后,驱动上传和下载的 JavaScript 可以通过 Capgo 实时更新进行修复和改进。关于从 Cordova 迁移到 Capacitor 的其余部分,请参阅我们的 迁移指南,以及其他付费或旧版原生 SDK,请参阅 Ionic Enterprise 插件替代方案.

为 Capacitor 应用提供 Live Updates

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

博客最新动态

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