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

Android中如何捕获含GameActivity OpenGL内容的全屏Bitmap?

问题原因

View.draw() 方法仅能捕获Android View体系内的绘制内容,而Jetpack GameActivity的游戏画面是通过OpenGL ES渲染到SurfaceView/TextureView的,这部分内容并不在View的绘制流程中,因此调用rootView.draw(c)只会绘制原生覆盖视图,游戏画面无法被捕获。

方案1:使用MediaProjection API(系统级屏幕捕获)

这是最可靠的方案,能捕获整个屏幕的所有内容(包括OpenGL游戏画面和原生视图),仅需申请屏幕捕获权限。

步骤

  1. 申请MediaProjection权限
  2. 创建VirtualDisplay,将屏幕内容输出到Surface
  3. 从Surface中获取帧数据并转换为Bitmap

代码示例

private var mediaProjection: MediaProjection? = null
private var imageReader: ImageReader? = null

// 初始化MediaProjection并申请权限
private fun initMediaProjection() {
    val captureIntent = MediaProjectionManager.getSingleton(this).createScreenCaptureIntent()
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK && result.data != null) {
            mediaProjection = MediaProjectionManager.getSingleton(this)
                .getMediaProjection(result.resultCode, result.data!!)
            setupVirtualDisplay()
        }
    }.launch(captureIntent)
}

// 设置VirtualDisplay用于接收屏幕帧
private fun setupVirtualDisplay() {
    val displayMetrics = resources.displayMetrics
    val width = displayMetrics.widthPixels
    val height = displayMetrics.heightPixels
    val density = displayMetrics.densityDpi

    imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2)
    mediaProjection?.createVirtualDisplay(
        "ScreenCapture",
        width, height, density,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
        imageReader?.surface, null, null
    )
}

// 捕获并转换屏幕内容为Bitmap
private fun captureScreen(): Bitmap? {
    val image = imageReader?.acquireLatestImage() ?: return null
    val planes = image.planes
    val buffer = planes[0].buffer
    val pixelStride = planes[0].pixelStride
    val rowStride = planes[0].rowStride
    val rowPadding = rowStride - pixelStride * image.width

    val bitmap = Bitmap.createBitmap(
        image.width + rowPadding / pixelStride,
        image.height, Bitmap.Config.ARGB_8888
    )
    bitmap.copyPixelsFromBuffer(buffer)
    image.close()

    // 裁剪掉多余的padding区域
    return Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
}

// 页面销毁时释放资源
override fun onDestroy() {
    super.onDestroy()
    mediaProjection?.stop()
    imageReader?.close()
}

方案2:结合OpenGL帧读取+原生视图绘制

如果你能控制游戏的OpenGL渲染流程,可以在游戏渲染完一帧后,读取OpenGL帧数据到Bitmap,再将原生覆盖视图绘制到这个Bitmap上。

代码示例

// 在GL线程中读取当前OpenGL帧数据
private fun readGLFrame(width: Int, height: Int): Bitmap {
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val pixelBuffer = IntArray(width * height)

    // 读取OpenGL帧(必须在GL线程调用)
    GLES20.glReadPixels(
        0, 0, width, height,
        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer
    )

    // OpenGL坐标系Y轴向上,需要翻转图像适配Android坐标系
    for (y in 0 until height) {
        for (x in 0 until width) {
            bitmap.setPixel(x, height - y - 1, pixelBuffer[y * width + x])
        }
    }
    return bitmap
}

// 在主线程将覆盖视图绘制到GL帧Bitmap上
private fun combineGLFrameAndOverlay(glBitmap: Bitmap): Bitmap {
    val combinedBitmap = glBitmap.copy(Bitmap.Config.ARGB_8888, true)
    val canvas = Canvas(combinedBitmap)
    // 绘制原生覆盖视图
    overlayViews.draw(canvas)
    return combinedBitmap
}

// 使用示例:跨线程完成帧读取与视图合并
fun captureGameWithOverlay() {
    val gameView = findViewById<View>(R.id.game_surface_view) // 替换为你的游戏视图ID
    val glWidth = gameView.width
    val glHeight = gameView.height

    // 在GL线程读取帧数据
    gameView.queueEvent {
        val glBitmap = readGLFrame(glWidth, glHeight)
        // 切换到主线程合并视图
        runOnUiThread {
            val finalBitmap = combineGLFrameAndOverlay(glBitmap)
            // 保存或使用finalBitmap
        }
    }
}

注意事项

  • 方案1需要用户授权屏幕捕获,无需修改游戏逻辑,适合大多数通用场景。
  • 方案2需要游戏端配合,适合对捕获性能要求较高或无法使用MediaProjection的场景,需注意GL线程与主线程的切换,避免ANR。

内容的提问来源于stack exchange,提问作者Felipe Ribeiro R. Magalhaes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 07:00:58