WebView 最佳封装模板(BaseWebActivity + WebViewHelper)

目标:封成一个 "可直接复用的组件",不用每次都写一堆重复代码。

✅ 支持内开页面、不跳系统浏览器

✅ 支持进度条、标题、返回键处理

✅ 支持文件上传 / 拍照 / 相册

✅ 支持相机 / 麦克风权限

✅ 支持离线包 / 本地资源映射(HTTPS)

✅ 支持追加 Header(如 token)

WebViewHelper(统一初始化)

📌 负责 WebViewSettings / Cookie / 深色模式等

🔥 你只要在 Activity 中一行:WebViewHelper.init(webView)

Kotlin 复制代码
// WebViewHelper.kt
package com.xxx.web

import android.os.Build
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature

object WebViewHelper {

    fun init(web: WebView) {
        val s = web.settings

        s.javaScriptEnabled = true
        s.domStorageEnabled = true
        s.databaseEnabled = true
        s.useWideViewPort = true
        s.loadWithOverviewMode = true
        s.cacheMode = WebSettings.LOAD_DEFAULT
        s.mediaPlaybackRequiresUserGesture = false

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            s.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
        }

        // 深色模式跟随系统
        if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
            WebSettingsCompat.setForceDark(s, WebSettingsCompat.FORCE_DARK_AUTO)
        }

        // Cookie 管理
        val cm = CookieManager.getInstance()
        cm.setAcceptCookie(true)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cm.setAcceptThirdPartyCookies(web, true)
        }
    }
}

SafeWebViewClient(跳转、离线包、错误处理)

📌 负责导航行为:内开 http/https,非 http(s) 外跳 APP

支持:

  • URL 内开/外跳
  • intent:// 链接
  • 资源拦截(离线包 / 本地资源)
Kotlin 复制代码
// SafeWebViewClient.kt
package com.xxx.web

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Message
import android.webkit.*
import androidx.webkit.WebViewAssetLoader

class SafeWebViewClient(
    private val context: Context,
    private val whiteHosts: Set<String> = emptySet(),
    private val assetLoader: WebViewAssetLoader? = null,
    private val onMainFrameError: (() -> Unit)? = null,
) : WebViewClient() {

    override fun shouldOverrideUrlLoading(v: WebView, request: WebResourceRequest): Boolean {
        val url = request.url
        val scheme = url.scheme?.lowercase() ?: ""

        // 非 http/https → 外跳系统或 App(如 weixin:// tel:)
        if (scheme !in listOf("http", "https")) {
            return try {
                context.startActivity(Intent(Intent.ACTION_VIEW, url))
                true
            } catch (_: Exception) {
                true
            }
        }

        return false // ✅ WebView 内开
    }

    override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
    }

    override fun onPageFinished(view: WebView, url: String) {
        super.onPageFinished(view, url)
    }

    // 资源请求劫持(离线包、追加 header 等)
    override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
        assetLoader?.let {
            it.shouldInterceptRequest(request.url)?.let { return it }
        }
        return super.shouldInterceptRequest(view, request)
    }

    override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
        if (request.isForMainFrame) {
            onMainFrameError?.invoke()
        }
        super.onReceivedError(view, request, error)
    }
}

ChromeClient(文件选择、进度条、标题、全屏视频)

📌 UI 层功能 → WebChromeClient 专属:

Kotlin 复制代码
// ChromeClient.kt
package com.xxx.web

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.webkit.*

