Electron DownloadItem 详解:主进程文件下载控制 API 与源码实现解析
发布时间:2026/9/5 18:59:06 作者:尧图编辑部 阅读量:1,286

Electron DownloadItem 详解主进程文件下载控制 API 与源码实现解析【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronDownloadItem是 Electron 主进程中专用于控制从远程来源下载文件的对象它不是electron模块的导出项只能作为Session类will-download事件的回调参数获得。读完本篇你将完整掌握DownloadItem的全部实例事件与方法保存路径设置、暂停/恢复/取消、进度与状态查询、断点续传元数据并能结合 Electron 源码理解状态机映射、保存路径决策链和下载取消机制写出带进度、可取消、支持断点恢复的生产级下载功能。类定位一个不可直接构造的事件发射器DownloadItem是一个EventEmitter代表 Electron 中的一个下载项。它与原生下载对象的生命周期绑定方式决定了使用方式进程归属仅存在于主进程Main Process渲染进程无法直接持有它。获取途径该类不从electron模块导出唯一获得实例的方式是监听Session的will-download事件事件回调参数中会传入下载项对象。生命周期下载进入终态后对象即被销毁。源码中 electron_api_download_item.cc 的CheckAlive()会对已销毁的对象抛错DownloadItem used after being destroyed——如果你缓存了item引用并在done事件后再次调用其方法就会看到这条错误。完整使用示例监听 will-download 并全程跟踪下载文档给出的标准用法如下覆盖了设置保存路径 进度监听 终态处理三件事// In the main process. const { BrowserWindow } require(electron) const win new BrowserWindow() win.webContents.session.on(will-download, (event, item, webContents) { // Set the save path, making Electron not to prompt a save dialog. item.setSavePath(/tmp/save.pdf) item.on(updated, (event, state) { if (state interrupted) { console.log(Download is interrupted but can be resumed) } else if (state progressing) { if (item.isPaused()) { console.log(Download is paused) } else { console.log(Received bytes: ${item.getReceivedBytes()}) } } }) item.once(done, (event, state) { if (state completed) { console.log(Download successfully) } else { console.log(Download failed: ${state}) } }) })will-download事件的签名是(event, item, webContents)其中webContents是触发下载的WebContents可用于把下载项与具体的窗口/视图关联起来。有一个文档未明说但源码确认的重要行为在will-download回调中调用event.preventDefault()会取消这次下载。见 electron_api_session.ccvoid Session::OnDownloadCreated(content::DownloadManager* manager, download::DownloadItem* item) { if (item-IsSavePackageDownload()) return; // ... if (item-GetState() download::DownloadItem::INTERRUPTED) handle-SetSavePath(item-GetTargetFilePath()); // ... bool prevent_default Emit(will-download, handle_object, web_contents); if (prevent_default) { item-Cancel(true); item-Remove(); } }这里还有两个实现细节值得注意will-download的底层来源是content::DownloadManager的OnDownloadCreated回调Electron 会为每个原生download::DownloadItem创建或复用一个 JS 包装对象后发出事件当下载项以INTERRUPTED状态重新进入will-download例如会话重启后的可恢复下载时Electron 会自动把保存路径预设为原目标文件路径因此续传下载通常不会再弹出保存对话框。实例事件状态机的两个出口Event: updated下载有更新且尚未结束时发出。Returns:eventEventstatestring - 取值为progressing或interruptedprogressing— 下载正在进行中interrupted— 下载被中断但可以恢复例如断网后重连。Event: done下载进入终态时发出涵盖三种终态completed— 下载成功完成cancelled— 下载被取消例如调用downloadItem.cancel()或在will-download中preventDefault;interrupted— 下载被中断且无法恢复。Returns:eventEventstatestring - 取值为上述三者之一。这四个状态字符串并非随意命名而是从 Chromium 内部枚举直接映射而来。electron_api_download_item.cc 中的 gin 转换器展示了完整映射switch (state) { case download::DownloadItem::IN_PROGRESS: download_state progressing; break; case download::DownloadItem::COMPLETE: download_state completed; break; case download::DownloadItem::CANCELLED: download_state cancelled; break; case download::DownloadItem::INTERRUPTED: download_state interrupted; break; }事件的分发点在 electron_api_download_item.ccvoid DownloadItem::OnDownloadUpdated(download::DownloadItem* item) { if (!CheckAlive()) return; if (download_item_-IsDone()) { Emit(done, item-GetState()); keep_alive_.Clear(); } else { Emit(updated, item-GetState()); } }即只要原生对象未结束就发updated一旦IsDone()为真就发done并解除自保活引用允许该 JS 对象被垃圾回收。实例方法保存位置控制downloadItem.setSavePath(path)pathstring - 下载项的保存文件路径。该 API 仅在session的will-download回调函数中可用。如果path的目录不存在Electron 会递归创建目录。若用户不通过该 API 设置保存路径Electron 将走默认流程来确定保存路径——这通常会弹出系统保存对话框。downloadItem.getSavePath()Returnsstring- 下载项的保存路径。它是setSavePath(path)设置的值或用户在保存对话框中选择的值。downloadItem.savePath属性string属性作用与setSavePath/getSavePath等价仅在will-download回调中可用。从源码的 V8 模板注册可以直接确认两者是同一对底层存取函数// shell/browser/api/electron_api_download_item.cc .SetMethod(setSavePath, DownloadItem::SetSavePath) .SetMethod(getSavePath, DownloadItem::GetSavePath) .SetProperty(savePath, DownloadItem::GetSavePath, DownloadItem::SetSavePath)downloadItem.setSaveDialogOptions(options)optionsSaveDialogOptions - 设置保存对话框选项。该对象的属性与dialog.showSaveDialog()的options参数完全相同。该 API 允许你自定义下载默认弹出的保存对话框例如指定标题、默认路径、Windows 下的文件类型过滤器。同样仅在will-download回调中可用。downloadItem.getSaveDialogOptions()ReturnsSaveDialogOptions- 返回之前通过setSaveDialogOptions(options)设置的选项对象。暂停 / 恢复 / 取消downloadItem.pause()暂停下载。downloadItem.isPaused()Returnsboolean- 下载是否处于暂停状态。downloadItem.resume()恢复已暂停的下载。源码中对应download_item_-Resume(true /* user_gesture */)即以用户手势语义恢复electron_api_download_item.cc。[!NOTE] 要启用断点续传目标服务器必须支持 Range 请求并返回Last-Modified和ETag响应头。否则resume()会丢弃已接收的字节从头开始下载。downloadItem.canResume()Returnsboolean- 下载是否可以恢复。downloadItem.cancel()取消下载操作。取消后done事件将以cancelled状态发出。下载信息读取方法返回类型说明getURL()string下载项的原始 URLgetMimeType()string文件的 MIME 类型hasUserGesture()boolean下载是否由用户手势触发getFilename()string下载项的文件名见下方说明getCurrentBytesPerSecond()Integer当前下载速度字节/秒getTotalBytes()Integer下载项总大小字节大小未知时返回 0getReceivedBytes()Integer已接收字节数getPercentComplete()Integer下载完成百分比getContentDisposition()string响应头中的 Content-Disposition 字段getState()string当前状态progressing、completed、cancelled或interrupted关于getFilename()的一个重要提示[!NOTE] 文件名不一定与最终保存到磁盘的文件名一致。如果用户在弹出的保存对话框中修改了文件名实际保存的文件名就会不同。getFilename()的底层实现并非简单读取请求 URL而是调用 Chromium 的net::GenerateFileName(url, content_disposition, suggested_filename, mime_type, ...)按优先级综合 URL、Content-Disposition 头、服务器建议文件名与 MIME 类型生成electron_api_download_item.cc。这与 electron_download_manager_delegate.cc 中默认保存路径生成所用的逻辑一致。会话重启后续传专用方法[!NOTE] 以下方法专门用于在 session 重启后恢复cancelled的下载项。方法返回类型说明getURLChain()string[]完整 URL 链包含所有重定向getLastModifiedTime()stringLast-Modified 响应头的值getETag()stringETag 响应头的值getStartTime()Double下载开始时刻的 UNIX 时间戳秒getEndTime()Double下载结束时刻的 UNIX 时间戳秒这组方法正好对应 Chromium 下载恢复所需的全部元数据URL 链、内容标识ETag / Last-Modified、时间戳。getETag()与getLastModifiedTime()也正是上一节resume()断点续传的前提条件——服务器需要用它们校验已下载部分是否仍然有效。源码纵深保存路径是如何被决定的文档说未设置保存路径时会弹出保存对话框这条默认流程在 electron_download_manager_delegate.cc 的DetermineDownloadTarget()中体现为一条清晰的决策链强制路径若原生下载项带有GetForcedFilePath()如session.downloadURL的内部机制直接使用JS 侧设置的路径通过GetItemSavePath()回查 JS 包装对象即你在will-download中调用setSavePath或item.savePath ...设置的值非空则直接作为目标路径——这正是设置保存路径后不弹对话框的实现依据默认路径以上皆无时先用上次保存目录或系统默认下载目录kDownloadDefaultDirectory拼出候选路径再进入OnDownloadPathGenerated()若GetItemSavePath()仍为空则弹出保存对话框可用setSaveDialogOptions定制标题、默认路径、过滤器等用户取消对话框时回调以空路径 DOWNLOAD_INTERRUPT_REASON_USER_CANCELED通知下载管理器下载随之以取消/中断终态结束。will-download 回调 (JS) ├─ setSavePath / savePath / setSaveDialogOptions 写入 JS 包装对象 └─ preventDefault() ── item-Cancel(true); item-Remove(); // 直接取消 DetermineDownloadTarget (C) ├─ GetForcedFilePath() 非空 ────────────── 直接使用该路径 ├─ GetItemSavePath() 非空 ─────────────── 直接使用该路径不弹窗 └─ 均为空 ── CreateDownloadPath() 生成候选路径 └─ OnDownloadPathGenerated() ├─ 仍无路径 ── ShowSaveDialog()按 dialog options 定制 │ ├─ 用户选择 ── 记住目录SetSavePath(path) │ └─ 用户取消 ── 空路径回调 ── 下载被取消 └─ 已有路径 ── 直接回调 DownloadTargetInfo另外JS 包装对象与原生对象通过弱引用关联DownloadItem的构造时会以UserData键把cppgc::WeakPersistentDownloadItem挂到原生download::DownloadItem上electron_api_download_item.cc两侧的销毁互不阻塞OnDownloadDestroyed时 JS 侧置空指针并清掉自保活。这就是为什么done之后再触碰该对象会抛DownloadItem used after being destroyed也是缓存item前必须先想清楚事件时序的原因。测试用例中的行为印证Electron 的官方测试套件 api-session-spec.ts 完整覆盖了上述 API 的实际行为可作为验证依据will-download事件参数中可读取getURL()且对象在销毁后调用getURL()会抛DownloadItem used after being destroyedL675-L705session.downloadURL(url)触发下载后在will-download回调中设置item.savePath downloadFilePathdone事件返回completed且item.savePath为绝对路径、getReceivedBytes()/getTotalBytes()与服务器实际发送字节数一致、getFilename()/getMimeType()/getContentDisposition()与响应头一致L1362-L1414测试还覆盖了带Authorization请求头的downloadURL、下载失败与取消、暂停后resume()恢复等场景L1460 起、L1813 起。session.downloadURL()是除页面内a download、window.location等导航外触发下载的另一入口will-download事件与DownloadItem对其同样生效。实践要点小结在will-download回调内完成所有配置setSavePath、setSaveDialogOptions与savePath属性都只在该回调中生效不在回调内设置等于放弃控制落入弹窗默认流程。需要静默下载就显式setSavePath设置后 Electron 递归创建目录并直接落盘不弹对话框想控制对话框外观则用setSaveDialogOptions与dialog.showSaveDialog的 options 相同。用updated驱动进度 UIprogressingisPaused()区分暂停与传输中配合getReceivedBytes()/getPercentComplete()/getCurrentBytesPerSecond()可绘制完整的进度条与速率显示。区分interrupted的两个语义updated中的interrupted表示中断但可恢复done中的interrupted表示中断且无法恢复前者应提示用户等待或调用resume()后者只能按失败处理。断点续传依赖服务器能力resume()只有在服务器支持 Range 且返回Last-ModifiedETag时才真正续传否则从头下载getETag()、getLastModifiedTime()、getURLChain()、getStartTime()、getEndTime()就是为跨会话恢复下载准备的元数据。不要跨终态缓存 itemdone事件发出后 JS 对象即失效后续调用会抛错需要在done回调里一次性取好所需信息如getSavePath()、getFilename()再使用。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考