在 WebView 里,一个网页要拿相机或定位,必须过两道独立的门——系统权限和 WebView 权限;而在 Android 上,如果 App 没实现那个回调,第二道门默认就是拒绝。更糟的是,Android 的 WebView 把 Web 权限处理的全部控制权交给宿主 App,不像真正的浏览器有按站点的同意——一旦系统授予了定位,App 就能把定位/相机分享给 WebView 里加载的任何页面,不再有任何提示。iOS 上,WKWebView 在 App 的提示之外又加了一道提示,而 TikTok、Instagram 这类内置浏览器官方不支持。宿主自有 runtime 提供统一的权限代理,把原生与 Web 同意对齐、强制按源授权、要求安全上下文,并只弹一次一致的提示。
两道门,第二道默认说「不」
Android 上,页面调用 getUserMedia 或 navigator.geolocation 会撞上两道检查。第一道是系统权限——CAMERA、RECORD_AUDIO 或定位在 manifest 里声明、并在运行时(API 23+)被授予。第二道是 WebView 权限:就算系统已经授权,页面在 App 这边仍表现为 WebChromeClient.onPermissionRequest(相机/麦克风)或 onGeolocationPermissionsShowPrompt(定位),而默认答案是否。没实现这个回调,症状会让人抓狂——权限已授予、getUserMedia 已调用、却什么都没发生,没有对话框,只有被 reject 的 promise。定位只对安全来源调用:面向 Android 7.0+ 的 App,HTTP 页面在任何人点击之前就被自动拒绝。
没有「按站点」同意,不像浏览器
一项 2025 年关于 Android WebView 权限执行的研究揭示了结构性缺口:传统浏览器会针对每个站点弹出提示、让用户逐个授予或拒绝,而 Android 的 WebView 把整个决定权委托给宿主 App。如果 App 实现了回调,它可以为任意源授予或拒绝,而系统里没有内置的按站点记忆或面向用户的控制。更深的隐患是:一旦系统把定位权限授予了 App,App 就能把这份定位交给 WebView 里加载的任何网站、无需用户再次同意——用户根本看不到是哪个页面在访问什么。开发者必须自己把这套同意 UI 做出来,否则它不存在。
iOS:第二道提示,与静默降级
iOS 上,WKWebView 的相机/麦克风访问要求 App 实现 requestMediaCapturePermissionFor(iOS 15+)以及相应 entitlement;在此之前,WebRTC 在 WKWebView 里根本不可用。即便实现了,用户仍会看到两道提示——一道来自 App,一道来自站点——而文档给出的解法是让 App 通过 delegate 跳过第二道,这意味着用户按页面的选择由 App 而非页面决定。当页面通过自定义 scheme handler 加载时,提示会把 App 的名字当作来源显示,让用户困惑。而微信、抖音、Instagram 这类内置浏览器带有额外约束、UI 也会坏,官方明确不予支持。
没有权限代理,你会失去什么
- 单一事实来源。 原生与 Web 的权限状态各住各处,会漂移、会矛盾。
- 按源控制。 Android WebView 没有按站点的授权记忆;App 要么全给、要么全拒。
- 用户可见性。 用户无法判断是哪个页面在访问相机或定位,因为同意活在 App 代码里。
- 安全上下文强制。 HTTP 页面在现代 Android 上静默失去定位,原因不可见。
- 一致的提示。 iOS 双重提示;内置浏览器干脆直接坏掉。
runtime 解法
宿主自有 runtime 在页面之下提供统一的权限代理。它维护一份原生与 Web 共用的同意存储,像浏览器那样强制按源授权,在任何敏感 API 被提供之前要求安全上下文,并呈现一次一致、用户可读的提示——点名页面与资源。App 的系统授权与页面的 Web 请求在同一处被调解,于是同意可见、可撤销,也绝不会被悄悄分享给未知来源。
| 维度 | WebView | 宿主自有 runtime |
|---|---|---|
| 同意模型 | App 决定一切,无按站点记忆 | 统一存储,按源授权 |
| Android 第二道门 | onPermissionRequest 默认拒绝 | 代理按源授予,可见 |
| iOS 提示 | 两道提示,App 可跳过站点提示 | 一次一致提示 |
| 安全上下文 | HTTP 定位静默拒绝 | 强制,并解释 |
| 用户可见性 | 看不到哪个页面在访问什么 | 提示点名页面与资源 |
| 内置浏览器 | 不支持,UI 坏 | 一个 runtime,到处支持 |
浏览器之所以值得信任,是因为它会在正确的时刻、向正确的对象、问正确的问题。原生 WebView 把这份决定推进不透明的 App 代码,然后默认说「不」。runtime 把浏览器的同意契约夺回来——按源、可见、可撤销——于是一个需要相机的页面,拿到的是用户真正看得懂的提示。
参考来源
- Android 开发者——WebChromeClient.onPermissionRequest 与 PermissionRequest:宿主必须实现回调,否则所有 Web 权限请求默认被拒绝 — 第二道门及其静默默认值。
- Android 开发者——WebSettings.setGeolocationEnabled:面向 Android 7.0+ 的 App 仅对安全来源调用定位 — 为何 HTTP 页面静默失去定位。
- CISPA——2025 年研究 Android WebView 的 Web 权限执行隐私风险:无按站点同意,App 可把系统授权分享给任何页面 — 结构性隐私缺口。
- Apple 开发者文档——WKUIDelegate.requestMediaCapturePermissionFor 与 WKPermissionDecision — iOS 双重提示与 delegate 跳过。
- web.dev——Android 上的 Web:WebView 把敏感行为委托给宿主 App — 为何权限处理是 App 侧,而非 Web 侧。