You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Jetpack Compose中实现SurfaceView的等效组件,支持多线程定时绘制与更新?

如何在Jetpack Compose中实现等效于SurfaceView的、基于协程定时渲染更新的组件?

我希望在Jetpack Compose中实现一个与SurfaceView功能等效的组件,该组件能够在独立线程中按特定时间间隔进行画面的绘制与更新。目前我通过Kotlin协程实现了一个基于SurfaceView的抽象类,代码如下:

abstract class CoroutineSurfaceView : SurfaceView, SurfaceHolder.Callback, DefaultLifecycleObserver {
    // 处理线程中捕获的异常
    private val handler = CoroutineExceptionHandler { coroutineContext, throwable -> }
    internal lateinit var canvas: Canvas
    var framePerSecond = 60
    private var renderTime = 100L / framePerSecond
    private val coroutineScope = CoroutineScope(handler + SupervisorJob() + Dispatchers.Default)
    private lateinit var job: Job
    private lateinit var surfaceHolder: SurfaceHolder

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context)
    }

    constructor(context: Context) : super(context) {
        init(context)
    }

    open fun init(context: Context) {
        surfaceHolder = this.holder
        surfaceHolder.addCallback(this)
        setZOrderOnTop(true)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
    }

    private fun startCoroutineRendering() {
        job = coroutineScope.launch {
            while (isActive) {
                if (!surfaceHolder.surface.isValid) {
                    continue
                }
                canvas = surfaceHolder.lockCanvas()
                update()
                render(canvas)
                holder.unlockCanvasAndPost(canvas)
                delay(renderTime)
            }
        }
    }

    internal abstract fun update()
    internal abstract fun render(canvas: Canvas)

    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        startCoroutineRendering()
    }

    override fun onPause(owner: LifecycleOwner) {
        super.onPause(owner)
        coroutineScope.launch(Dispatchers.Main.immediate) {
            job.cancelAndJoin()
        }
    }
}

我来帮你梳理两种在Jetpack Compose中实现这类组件的方案:一种是基于你现有代码快速适配,另一种是更贴合Compose函数式风格的重构方案。

方案一:用AndroidView封装现有CoroutineSurfaceView

Jetpack Compose提供了AndroidView组件,可以无缝嵌入原生Android View。我们可以直接复用你已经写好的CoroutineSurfaceView,只需要做少量适配:

1. 实现具体的渲染子类

首先创建一个继承自CoroutineSurfaceView的子类,实现抽象的update()render()方法,这里以一个简单的移动圆形为例:

class CustomRenderView(context: Context) : CoroutineSurfaceView(context) {
    private var xPos = 0f
    private var yPos = 0f
    private var dx = 3f
    private var dy = 3f

    override fun update() {
        // 帧更新逻辑:计算坐标偏移
        xPos += dx
        yPos += dy
    }

    override fun render(canvas: Canvas) {
        // 绘制逻辑:先清屏再画圆
        canvas.drawColor(Color.WHITE)
        // 边界碰撞检测
        if (xPos > width || xPos < 0) dx *= -1
        if (yPos > height || yPos < 0) dy *= -1
        canvas.drawCircle(xPos, yPos, 60f, Paint().apply {
            color = Color.parseColor("#2196F3")
            isAntiAlias = true
        })
    }
}

2. 封装为Compose可组合函数

AndroidView把原生View包装成Compose组件,同时绑定Compose的生命周期,确保渲染协程能正确启停:

@Composable
fun CoroutineSurfaceRender(
    modifier: Modifier = Modifier,
    fps: Int = 60
) {
    val lifecycleOwner = LocalLifecycleOwner.current
    AndroidView(
        modifier = modifier,
        factory = { context ->
            CustomRenderView(context).apply {
                framePerSecond = fps
                // 将View绑定到Compose的生命周期
                lifecycleOwner.lifecycle.addObserver(this)
            }
        },
        update = { view ->
            // 当fps变化时更新配置
            if (view.framePerSecond != fps) {
                view.framePerSecond = fps
                view.renderTime = 100L / fps
            }
        }
    )
}

3. 在Compose布局中使用

现在你可以像使用普通Compose组件一样调用它:

