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

相关推荐
WAsbry9 小时前
NFC开发系列-第一篇:NFC开发基础与实战入门
android·程序员
WAsbry9 小时前
NFC开发系列 - 第二篇:NFC企业级架构设计与最佳实践
android·程序员·架构
feibafeibafeiba10 小时前
Android 14 关于imageview设置动态padding值导致图标旋转的问题
android
tangweiguo0305198711 小时前
ProcessLifecycleOwner 完全指南:优雅监听应用前后台状态
android·kotlin
介一安全12 小时前
【Frida Android】基础篇15(完):Frida-Trace 基础应用——JNI 函数 Hook
android·网络安全·ida·逆向·frida
吞掉星星的鲸鱼12 小时前
android studio创建使用开发打包教程
android·ide·android studio
陈老师还在写代码12 小时前
android studio 签名打包教程
android·ide·android studio
csj5012 小时前
android studio设置
android
hifhf12 小时前
Android Studio gradle下载失败报错
android·ide·android studio