2. Compose中集成WebView:AndroidView包裹WebView、JavaScript接口通信、加载状态管理

2.1 原理概述

Android Compose 通过 AndroidView 工厂函数将传统 View 体系中的控件嵌入 Composable 树。WebView 作为一个功能完整的浏览器内核 View,同样通过 AndroidView 进行托管。核心原理包含三层联动:

  • 视图层:AndroidView 创建并管理 WebView 实例,在 composition 生命周期内维护它的引用。
  • 通信层:通过 addJavascriptInterface 将 Kotlin 对象注入 WebView 的 JavaScript 环境,实现双向数据交换。
  • 状态层:借助 WebViewClient 和 WebChromeClient 回调获取加载进度、错误、页面完成等事件,驱动 Compose 状态更新。

2.2 实现流程

  1. 添加依赖:确保 build.gradle.kts 包含 WebView 基础库(非额外依赖,Android SDK 内置)。
  2. 声明权限:在 AndroidManifest.xml 中添加 INTERNET 权限。
  3. 编写 Composable 函数:使用 AndroidView 创建 WebView,配置 settings,绑定 JS 接口和 WebViewClient。
  4. 注入 JS 接口:定义 Kotlin 类,方法加上 @JavascriptInterface 注解。
  5. 管理加载状态:在 WebViewClient 的 onPageStarted、onPageFinished、onReceivedError 中更新 Compose 的 MutableState。
  6. 清理资源:在 DisposableEffect 或 onDispose 中调用 webView.destroy(),避免内存泄漏。

2.3 核心代码示例

@Composable
fun WebViewComponent(
    url: String,
    jsInterface: Any,
    jsInterfaceName: String = "Android"
) {
    var isLoading by remember { mutableStateOf(true) }
    var errorMessage by remember { mutableStateOf<String?>(null) }

    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                settings.domStorageEnabled = true
                addJavascriptInterface(jsInterface, jsInterfaceName)

                webViewClient = object : WebViewClient() {
                    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                        isLoading = true
                        errorMessage = null
                    }

                    override fun onPageFinished(view: WebView?, url: String?) {
                        isLoading = false
                    }

                    override fun onReceivedError(
                        view: WebView?,
                        request: WebResourceRequest?,
                        error: WebResourceError?
                    ) {
                        isLoading = false
                        errorMessage = "加载失败: ${error?.description}"
                    }
                }

                loadUrl(url)
            }
        },
        modifier = Modifier.fillMaxSize()
    )

    // 加载状态指示器
    if (isLoading) {
        CircularProgressIndicator(
            modifier = Modifier.size(48.dp).align(Alignment.Center)
        )
    }

    // 错误提示
    errorMessage?.let { message ->
        Text(
            text = message,
            color = MaterialTheme.colorScheme.error,
            modifier = Modifier.padding(16.dp)
        )
    }
}

2.4 JavaScript 接口通信

双向通信通过 @JavascriptInterface 注解的方法实现。Kotlin 端暴露的方法可以在 JS 中直接调用;JS 端的数据通过 Kotlin 方法的参数传递。

class WebAppInterface(private val context: Context) {
    @JavascriptInterface
    fun showToast(text: String) {
        Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
    }

    @JavascriptInterface
    fun getData(): String {
        return "{ \"key\": \"value\" }"
    }
}

JS 调用示例:

<script>
    Android.showToast("来自JS的消息");
    var data = Android.getData();
    console.log(data);
</script>
安全注意:@JavascriptInterface 仅在 API 17+ 上支持,且必须使用 @JavascriptInterface 注解而非旧版 @SuppressLint("SetJavaScriptEnabled")。低版本设备上不要开启 JavaScript,或使用其他通信方式。

2.5 加载状态管理

回调触发时机用途
onPageStarted页面开始加载显示加载指示器,重置错误状态
onPageFinished页面加载完成隐藏加载指示器,触发后续 JS 调用
onReceivedError加载失败显示错误信息,提供重试按钮
onProgressChanged加载进度变化显示精确进度条(需要继承 WebChromeClient)

2.6 调试方法

  • Chrome DevTools 远程调试:在连接 USB 的 Android 设备上,打开 chrome://inspect,选择应用的 WebView 进程,即可查看 Console、Network、Elements 面板。
  • 日志输出:在 WebViewClient 回调中加入 Log.d("WebViewDebug", ...),通过 Logcat 过滤查看。
  • JS 调试:在 JS 代码中使用 console.log,DevTools 中可直接查看。
// 启用调试模式(仅调试构建)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WebView.setWebContentsDebuggingEnabled(true)
}

2.7 常见错误与解决方案

错误现象成因解决方法
JS 接口无法调用未添加 @JavascriptInterface 注解在方法上添加 @JavascriptInterface
WebView 内存泄漏未在 onDispose 中调用 destroy()使用 DisposableEffect 或在 onDispose 中清理
加载空白页缺少 INTERNET 权限在 AndroidManifest.xml 添加 <uses-permission android:name="android.permission.INTERNET" />
低版本设备崩溃使用了高版本 API 但未做兼容检查 API 版本,为 <17 设备禁用 JS 或提供降级方案

2.8 性能与稳定性优化

  • 复用 WebView 实例:在列表或频繁切换页面时,使用 WebViewPool 或保持单例,避免重复创建开销。
  • 硬件加速:确保 Activity 的 android:hardwareAccelerated="true",WebView 默认使用 GPU 渲染。
  • 缓存策略:设置 WebSettings.setCacheMode(LOAD_CACHE_ELSE_NETWORK) 减少网络请求。
  • 处理页面崩溃:在 onReceivedError 中补充 onReceivedHttpError 回调,捕获 HTTP 状态码错误。
  • 内存优化:在 onDetachedFromWindow 中主动释放 WebView 占用的 native 内存,避免被 retain。

2.9 架构关系图

Compose WebView 集成架构 Compose Composable @Composable fun WebViewScreen() AndroidView { factory } WebView 实例 @JavascriptInterface WebViewClient 回调 MutableState 关键数据流 Composable 通过 AndroidView 创建 WebView → WebView 加载网页并调用 JS 接口 WebViewClient 回调更新 State → Compose 重组 UI(加载指示器/错误提示)

2.10 部署要点

  • AndroidManifest.xml:必须声明 <uses-permission android:name="android.permission.INTERNET" />;如果目标 API 29+,默认允许明文流量,无需额外配置。
  • WebView 版本:通过 Google Play 自动更新,建议在 AndroidManifest 中设置 android:usesCleartextTraffic="true" 以支持 HTTP(仅调试阶段)。
  • ProGuard 规则:如果启用混淆,需保留 JS 接口类名和方法名:-keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; }
调试利器:使用 Chrome 远程调试时,确保 WebView 的 setWebContentsDebuggingEnabled(true) 仅在 debug 构建中启用,避免发布版本暴露安全风险。