You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 11:14:53