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 实现流程
- 添加依赖:确保
build.gradle.kts包含 WebView 基础库(非额外依赖,Android SDK 内置)。 - 声明权限:在
AndroidManifest.xml中添加INTERNET权限。 - 编写 Composable 函数:使用
AndroidView创建 WebView,配置 settings,绑定 JS 接口和 WebViewClient。 - 注入 JS 接口:定义 Kotlin 类,方法加上
@JavascriptInterface注解。 - 管理加载状态:在 WebViewClient 的
onPageStarted、onPageFinished、onReceivedError中更新 Compose 的MutableState。 - 清理资源:在
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 架构关系图
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 构建中启用,避免发布版本暴露安全风险。