在 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 把这份能力重新移回页面之下,于是该弹出选择器的点击永远会弹出选择器。

参考来源