4. WebView 页面内链接拦截与外部浏览器分流
为什么需要链接分流
WebView 默认会把页面内所有可点击链接都当作普通网页继续加载。但真实页面里常混有 tel:、mailto:、sms:、weixin://、alipays:// 等非 HTTP 链接,以及需要交给系统浏览器处理的 http(s) 外链。若不加区分,WebView 会因无法处理这些 scheme 而静默失败,用户点击后毫无反应。
核心拦截点是 WebViewClient.shouldOverrideUrlLoading(WebView, WebResourceRequest)。它会在每次主框架导航前被调用,返回 true 表示"我已处理,WebView 不要加载",返回 false 表示"交给 WebView 继续加载"。
分流决策逻辑
判断顺序很关键:先看 scheme 是否属于 WebView 能渲染的 http/https,再决定是内部加载还是外部跳转。对于非 HTTP scheme,必须用 Intent 尝试唤起系统组件,并用 try/catch 兜底处理无对应应用的情况。
实现代码
在 WebViewClient 中重写拦截方法,按 scheme 分流。注意 API 24 以上使用 WebResourceRequest 重载,低版本需兼容 String 重载。
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
return handleUrl(view, request.url.toString())
}
@Suppress("DEPRECATION")
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
return handleUrl(view, url)
}
private fun handleUrl(view: WebView, url: String): Boolean {
val uri = Uri.parse(url)
val scheme = uri.scheme?.lowercase() ?: return false
// HTTP/HTTPS 留在 WebView 内加载
if (scheme == "http" || scheme == "https") {
return false
}
// 非 HTTP scheme 交给系统处理
return try {
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
addCategory(Intent.CATEGORY_BROWSABLE)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
view.context.startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
Toast.makeText(
view.context,
"未找到可处理该链接的应用",
Toast.LENGTH_SHORT
).show()
true
}
}
}
关键点:非 HTTP scheme 必须返回
true,否则 WebView 会尝试自行加载并失败。HTTP 链接返回 false 让 WebView 正常渲染,避免所有链接都被踢到外部浏览器。
验证方法
准备一个包含多种链接的测试页面,点击后观察行为是否符合预期:
| 链接 | scheme | 预期行为 | 返回值 |
|---|---|---|---|
https://example.com | https | WebView 内加载 | false |
tel:10086 | tel | 唤起拨号盘 | true |
mailto:a@b.com | mailto | 唤起邮件应用 | true |
weixin:// | weixin | 唤起微信(若已安装) | true |
unknown://x | unknown | Toast 提示无应用 | true |
常见坑:部分页面用
target="_blank" 打开新窗口,此时会触发 WebChromeClient.onCreateWindow 而非 shouldOverrideUrlLoading。若发现新窗口链接未被拦截,需额外处理 onCreateWindow,将新窗口 URL 复用同一套分流逻辑。