使用NavHost组件时SurfaceCreated回调未触发的问题排查
问题
在Jetpack Compose应用中使用SurfaceView展示OpenGL/Vulkan内容时,遇到异常:SurfaceView的surfaceCreated回调在某一应用中能正常触发,但在另一个使用NavHost组件的应用里从未被调用。已通过设置背景色确认MapView尺寸正常,调整尺寸后问题仍未解决。
相关代码
VkSurfaceView实现(Java)
public final class VkSurfaceView extends SurfaceView { public VkSurfaceView(Context pContext, long pViewCPtr) { super(pContext, pViewCPtr); setWillNotDraw(false); SurfaceHolder sh = getHolder(); Log.i(TAG, "getHolder"+ sh.toString()); getHolder().addCallback(this); } /** * This method is part of the SurfaceHolder.Callback interface. */ @Override public void surfaceCreated(final SurfaceHolder pHolder) { Log.i(TAG, "Surface will be created"); } }
Compose封装的MapView(Kotlin)
@Composable fun MapView( modifier: Modifier = Modifier, onMapViewAttached: (mapView : VMEMapView) -> Unit = { } ) { AndroidView( modifier = modifier.fillMaxSize() .background(Color.Green) .clipToBounds(), // 占据Compose UI树中的最大尺寸 factory = { context -> VMEMapView(context, attributeSet = null).also { onMapViewAttached(it) } }, update = { view -> } ) }
正常工作的MapView使用方式
@Composable fun ContentView( onMapViewCreated: (VMEMapView) -> Unit = {}, loadMapDataRequested: () -> Unit = {}, ) { MapView( onMapViewAttached = { mapView -> onMapViewCreated(mapView) } ) Box( modifier = Modifier ) { } }
出现问题的NavHost嵌套代码
@Composable fun MapScreen(onMapViewAttached: (mapView : VMEMapView) -> Unit = { }) { MapView( onMapViewAttached = { mapView -> onMapViewAttached(mapView) } ) } @Composable private fun ScaffoldContent( navController: NavHostController, scope: CoroutineScope, drawerState: DrawerState, bottomBar: @Composable () -> Unit ) { Scaffold( bottomBar = { bottomBar() }, floatingActionButton = { ExtendedFloatingActionButton( text = { }, icon = { Icon(Icons.Filled.Add, contentDescription = "") }, onClick = { scope.launch { drawerState.apply { if (isClosed) open() else close() } } } ) } ) { contentPadding -> // 页面内容 Box(modifier = Modifier.padding(contentPadding)) { NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen() } composable("map") { Box( modifier = Modifier .background(Color.Yellow) .padding(32.dp) ) { MapScreen( onMapViewAttached = { mapView -> viewModel.loadMapView(this@MainActivity, mapView) } ) } } composable("search") { SearchScreen() } composable("settings") { SettingsScreen() } } } } }
排查与解决方案
以下是几个可能导致surfaceCreated不触发的原因及对应解决方式:
- NavHost页面可见性与Surface预创建问题
NavHost默认会预加载相邻页面,但非初始路由的MapScreen可能未真正获得焦点或完成布局。可以给MapView添加布局监听,确认View完成布局后强制触发Surface创建:
@Composable fun MapView( modifier: Modifier = Modifier, onMapViewAttached: (mapView : VMEMapView) -> Unit = { } ) { AndroidView( modifier = modifier.fillMaxSize() .background(Color.Green) .clipToBounds() .onGloballyPositioned { coordinates -> // 获取VMEMapView内部的VkSurfaceView实例 val vkSurfaceView = ... // 强制设置尺寸并触发Surface创建 vkSurfaceView.holder.setFixedSize(coordinates.size.width, coordinates.size.height) vkSurfaceView.holder.lockCanvas()?.let { vkSurfaceView.holder.unlockCanvasAndPost(it) } }, factory = { context -> VMEMapView(context, attributeSet = null).also { onMapViewAttached(it) } }, update = { view -> } ) }
- AndroidView生命周期同步问题
Compose中AndroidView的生命周期依赖重组状态,NavHost切换页面时MapScreen可能被暂存或隐藏,导致Surface销毁后未重建。可以在update回调中监听View可见性,手动补全回调逻辑:
update = { view -> if (view.isVisible) { view.post { val vkSurfaceView = ... // 获取内部VkSurfaceView实例 val holder = vkSurfaceView.holder // 重新注册回调(防止原有回调丢失) holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { vkSurfaceView.surfaceCreated(holder) } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} override fun surfaceDestroyed(holder: SurfaceHolder) {} }) // 明确指定Surface类型为GPU加速 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU) } } }
- 嵌套布局的尺寸叠加问题
Scaffold的contentPadding与外层Box的padding(32.dp)可能叠加,导致SurfaceView实际可用尺寸异常。可以调整布局结构,将padding直接传递给MapView:
// 修改MapScreen,接收modifier参数 @Composable fun MapScreen( modifier: Modifier = Modifier, onMapViewAttached: (mapView : VMEMapView) -> Unit = { } ) { MapView( modifier = modifier, onMapViewAttached = { mapView -> onMapViewAttached(mapView) } ) } // 在NavHost中直接设置padding composable("map") { MapScreen( modifier = Modifier.padding(contentPadding) .background(Color.Yellow) .padding(32.dp), onMapViewAttached = { mapView -> viewModel.loadMapView(this@MainActivity, mapView) } ) }
- SurfaceHolder类型未明确设置
在VkSurfaceView构造函数中,明确指定SurfaceHolder的类型为GPU加速:
public VkSurfaceView(Context pContext, long pViewCPtr) { super(pContext, pViewCPtr); setWillNotDraw(false); SurfaceHolder sh = getHolder(); sh.setType(SurfaceHolder.SURFACE_TYPE_GPU); // 强制GPU类型 Log.i(TAG, "getHolder"+ sh.toString()); getHolder().addCallback(this); }
内容的提问来源于stack exchange,提问作者SuperCed
相关产品推荐
相关产品推荐

