webkitx(Android WebView 最佳实践库)
功能清单
WebView 基线设置(JS/存储/深色模式/Cookie)
Fragment 容器:
BaseWebFragmentActivity 容器:
BaseWebActivity导航与资源拦截:
SafeWebViewClienthttp/https 内开、非 http(s) 外跳
仅 API 路由注入请求头(Authorization)
离线包路径映射(
WebViewAssetLoader)
UI/权限:
ChromeClientFragment、ChromeClientActivity进度条、标题、
<input type=file>、相机/麦克风权限钩子
登录态:
TokenSync(推荐:Cookie 同步)离线包热更新:
OfflineH5Manager(下载/校验/解压/切换/回滚)下载:
DownloadHelper+ 系统DownloadManager
1) 目录结构
webkitx/ ├─ build.gradle ├─ proguard-rules.pro ├─ src/main/ │ ├─ AndroidManifest.xml │ ├─ java/com/yourorg/webkitx/ │ │ ├─ WebViewHelper.kt │ │ ├─ TokenSync.kt │ │ ├─ OfflineH5Manager.kt │ │ ├─ SafeWebViewClient.kt │ │ ├─ ChromeClientFragment.kt │ │ ├─ ChromeClientActivity.kt │ │ ├─ BaseWebFragment.kt │ │ ├─ BaseWebActivity.kt │ │ └─ WebKitx.kt // 可选:全局开关 │ ├─ res/layout/fragment_webkitx.xml │ ├─ res/layout/activity_base_webkitx.xml │ ├─ res/xml/network_security_config.xml │ └─ assets/error/net_error.html
2) Gradle & 依赖
根 settings.gradle
include ':app', ':webkitx'
:webkitx/build.gradle
plugins { id 'com.android.library'; id 'org.jetbrains.kotlin.android' } android { namespace 'com.yourorg.webkitx' compileSdk 34 defaultConfig { minSdk 21 consumerProguardFiles 'proguard-rules.pro' } buildFeatures { viewBinding true } } dependencies { implementation "androidx.appcompat:appcompat:1.7.0" implementation "androidx.core:core-ktx:1.13.1" implementation "androidx.activity:activity-ktx:1.9.2" implementation "androidx.fragment:fragment-ktx:1.8.3" implementation "androidx.webkit:webkit:1.11.0" }
在
:app中加入implementation project(':webkitx')
3) 必要资源/配置(一键复制)
AndroidManifest.xml
<manifest> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <application android:networkSecurityConfig="@xml/network_security_config" /> </manifest>
res/xml/network_security_config.xml(如需 http 调试域)
<network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">dev.yourdomain.com</domain> </domain-config> </network-security-config>
res/layout/fragment_webkitx.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/web_root" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web" android:layout_width="match_parent" android:layout_height="match_parent"/> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="3dp" android:max="100" android:visibility="gone"/> <FrameLayout android:id="@+id/error_view" android:visibility="gone" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
res/layout/activity_base_webkitx.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web" android:layout_width="match_parent" android:layout_height="match_parent"/> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="3dp" android:max="100" android:visibility="gone"/> </FrameLayout>
assets/error/net_error.html
<!doctype html><meta charset="utf-8"><title>网络异常</title> <style>body{font-family:sans-serif;padding:24px}</style> <h2>网络开小差了</h2><p>请检查网络后重试</p>
proguard-rules.pro
-keep class android.webkit.** { *; } -keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; } -dontwarn org.chromium.**
4) 关键类与职责(已实现)
WebViewHelper:初始化WebSettings、深色模式、Cookie、调试TokenSync:Cookie 同步(推荐),附带token()占位OfflineH5Manager:离线包下载/校验/解压/切换/回滚;暴露currentDir()与hasBundle()SafeWebViewClient:导航/拦截/SSL/错误;仅 API 路由注入请求头;离线包优先ChromeClientFragment/ChromeClientActivity:进度/标题/文件选择(使用 ActivityResult)DownloadHelper:系统DownloadManager下载 + 完成回调BaseWebFragment:组合(Settings + Client + Chrome + Token + Offline)BaseWebActivity:组合(同上)+DownloadManager集成
你只需要替换:白名单域名、入口 URL、
TokenSync.token()获取方式。
5) 业务侧如何使用
A) Fragment 版(适合 ViewPager/Tab)
class WebHostActivity : AppCompatActivity(R.layout.activity_host) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val frag = BaseWebFragment( startUrl = "https://yourdomain.com/index.html", whiteHosts = setOf("yourdomain.com","api.yourdomain.com","cdn.yourdomain.com"), domainsForCookie = listOf("yourdomain.com","api.yourdomain.com"), offline = OfflineH5Manager(this), enableDebug = BuildConfig.DEBUG ) supportFragmentManager.beginTransaction() .replace(R.id.container, frag) .commit() } override fun onBackPressed() { val f = supportFragmentManager.findFragmentById(R.id.container) as? BaseWebFragment if (f?.canGoBack() == true) f.goBack() else super.onBackPressed() } }
B) Activity 版(简单直接)
class MyWebActivity : BaseWebActivity() { override fun startUrl() = "https://yourdomain.com/index.html" override fun whiteHosts() = setOf("yourdomain.com","api.yourdomain.com","cdn.yourdomain.com") override fun cookieDomains() = listOf("yourdomain.com","api.yourdomain.com") override fun offlineManager() = OfflineH5Manager(this) // 若启用离线包 }
6) 离线包更新(热更流程)
服务端提供 manifest(
version、zipUrl、sha256)App 下载 zip →
offline.installFromZip(zip, version, sha256)成功后
OfflineH5Manager.switchTo(version)(封装里已做)WebView 入口统一:
在线:
https://yourdomain.com/index.html离线:
https://appassets.androidplatform.net/dynamic/index.html
如需回滚:
offline.rollback()
BaseWebFragment/Activity 已根据
offline.hasBundle()自动选择入口。
7) Token 注入策略(优先级)
Cookie 同步(推荐):
TokenSync.syncCookie(web, listOf("yourdomain.com","api.yourdomain.com"))
后端从 Cookie 读取AUTH_TOKEN。请求头注入(仅 API 路由):
SafeWebViewClient.extraHeadersProvider = { mapOf("Authorization" to "Bearer ${TokenSync.token()}") }
并通过isApiPath(uri)严格限制在/api/**,不要给静态资源加头。JS 注入(备选):
web.evaluateJavascript("window.__TOKEN__='${TokenSync.token()}';", null)
8) 下载(DownloadManager)
WebView 内部下载链接 → 交给系统
DownloadManager模块中已在
BaseWebActivity里注册:setDownloadListener{ url, ua, cd, mime, _ -> DownloadHelper.enqueue(...) }完成广播
DownloadHelper.registerCompletionReceiver(...)
Android Q+ 下载到公有 Downloads 目录无需手动存储权限;如需通知权限(Android 13+),在 App 动态申请
POST_NOTIFICATIONS。
9) 扩展与开关
域名白名单:
whiteHosts()Cookie 同步域:
cookieDomains()仅 API 路由:在
SafeWebViewClient.isApiPath中自定义调试:
WebViewHelper.init(web, enableDebug = BuildConfig.DEBUG)错误页:
error_view自定义 UI;或加载assets/error/net_error.html
10) 上线安全核查清单
onReceivedSslError()一律 cancel(禁止放行自签证书)仅对 API 路由加请求头;禁止给静态资源加头
Scheme 外跳(
weixin://,alipays://,tel:)留在白名单内显式处理仅在可信域注入
@JavascriptInterface(如后续要加 Bridge)生产关闭不必要的
mixedContent;能 https 就 https
11) 最少需要你改的 3 处
域名:
yourdomain.com(whiteHosts&cookieDomains)入口 URL:
startUrlToken 获取:实现
TokenSync.token()
