WebView 最佳封装模板(BaseWebActivity + WebViewHelper)
目标:封成一个 “可直接复用的组件”,不用每次都写一堆重复代码。
✅ 支持内开页面、不跳系统浏览器
✅ 支持进度条、标题、返回键处理
✅ 支持文件上传 / 拍照 / 相册
✅ 支持相机 / 麦克风权限
✅ 支持离线包 / 本地资源映射(HTTPS)
✅ 支持追加 Header(如 token)
WebViewHelper(统一初始化)
📌 负责 WebViewSettings / Cookie / 深色模式等
🔥 你只要在 Activity 中一行:
WebViewHelper.init(webView)
// WebViewHelper.kt
package com.xxx.webimport android.os.Build
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeatureobject WebViewHelper {fun init(web: WebView) {val s = web.settingss.javaScriptEnabled = trues.domStorageEnabled = trues.databaseEnabled = trues.useWideViewPort = trues.loadWithOverviewMode = trues.cacheMode = WebSettings.LOAD_DEFAULTs.mediaPlaybackRequiresUserGesture = falseif (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:// 链接
- 资源拦截(离线包 / 本地资源)
// SafeWebViewClient.kt
package com.xxx.webimport 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.WebViewAssetLoaderclass 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.urlval 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 专属:
// ChromeClient.kt
package com.xxx.webimport 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>>? = nulloverride 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 = callbackactivity.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 全搞定)
// BaseWebActivity.kt
package com.xxx.webimport android.os.Bundle
import android.view.View
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import com.xxx.yourapp.databinding.ActivityBaseWebBindingabstract class BaseWebActivity : AppCompatActivity() {private lateinit var binding: ActivityBaseWebBindingprivate lateinit var chrome: ChromeClientabstract fun provideStartUrl(): Stringopen 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, // 如有离线包:传 loaderonMainFrameError = { 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)
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 注入 + 离线包热更新
