webkitx(Android WebView 最佳实践库)--> 上

功能清单

  • WebView 基线设置(JS/存储/深色模式/Cookie)

  • Fragment 容器:BaseWebFragment

  • Activity 容器:BaseWebActivity

  • 导航与资源拦截SafeWebViewClient

    • http/https 内开、非 http(s) 外跳

    • 仅 API 路由注入请求头(Authorization)

    • 离线包 路径映射(WebViewAssetLoader

  • UI/权限ChromeClientFragmentChromeClientActivity

    • 进度条、标题、<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、调试

  • TokenSyncCookie 同步 (推荐),附带 token() 占位

  • OfflineH5Manager:离线包下载/校验/解压/切换/回滚;暴露 currentDir()hasBundle()

  • SafeWebViewClient:导航/拦截/SSL/错误;仅 API 路由注入请求头;离线包优先

  • ChromeClientFragment / ChromeClientActivity:进度/标题/文件选择(使用 ActivityResult)

  • DownloadHelper:系统 DownloadManager 下载 + 完成回调

  • BaseWebFragment:组合(Settings + Client + Chrome + Token + Offline)

  • BaseWebActivity:组合(同上)+ DownloadManager 集成

你只需要替换:白名单域名入口 URLTokenSync.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) 离线包更新(热更流程)

  1. 服务端提供 manifestversionzipUrlsha256

  2. App 下载 zip → offline.installFromZip(zip, version, sha256)

  3. 成功后 OfflineH5Manager.switchTo(version)(封装里已做)

  4. WebView 入口统一:

    • 在线:https://yourdomain.com/index.html

    • 离线:https://appassets.androidplatform.net/dynamic/index.html

  5. 如需回滚:offline.rollback()

BaseWebFragment/Activity 已根据 offline.hasBundle() 自动选择入口。


7) Token 注入策略(优先级)

  1. Cookie 同步(推荐)
    TokenSync.syncCookie(web, listOf("yourdomain.com","api.yourdomain.com"))

    后端从 Cookie 读取 AUTH_TOKEN

  2. 请求头注入(仅 API 路由)
    SafeWebViewClient.extraHeadersProvider = { mapOf("Authorization" to "Bearer ${TokenSync.token()}") }

    并通过 isApiPath(uri) 严格限制在 /api/**不要给静态资源加头

  3. 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 处

  1. 域名yourdomain.comwhiteHosts & cookieDomains

  2. 入口 URLstartUrl

  3. Token 获取 :实现 TokenSync.token()

相关推荐
阿巴斯甜7 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker7 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95278 小时前
Andorid Google 登录接入文档
android
黄林晴9 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android