Android View 与 Compose 互操作避坑指南
混合开发中常见的陷阱与解决方案
目录
- Compose in View (传统 View 中使用 Compose)
- View in Compose (Compose 中使用传统 View)
- 常见崩溃与解决方案
- 生命周期同步问题
- 性能最佳实践
1. Compose in View (传统 View 中使用 Compose)
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="match_parent" android:layout_height="match_parent" />
class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { MyComposable() } } } }
|
在 Fragment 中使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MyFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { MyComposable() } } } }
|
2. View in Compose (Compose 中使用传统 View)
AndroidView 基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Composable fun WebViewScreen() { AndroidView( factory = { context -> WebView(context).apply { webViewClient = WebViewClient() loadUrl("https://example.com") } }, update = { webView -> webView.loadUrl("https://new-url.com") }, modifier = Modifier.fillMaxSize() ) }
|
常见场景:MapView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Composable fun MapViewScreen() { val mapView = rememberMapViewWithLifecycle() AndroidView( factory = { mapView }, modifier = Modifier.fillMaxSize() ) }
@Composable fun rememberMapViewWithLifecycle(): MapView { val context = LocalContext.current val mapView = remember { MapView(context).apply { onCreate(null) } } DisposableEffect(Unit) { val lifecycle = LocalLifecycleOwner.current.lifecycle lifecycle.addObserver(MapLifecycleObserver(mapView)) onDispose { lifecycle.removeObserver(MapLifecycleObserver(mapView)) } } return mapView }
class MapLifecycleObserver( private val mapView: MapView ) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() = mapView.onResume() @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun onPause() = mapView.onPause() }
|
3. 常见崩溃与解决方案
崩溃 1:Can’t create handler inside thread that has not called Looper.prepare()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| thread { someState.value = "new value" }
thread { Handler(Looper.getMainLooper()).post { someState.value = "new value" } }
viewModelScope.launch(Dispatchers.Main) { someState.value = "new value" }
|
崩溃 2:Composing on non-main thread
1 2 3 4 5 6 7 8 9 10 11 12 13
| thread { setContent { Text("Hello") } }
runOnUiThread { composeView.setContent { Text("Hello") } }
|
崩溃 3:View is not attached to a window manager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Composable fun BadExample(view: View) { AndroidView( factory = { view }, update = { v -> v.visibility = View.VISIBLE } ) }
AndroidView( factory = { view }, update = { v -> if (v.isAttachedToWindow) { v.visibility = View.VISIBLE } } )
|
4. 生命周期同步问题
问题:Compose 生命周期与 View 不一致
flowchart TB
Lifecycle["Activity / Fragment 生命周期"] --> OnCreate["onCreate"]
OnCreate --> OnStart["onStart"]
OnStart --> OnResume["onResume"]
OnResume --> OnPause["onPause"]
OnCreate --> ComposeCreated["Compose Created"]
OnStart --> ComposeStarted["Started"]
OnResume --> ComposeResumed["Resumed"]
OnPause --> ComposePaused["Paused"]
ComposePaused --> Risk["问题:Composition 可能在 onDestroy 后继续执行"]
解决:使用 rememberUpdatedState
1 2 3 4 5 6 7 8 9
| @Composable fun DataObserver(data: SomeData) { val latestData by rememberUpdatedState(data) LaunchedEffect(data) { } }
|
解决:使用 DisposableEffect 管理资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Composable fun SensorView(context: Context) { val sensorManager = context.getSystemService<SensorManager>() var rotation by remember { mutableFloatStateOf(0f) } DisposableEffect(Unit) { val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) val listener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent?) { rotation = event?.values?.get(0) ?: 0f } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} } sensorManager.registerListener( listener, sensor, SensorManager.SENSOR_DELAY_UI ) onDispose { sensorManager.unregisterListener(listener) } } Text("Rotation: $rotation") }
|
5. 性能最佳实践
避免重组时重建 View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Composable fun OptimizedAndroidView() { val view = remember { SomeExpensiveView(context).apply { isFocusable = true } } AndroidView( factory = { view }, update = { v -> } ) }
|
使用 InteropViewModel 桥接状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class InteropViewModel : ViewModel() { private val _text = MutableStateFlow("Hello") val text: StateFlow<String> = _text.asStateFlow() fun updateText(newText: String) { _text.value = newText } }
@Composable fun Screen(viewModel: InteropViewModel = hiltViewModel()) { val text by viewModel.text.collectAsState() AndroidView( factory = { context -> TextView(context).apply { viewModel.lifecycleScope.launch { viewModel.text.collect { value -> text = value } } } } ) }
|
互操作场景对照表
| 场景 |
方案 |
| XML 中嵌入 Compose |
ComposeView |
| Fragment 中嵌入 Compose |
ComposeView |
| Compose 中使用 View |
AndroidView |
| Compose 中使用 WebView |
AndroidView |
| Compose 中使用 MapView |
AndroidView + DisposableEffect |
| View 中使用 Compose |
ComposeView.setContent |
| 需要共享状态 |
ViewModel + StateFlow |
常见错误检查清单
| 错误 |
解决方案 |
| 在后台线程调用 Composable |
切换到主线程 |
| View 已 detach 后更新 |
检查 isAttachedToWindow |
| 内存泄漏 |
使用 DisposableEffect 清理 |
| 重组导致 View 重建 |
使用 remember 缓存 |
| 生命周期不同步 |
使用 LifecycleObserver |
相关文章: