You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ember.js中使用Capacitor的downloadFile?原生端报错排查

Ember.js + Capacitor 文件下载原生端报错排查与路径配置指南

问题背景

使用Ember.js开发组件实现文件下载功能,基于Capacitor工具开发。Web应用运行正常,但原生应用触发下载时,Xcode抛出错误:

[error] - {"errorMessage":"Unable to download file","message":"Unable to download file","code":"DOWNLOAD"}

组件代码路径:/components/component.js

import Component             from '@glimmer/component';
import { action }            from '@ember/object';
import { Plugins, FilesystemDirectory, CapacitorHttp, Diretory } from '@capacitor/core';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { Http } from '@capacitor-community/http'

export default class ExportLink extends Component {
  @action
  async onClick() {
    this.isRequesting = true;

    try {
      // Fetch the download url, since a download has no Authentication information
      const urlResponse = await this.store
        .fetch('signed_urls', { type: 'post', body: { request_url: this.requestUrl } });
      const options = {
        url: urlResponse.url,
        filePath: `${this.fileName}.${this.args.extension}`,
        fileDirectory: Directory.Documents,
      };
      const response = await Http.downloadFile(options);
      const blob     = await response.blob;
      const downloadResponse = new Response(blob, {
        status: response.status,
        headers: (new Headers(response.headers))
      })

      if (!downloadResponse.ok) {
        throw downloadResponse.statusText
      }

      // create a new download link. Add the content of the download, so the download prompt is immediatly invoked
      const link             = document.createElement('a');
      const objectUrl        = URL.createObjectURL(blob);

      link.setAttribute('href', objectUrl);
      link.setAttribute('download', `${this.fileName}.${this.args.extension}`);
      link.setAttribute('rel', 'noopener noreferrer');
      link.setAttribute('target', '_blank'); // fallback if a direct download isnt supported

      link.dispatchEvent(new MouseEvent('click'));

      URL.revokeObjectURL(objectUrl);
    }
    catch(e) {
      console.error({e: e});
      this.notifications.add(this.intl.t('general.forms.unknown_error'), { type: 'warning' });
    }
    finally {
      this.isRequesting = false;
    }

    return false;
  }
}

错误排查与修复

1. 冗余导入与拼写错误

代码中存在重复导入和拼写错误,会干扰Capacitor API的正常调用:

  • 删除冗余导入行:import { Plugins, FilesystemDirectory, CapacitorHttp, Diretory } from '@capacitor/core';(已单独导入Filesystem和Directory,且Diretory存在拼写错误)
  • 确保仅保留必要导入:import { Filesystem, Directory } from '@capacitor/filesystem'; 和 import { Http } from '@capacitor-community/http'

2. 原生端与Web端处理逻辑差异

Capacitor的Http.downloadFile在原生端直接将文件保存到指定目录,不会返回blob对象,原代码中response.blob的逻辑仅适用于Web端,这是导致原生报错的核心原因:

修改后的核心处理逻辑:

// 替换原response处理代码
const response = await Http.downloadFile(options);

if (response.status !== 200) {
  throw new Error('下载请求失败');
}

// 区分原生/Web环境处理
if (Capacitor.isNativePlatform()) {
  // 原生端:获取文件URI并触发系统打开/分享
  const fileUri = await Filesystem.getUri({
    path: options.filePath,
    directory: options.fileDirectory
  });
  // 调用Share插件让用户选择打开方式
  await Plugins.Share.share({ url: fileUri.uri });
} else {
  // Web端:保留原blob下载逻辑
  const blob = await response.blob;
  const downloadResponse = new Response(blob, {
    status: response.status,
    headers: new Headers(response.headers)
  });

  if (!downloadResponse.ok) {
    throw downloadResponse.statusText;
  }

  const link = document.createElement('a');
  const objectUrl = URL.createObjectURL(blob);
  link.setAttribute('href', objectUrl);
  link.setAttribute('download', `${this.fileName}.${this.args.extension}`);
  link.setAttribute('rel', 'noopener noreferrer');
  link.dispatchEvent(new MouseEvent('click'));
  URL.revokeObjectURL(objectUrl);
}

3. 原生平台权限配置

iOS和Android需配置文件访问权限,否则会因权限不足导致下载失败:

  • iOS:在Info.plist中添加NSDocumentsFolderUsageDescription,填写访问文档目录的用途说明(如"需要访问文档目录保存下载文件")
  • Android:在AndroidManifest.xml中添加权限(Android 13+可使用更细粒度权限):
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    

附加问题:Android/iOS端filePath设置规范及与报错的关联

正确设置方式

  • iOS:
    • 直接写文件名:如filePath: 'report.pdf',文件会保存到Documents根目录
    • 子目录路径:如filePath: 'exports/report.pdf',需提前用Filesystem.mkdir创建exports目录
  • Android:
    • 直接写文件名:文件保存到内部存储/Documents目录
    • 子目录路径:如filePath: 'my_app/report.xlsx',同样需提前创建子目录

与当前报错的关联

如果filePath指定的子目录不存在且未提前创建,会导致Capacitor无法写入文件,触发DOWNLOAD错误。确保路径中的目录已创建,或直接使用根文件名即可避免此类问题。

内容的提问来源于stack exchange,提问作者Mikelemuel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 05:07:51