在开放 Web 上,一个 <a download> 链接或 PDF/CSV 接口就会把文件存到用户设备。在 WebView 里,同一个点击静默无作为。Android 只通过宿主侧 DownloadListener 暴露下载,iOS WKWebView 则根本没有下载管理器。宿主自有 runtime 桥接下载层——同一个 API,在每种引擎上都能存下真实文件。
Android:下载需要宿主监听器
Android 官方文档和社区共识一致:裸 WebView 自己不会下载任何东西。用户点击下载链接时,引擎会把请求参数——url、userAgent、contentDisposition、mimeType、contentLength——交给你通过 setDownloadListener 注册的 DownloadListener,否则什么都不发生。没有这个监听器,在 Chrome 里能用的结算页「下载发票 PDF」按钮,在 App 的 WebView 里毫无反应。接入它意味着持有 WebView 实例、从 Content-Disposition 解析文件名、申请 WRITE_EXTERNAL_STORAGE(或交给 DownloadManager)、并向用户展示进度。blob 下载和 <a download> 同理:页面无法自行写入文件,只有宿主可以。
iOS:WKWebView 没有下载管理器
苹果的 WebView 没有内置下载管线。对 WKWebView 局限性的梳理说得很直白:「没有下载管理器,文件下载会失败或静默无作为」。iOS 15 之前唯一的路径是拦截请求、自己重新实现保存。iOS 15 加入了 WKDownloadDelegate,但仍须宿主实现——<a download> 只表达意图,真正执行活在 App 里。默认行为比无作为更糟:文件常常在网页框内打开并打断导航历史,或者点击被直接吞掉、毫无反馈。
下载失效时,什么会崩
- 发票与对账单出不来。 在 Safari 能用的「下载 PDF」按钮,在 WebView 里毫无反应。
- CSV 与报表导出消失。 依赖导出数据的 SaaS 与财务看板丢失了唯一的出口。
- 应用更新与 APK 卡死。 推送 APK 或配置包的更新流程在嵌入式浏览器里失败。
- 内置浏览器更糟。 微信、抖音等内置 WebView 几乎不暴露任何下载面,桌面 QA 时问题被掩盖。
runtime 解法
宿主自有 runtime 桥接下载层。页面调用统一 API——runtime.download(url, options) 或 runtime.saveFile(blob)——宿主通过 Android 原生 DownloadManager 与 iOS 原生 WKDownloadDelegate 保存。文件名、落盘位置、进度、存储权限由原生统一处理。一次集成,每种引擎,同一个存下的文件。
| 维度 | WebView | 宿主自有 runtime |
|---|---|---|
| 触发 | <a download> 无作为 | 统一 runtime.download() |
| Android 路径 | 宿主须接 DownloadListener | 原生 DownloadManager |
| iOS 路径 | 无下载管理器 | 原生 WKDownloadDelegate |
| blob 保存 | 页面无法发起 | 单一 API |
| 权限 | 每个 App 手动处理 | 原生桥 |
| 一致性 | 因引擎而异 | 统一 |
原生 App 能在每台设备上存下文件,裸 WebView 几乎一台都存不了。runtime 给页面一个保存动作——最终产出真实的文件。
参考来源
- dev.to — WebView 的 Android 下载监听器如何运作 — 没有
setDownloadListener,点下载链接毫无反应;监听器把url/mimeType/filename交给宿主。 - DevGex — 在 Android WebView 中启用文件下载 —
setDownloadListener加DownloadManager;需要存储权限。 - gotoapp.store — WKWebView 局限性 — 「没有下载管理器,文件下载会失败或静默无作为」。
- Medium — iOS 15 起从 WKWebView 下载文件 —
WKDownloadDelegate;<a download>只表达意图、执行仍须宿主;canShowMimeType。 - GitHub MAUI 讨论 — iOS WebView 无法下载文件 — iOS 默认 WebView 在页内打开文件或打断导航;必须自行加
WKDownloadDelegate处理。