功能清单
-
WebView 基线设置(JS/存储/深色模式/Cookie)
-
Fragment 容器:
BaseWebFragment -
Activity 容器:
BaseWebActivity -
导航与资源拦截 :
SafeWebViewClient-
http/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 :
startUrl -
Token 获取 :实现
TokenSync.token()