@Composable
fun MainContent() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Compose集成SurfaceView渲染示例",
            modifier = Modifier.padding(16.dp),
            fontSize = 18.sp
        )
        CoroutineSurfaceRender(
            modifier = Modifier.fillMaxSize().weight(1f),
            fps = 30
        )
    }
}

方案二:纯Compose风格的重构实现

如果你希望完全用Compose的方式管理协程和状态,可以直接创建SurfaceView并在Compose协程作用域中处理渲染逻辑,避免继承原生抽象类:

@Composable
fun ComposeSurfaceRenderer(
    modifier: Modifier = Modifier,
    fps: Int = 60,
    onUpdate: () -> Unit,
    onRender: (Canvas, Int, Int) -> Unit
) {
    val renderInterval = remember(fps) { 100L / fps }
    val coroutineScope = rememberCoroutineScope()
    var surfaceHolder by remember { mutableStateOf<SurfaceHolder?>(null) }
    var renderJob by remember { mutableStateOf<Job?>(null) }
    val lifecycleOwner = LocalLifecycleOwner.current

    // 嵌入原生SurfaceView
    AndroidView(
        modifier = modifier,
        factory = { context ->
            SurfaceView(context).apply {
                setZOrderOnTop(true)
                holder.addCallback(object : SurfaceHolder.Callback {
                    override fun surfaceCreated(holder: SurfaceHolder) {
                        surfaceHolder = holder
                    }

                    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
                        surfaceHolder = holder
                    }

                    override fun surfaceDestroyed(holder: SurfaceHolder) {
                        surfaceHolder = null
                    }
                })
            }
        }
    )

    // 绑定生命周期,管理渲染协程
    DisposableEffect(lifecycleOwner, surfaceHolder) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                super.onResume(owner)
                surfaceHolder?.let { holder ->
                    renderJob = coroutineScope.launch(
                        Dispatchers.Default + CoroutineExceptionHandler { _, throwable ->
                            // 捕获渲染线程异常,避免崩溃
                            Log.e("ComposeSurface", "Render failure", throwable)
                        }
                    ) {
                        while (isActive) {
                            if (!holder.surface.isValid) continue
                            val canvas = holder.lockCanvas()
                            try {
                                onUpdate()
                                onRender(canvas, holder.surfaceFrame.width(), holder.surfaceFrame.height())
                            } finally {
                                // 必须确保解锁画布,否则会导致Surface卡死
                                holder.unlockCanvasAndPost(canvas)
                            }
                            delay(renderInterval)
                        }
                    }
                }
            }

            override fun onPause(owner: LifecycleOwner) {
                super.onPause(owner)
                renderJob?.cancel()
                renderJob = null
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)

        // 组件销毁时清理资源
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
            renderJob?.cancel()
        }
    }
}

使用这个重构后的组件

调用时只需传入更新和绘制逻辑,完全遵循Compose的函数式风格:

@Composable
fun OptimizedRenderScreen() {
    var xPos by remember { mutableStateOf(0f) }
    var yPos by remember { mutableStateOf(0f) }
    var dx by remember { mutableStateOf(2.5f) }
    var dy by remember { mutableStateOf(2.5f) }

    ComposeSurfaceRenderer(
        modifier = Modifier.fillMaxSize(),
        fps = 60,
        onUpdate = {
            xPos += dx
            yPos += dy
        },
        onRender = { canvas, width, height ->
            // 边界碰撞处理
            if (xPos > width || xPos < 0) dx *= -1
            if (yPos > height || yPos < 0) dy *= -1
            // 绘制内容
            canvas.drawColor(Color.WHITE)
            canvas.drawCircle(xPos, yPos, 50f, Paint().apply {
                color = Color.parseColor("#F44336")
                isAntiAlias = true
            })
        }
    )
}

核心注意事项

  • 生命周期绑定:必须将渲染逻辑与Compose的LocalLifecycleOwner绑定,确保在页面暂停时停止渲染,避免不必要的资源消耗和内存泄漏。
  • Surface有效性检查:每次渲染前一定要检查surface.isValid,防止Surface已销毁时尝试绘制导致崩溃。
  • 异常处理:渲染线程的异常必须通过CoroutineExceptionHandler捕获,避免影响主线程。
  • 画布解锁:调用lockCanvas()后必须在finally块中调用unlockCanvasAndPost(),否则会导致Surface无法继续使用。

内容的提问来源于stack exchange,提问作者Thracian

火山引擎 最新活动