Android WebView 最佳实践:Fragment 版本 + Token 注入 + 离线包热更新
本文从实际业务需求出发,将 WebView 封装成 可复用组件,解决:
页面跳转不内开
Token 不同步导致 H5 登录状态失效
离线包更新难维护、H5 缓存失效 / 白屏
文件选择、全屏视频、相机权限等问题
阅读后,你可以快速构建 稳定、拓展性强、可热更新的 WebView 体系。
1. 为什么 WebView 需要封装?
知乎、支付宝、京东等 App 都采用 Hybrid 模式:原生承载 Web 内容。
如果不开箱即用,常见问题如下:
| 问题 | 原因 |
|---|---|
| 链接点开跳系统浏览器 | 未设置 WebViewClient |
| 上传图片不生效 | 未处理 WebChromeClient.onShowFileChooser |
| H5 登录态丢失 | Token 未同步 Cookie |
| 页面白屏 | 缓存混乱 / 证书错误 / 离线包资源找不到 |
所以:不能裸放 WebView,用框架化封装提升稳定性。
2. 设计目标(Go)
目标:业务永远只关注 URL + Token,不关心 WebView 内部细节
| 模块 | 内容 |
|---|---|
| BaseWebFragment | 可复用 Web 容器 |
| WebViewHelper | 统一 WebSettings / Cookie / Debug |
| SafeWebViewClient | 处理 路由 / 内开 / Header 注入 |
| ChromeClientFragment | 处理 UI 权限:进度 / 上传 / 全屏视频 |
| 离线包 OfflineH5Manager | 支持热更新 / 完整性校验 / 回滚 |
架构:
WebView (UI)
│
├── WebViewHelper ← 设置能力(JS / Cookie / DOMStorage)
├── SafeWebViewClient ← 控制加载 / 资源拦截 / Token 注入
├── ChromeClientFragment ← UI & 权限处理(上传 / 全屏)
└── OfflineH5Manager ← 离线包 / 热更新 / 回滚
3. Fragment 封装(ViewPager/BottomTab 推荐使用)
✅ fragment_web.xml
<FrameLayoutandroid:id="@+id/web_root"android:layout_width="match_parent"android:layout_height="match_parent"><WebViewandroid:id="@+id/web"android:layout_width="match_parent"android:layout_height="match_parent"/><ProgressBarandroid:id="@+id/progress"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="3dp"/>
</FrameLayout>
4. Token 注入(3 种策略,按优先级)
✅ 写 Cookie(推荐,用于登录态统一)
object TokenSync {fun token() = AccountManager.token()fun syncCookie(webView: WebView, domains: List<String>) {val cm = CookieManager.getInstance()val t = token()domains.forEach { domain ->cm.setCookie("https://$domain", "AUTH_TOKEN=$t;Path=/")}}
}
优势:与 Web 端登录一致,不破坏缓存,不污染静态资源。
✅ Header 注入(只拦 API)
在 SafeWebViewClient.shouldInterceptRequest() 中:
private val extraHeadersProvider: (() -> Map<String, String>) = {mapOf("Authorization" to "Bearer ${TokenSync.token()}")
}
⚠️ 注意:只给 API 路径 加,不给 静态资源 加。
✅ JS 注入(备用方案)
web.evaluateJavascript("window.__TOKEN__='${TokenSync.token()}';")
5. 离线包热更新(支持增量、校验、回滚)
解决:弱网/无网时仍能正常使用 Web 内容。
离线包目录结构:
/files/h5/bundles/v1001/v1002/
current.json ← 当前版本
backup.json ← 上一版本 (回滚使用)
✅ OfflineH5Manager:下载/校验/切换/回滚
class OfflineH5Manager(private val ctx: Context) {private val root = File(ctx.filesDir, "h5")private val bundles = File(root, "bundles")private val currentMeta = File(root, "current.json")private val backupMeta = File(root, "backup.json")fun currentDir(): File = File(bundles, read(currentMeta) ?: "none")fun hasBundle() = currentDir().exists()fun install(zip: File, version: String, sha256: String): Boolean {if (sha256(zip) != sha256) return falseval dest = File(bundles, version)dest.deleteRecursively(); dest.mkdirs()unzip(zip, dest)switch(version)return true}private fun switch(version: String) {write(backupMeta, read(currentMeta))write(currentMeta, version)}fun rollback() = write(currentMeta, read(backupMeta))
}
✅ WebView 内部资源映射(AssetLoader)
val assetLoader = WebViewAssetLoader.Builder().addPathHandler("/dynamic/",WebViewAssetLoader.InternalStoragePathHandler(requireContext(), offline.currentDir())).build()
离线地址:
https://appassets.androidplatform.net/dynamic/index.html
这样 WebView 访问离线资源也走 HTTPS,不会违反浏览器安全策略。
6. BaseWebFragment(整合 + 可复用)
class BaseWebFragment : Fragment(R.layout.fragment_web) {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {val web = view.findViewById<WebView>(R.id.web)val progress = view.findViewById<ProgressBar>(R.id.progress)WebViewHelper.init(web)val assetLoader = WebViewAssetLoader.Builder().addPathHandler("/dynamic/", WebViewAssetLoader.InternalStoragePathHandler(requireContext(), offline.currentDir())).build()web.webViewClient = SafeWebViewClient(context = requireContext(),whiteHosts = setOf("yourdomain.com"),assetLoader = assetLoader,extraHeadersProvider = { mapOf("Authorization" to "Bearer ${TokenSync.token()}") })web.webChromeClient = ChromeClientFragment(owner = this,onProgress = { p -> progress.isVisible = p < 100; progress.progress = p })TokenSync.syncCookie(web, listOf("yourdomain.com"))web.loadUrl(startUrl())}
}
7. 使用方式(业务页面只写 URL)
业务层无需关心内部逻辑:
✔️ 内开
✔️ Token
✔️ 离线包
✔️ 上传/权限
✔️ 静默更新
8. 效果
| 功能 | 是否支持 |
|---|---|
| 内开 + 外跳处理 | ✅ |
| Token 注入(Cookie / Header / JS) | ✅ |
| 离线包热更新(支持校验 & 回滚) | ✅ |
| 文件上传 / 相机 / 麦克风权限 | ✅ |
| Fragment / ViewPager / Tab | ✅ |
| 支持灰度发布 + 热更新 | ✅ |
✅ 总结(Why it works)
WebView 不要裸用,封装成组件后,业务只传 URL + Token。
最终结果:
WebView 易维护、可扩展、可热更新
下一篇:
webkitx(Android WebView 最佳实践库)
