4. WebView 页面内链接拦截与外部浏览器分流

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 兜底处理无对应应用的情况。

shouldOverrideUrlLoading 分流决策 用户点击链接 shouldOverrideUrlLoading scheme 是 http / https ? 是 WebView 内加载 return false 否 系统有应用 能处理该 scheme ? 有 Intent 跳转外部 return true 无 Toast 提示无法打开 return true

实现代码

在 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.comhttpsWebView 内加载false
tel:10086tel唤起拨号盘true
mailto:a@b.commailto唤起邮件应用true
weixin://weixin唤起微信(若已安装)true
unknown://xunknownToast 提示无应用true
常见坑:部分页面用 target="_blank" 打开新窗口,此时会触发 WebChromeClient.onCreateWindow 而非 shouldOverrideUrlLoading。若发现新窗口链接未被拦截,需额外处理 onCreateWindow,将新窗口 URL 复用同一套分流逻辑。