class ChromeClient(
    private val activity: Activity,
    private val onProgressChanged: (Int) -> Unit = {},
    private val onReceivedTitle: (String) -> Unit = {},
) : WebChromeClient() {

    private var fileChooserCallback: ValueCallback<Array<Uri>>? = null

    override fun onProgressChanged(view: WebView, newProgress: Int) {
        onProgressChanged(newProgress)
    }

    override fun onReceivedTitle(view: WebView, title: String?) {
        title?.let(onReceivedTitle)
    }

    // 处理 <input type="file">
    override fun onShowFileChooser(
        webView: WebView,
        callback: ValueCallback<Array<Uri>>,
        params: FileChooserParams
    ): Boolean {
        fileChooserCallback?.onReceiveValue(null)
        fileChooserCallback = callback
        activity.startActivityForResult(params.createIntent(), FILE_CHOOSER_REQ)
        return true
    }

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == FILE_CHOOSER_REQ) {
            val result = WebChromeClient.FileChooserParams.parseResult(resultCode, data)
            fileChooserCallback?.onReceiveValue(result)
            fileChooserCallback = null
        }
    }

    companion object {
        private const val FILE_CHOOSER_REQ = 2000
    }
}

BaseWebActivity(一个 Activity 全搞定)

Kotlin 复制代码
// BaseWebActivity.kt
package com.xxx.web

import android.os.Bundle
import android.view.View
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import com.xxx.yourapp.databinding.ActivityBaseWebBinding

abstract class BaseWebActivity : AppCompatActivity() {

    private lateinit var binding: ActivityBaseWebBinding
    private lateinit var chrome: ChromeClient

    abstract fun provideStartUrl(): String
    open fun provideWhiteHosts() = setOf<String>()  // 支持白名单

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityBaseWebBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val web = binding.webView

        // 1) WebView 设置
        WebViewHelper.init(web)

        // 2) Navigation & 资源拦截(可支持离线包)
        web.webViewClient = SafeWebViewClient(
            context = this,
            whiteHosts = provideWhiteHosts(),
            assetLoader = null,                    // 如有离线包:传 loader
            onMainFrameError = { showError() }
        )

        // 3) UI 层
        chrome = ChromeClient(
            activity = this,
            onProgressChanged = { binding.progress.progress = it },
            onReceivedTitle = { supportActionBar?.title = it }
        )
        web.webChromeClient = chrome

        // ⏯ 启动加载
        web.loadUrl(provideStartUrl())
    }

    private fun showError() {
        binding.errorView.visibility = View.VISIBLE
    }

    override fun onBackPressed() {
        if (binding.webView.canGoBack()) binding.webView.goBack()
        else super.onBackPressed()
    }

    override fun onDestroy() {
        binding.webView.apply {
            stopLoading()
            removeAllViews()
            destroy()
        }
        super.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        chrome.onActivityResult(requestCode, resultCode, data)
        super.onActivityResult(requestCode, resultCode, data)
    }
}

使用方法(只要继承 BaseWebActivity)

Kotlin 复制代码
class MyWebActivity : BaseWebActivity() {

    override fun provideStartUrl() = "https://www.example.com"

    override fun provideWhiteHosts() = setOf(
        "example.com",
        "login.example.com",
    )
}

Done ✅

最终效果:

  • 可直接用于生产环境

  • 支持所有常见 WebView 功能

  • 已封装 最佳实践 + 安全策略 + 离线逻辑拓展点

下一篇:

Android WebView 最佳实践:Fragment 版本 + Token 注入 + 离线包热更新

相关推荐
代码s贝多芬的音符16 小时前
android 两个人脸对比 mlkit
android
darkb1rd18 小时前
五、PHP类型转换与类型安全
android·安全·php
gjxDaniel19 小时前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
csj5019 小时前
安卓基础之《(22)—高级控件(4)碎片Fragment》
android
峥嵘life19 小时前
Android16 【CTS】CtsMediaCodecTestCases等一些列Media测试存在Failed项
android·linux·学习
stevenzqzq20 小时前
Compose 中的状态可变性体系
android·compose
似霰21 小时前
Linux timerfd 的基本使用
android·linux·c++
darling3311 天前
mysql 自动备份以及远程传输脚本,异地备份
android·数据库·mysql·adb
你刷碗1 天前
基于S32K144 CESc生成随机数
android·java·数据库
TheNextByte11 天前
Android上的蓝牙文件传输:跨设备无缝共享
android