在 Chrome 里一点就弹出的文件选择器,在 WebView 里纹丝不动。Android 的 WebView 默认会取消每一次文件请求,除非宿主 App 实现了 WebChromeClient.onShowFileChooser(旧版本还要 openFileChooser);漏掉回调、运行时权限或 Activity 结果中的任何一个,点击就毫无反应——没有报错,也没有选择器。iOS 上,WKWebView 在微信、抖音、Instagram 这类内置浏览器里会把 capture=camera 悄悄降级成相册;而 iOS 一个长期存在的 WebKit 行为会让每一次拍照都把 FileList 重置成一项。这些都是 App 侧能力,不是 Web 侧——所以上传能用是包装器的属性,不是页面的属性。宿主自有 runtime 提供一条统一的文件与相机通道,在每台设备、每个系统版本上都表现一致。
点击毫无反应的输入框
Android 上,<input type="file"> 自己不会弹出选择器。平台把它路由到 WebChromeClient.onShowFileChooser——文档说,当网页请求上传或保存文件时这个回调被调用。文档对默认行为说得很直白:WebView 会取消所有文件请求。所以在一个没实现该回调的包装器里,输入框渲染正常、用户点它,却什么都不发生。没有报错、没有选择器——页面在外面看完全健康。Android 4.x 上那个旧的 openFileChooser 是另一套隐藏 API 覆盖;如果用户在选择器里取消时忘记回调,输入框会一直死到页面刷新。
一个文件前的三道门
就算回调写好了,一次上传还得再跨三道页面给不了的坎:manifest 权限(READ_EXTERNAL_STORAGE 或相机)、Android 运行时权限(API 23+)、以及对 Activity 结果和分区存储(Android 10+)的正确处理。漏掉任何一道,失败都是静默的——黑屏、空的 files 列表,或者一个页面读不了的 blob URL。这就是为什么「WebView 里文件上传用不了」是最高频的 WebView 支持问题之一:它看着是个标准 HTML 控件,坏起来却像少了个原生功能。
iOS:capture 撒谎,FileList 只有一张
iOS 上 WKWebView 确实处理文件输入,但 capture 属性在微信、抖音、Instagram 这类内置浏览器里不可靠——capture=camera 被无视,用户被直接丢进相册。更糟的是,iOS 的系统选择器在每次会话都会把整个 FileList 替换掉,于是 Web 应用永远无法在多次点击间累加多张拍照;Apple 自己的开发者论坛确认这个行为一直延续到 iOS 26.4。而通过 getUserMedia(WebRTC)拿相机/麦克风,在 iOS 14.3 之前的 WKWebView 里根本不支持,之后也仍需要 delegate、entitlement 和安全上下文。
没有统一 I/O 层,你会失去什么
- 一致的上传。 同一个
<input type="file">在 Chrome 能用、在你的 App 里却失败,因为选择器是 App 侧代码。 - 相机采集。 capture=camera 在不同 WebView 里含义不同;内置浏览器会悄悄降级它。
- 多文件流程。 Android 多选与 iOS 累加拍照都需要页面给不了的定制代码。
- 下载。 WebView 自己不会存文件;没有 setDownloadListener,一个 PDF 或 CSV 链接什么都做不了。
- 真实的测试面。 在 Safari 里能说明不了任何事,因为每个包装器都是同一段 HTML 的不同实现。
runtime 解法
宿主自有 runtime 在页面之下提供一条文件与媒体通道。它为每个系统、每台设备只实现一次选择器,统一映射 accept 属性与相机/相册意图,处理好多选与累加拍照,让 FileList 按规范意图工作,把下载桥接到受管存储,再在中央统一应用安全上下文与权限规则。页面调用标准的 <input type="file"> 与 capture,runtime 保证结果。没有逐 App 的样板代码,没有静默死掉的输入框,没有在 Safari 里能的意外。
| 维度 | WebView | 宿主自有 runtime |
|---|---|---|
| 文件选择器 | App 须实现 onShowFileChooser;默认取消 | 统一选择器,一次实现 |
| 相机采集 | capture=camera 在内置浏览器被无视 | 统一采集意图,不降级 |
| 多文件 | Android 多选与 iOS 累加需定制代码 | 原生多选 + 累加拍照 |
| 权限门槛 | manifest、运行时、分区存储全手动 | 集中处理,一次强制 |
| 下载 | 需 setDownloadListener,否则无反应 | 受管存储,自动 |
| 测试面 | 逐包装器行为,Safari 能是谎言 | 一个 runtime,一种行为 |
文件输入是 Web 上最普通的控件,也是最容易在 WebView 里被悄悄弄坏的那个。它坏,不是因为页面错了,而是因为选择器活在 App 里、不在浏览器里。runtime 把这份能力重新移回页面之下,于是该弹出选择器的点击永远会弹出选择器。
参考来源
- Android 开发者——WebChromeClient.onShowFileChooser:默认行为是 WebView 会取消所有文件请求 — 裸 WebView 文件输入失效的文档依据。
- Android 开发者——用 WebView 构建 Web 应用:文件访问、权限与 onShowFileChooser 要求 — 宿主 App 必须打开的全部门槛。
- Apple 开发者论坛——iOS WKWebView 每次拍照都把 FileList 重置成一项(延续到 iOS 26.4) — 一个被确认、跨版本的 WebKit 限制。
- web.dev——Android 上的 Web:WebView 把敏感行为委托给宿主 App — 为何上传与下载是 App 侧能力。
- Stack Overflow——Android WebView input type=file 需实现四套隐藏回调 — 样板代码的实际范围。