在开放 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 给页面一个保存动作——最终产出真实的文件。

参考来源