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 注入 + 离线包热更新

相关推荐
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓4 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1235 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)5 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白21 小时前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android