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

相关推荐
my_power52021 小时前
检出git项目到android studio该如何配置
android·git·android studio
三少爷的鞋1 天前
Repository 方法设计:suspend 与 Flow 的决选择指南(以朋友圈为例)
android
阿里云云原生1 天前
Android App 崩溃排查指南:阿里云 RUM 如何让你快速从告警到定位根因?
android·java
cmdch20171 天前
手持机安卓新增推送按钮功能
android
攻城狮20151 天前
【rk3528/rk3518 android14 kernel-6.10 emcp sdk】
android
何妨呀~1 天前
mysql 8服务器实验
android·mysql·adb
QuantumLeap丶1 天前
《Flutter全栈开发实战指南:从零到高级》- 25 -性能优化
android·flutter·ios
木易 士心1 天前
MVC、MVP 与 MVVM:Android 架构演进之路
android·架构·mvc
百锦再1 天前
国产数据库的平替亮点——关系型数据库架构适配
android·java·前端·数据库·sql·算法·数据库架构
走在路上的菜鸟1 天前
Android学Dart学习笔记第十三节 注解
android·笔记·学习·flutter