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

Android 13 程序化截图频繁黑屏问题求助

问题

我有一段在Android 12及以下版本运行完全正常的代码,但在Android 13中约95%的概率会截取到黑屏截图。代码如下:

fun setVirtualDisplay() {
    mImageReader = ImageReader.newInstance(
        deviceScreenUtils.getWidth(),
        deviceScreenUtils.getHeight(),
        PixelFormat.RGBA_8888,
        2
    )

    mImageReader?.let {
        val flags =
            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC

        try {
            mVirtualDisplay = mMediaProjection?.createVirtualDisplay(
                "screen-mirror", deviceScreenUtils.getWidth(), deviceScreenUtils.getHeight(),
                deviceScreenUtils.getDensity(), flags, it.surface, null, null
            )
        } catch (e: Throwable) {
            Log.i(TAG, "Media Projection not longer available...")
            mMediaProjectionIntent = null
            mImageReader = null
        }
    }
}

fun takeScreenshot() {
    Log.i(TAG, "Taking screenshot...")
    val handler = Handler(Looper.getMainLooper())
    mImageReader?.setOnImageAvailableListener({ imageReader ->
        val image = imageReader.acquireLatestImage()
        Log.i(TAG, "Acquiring image...")
        if (image != null) {
            val planes = image.planes
            val buffer = planes[0].buffer
            val pixelStride = planes[0].pixelStride
            val rowStride = planes[0].rowStride
            val rowPadding: Int = rowStride - pixelStride * deviceScreenUtils.getWidthPixels()
            val bitmap = Bitmap.createBitmap(
                deviceScreenUtils.getWidth() + (rowPadding.toFloat() / pixelStride.toFloat()).toInt(),
                deviceScreenUtils.getHeight(),
                Bitmap.Config.ARGB_8888
            )
            bitmap.copyPixelsFromBuffer(buffer)
            image.close()

            fileUtil.saveImage(bitmap)?.let {
                lastScreenshot.onNext(it)
            }
        }

        imageReader.setOnImageAvailableListener(null, handler)
        releaseBuffer()
    }, handler)
}

请问该截图功能在Android 13中有什么变更吗?还是我的代码本身存在问题,只是在之前版本中侥幸正常运行?

分析与解决方案

你的黑屏问题是代码本身的潜在缺陷和Android 13的机制调整共同导致的,之前版本只是因为系统调度宽松而侥幸正常,具体分析和修复如下:

一、代码本身的核心问题

  1. 未等待VirtualDisplay就绪就捕获帧
    VirtualDisplay创建后需要和系统 compositor 同步帧数据,直接调用takeScreenshot大概率会拿到未初始化的空帧,Android 12及以下系统同步速度快,这个问题概率低,Android 13的调度延迟放大了这个问题。

  2. 屏幕尺寸单位混用
    代码中混用了deviceScreenUtils.getWidth()和deviceScreenUtils.getWidthPixels(),如果前者返回dp、后者返回px,会导致Bitmap尺寸计算错误,直接出现黑屏或画面错位。

  3. ImageReader缓冲区处理不严谨

  • 设置的缓冲区数量为2,但回调中处理一次就移除监听器,可能导致缓冲区未正确回收,后续调用会出现帧阻塞。
  • acquireLatestImage()可能返回null,未做重试处理,直接跳过就会丢失有效帧。

二、Android 13的相关变更

Android 13针对MediaProjection和VirtualDisplay做了以下关键调整,进一步触发了代码缺陷:

  • VirtualDisplay帧同步延迟增加:系统为优化后台资源占用,对非活跃VirtualDisplay会延迟帧渲染,第一次获取帧的等待时间变长,空帧概率大幅提升。
  • 缓冲区管理逻辑优化:Android 13严格了ImageReader的缓冲区回收机制,之前版本中未正确回收缓冲区的代码,现在会直接导致空帧。
  • 后台捕获限制收紧:App在后台调用MediaProjection时,系统会限制帧捕获频率,进一步增加黑屏概率。

三、具体修复方案

  1. 等待VirtualDisplay就绪后再捕获帧
    通过VirtualDisplay.Callback监听onDisplayAdded事件,确认Display就绪后再触发截图:
mVirtualDisplay = mMediaProjection?.createVirtualDisplay(
    "screen-mirror", deviceScreenUtils.getWidthPixels(), deviceScreenUtils.getHeightPixels(),
    deviceScreenUtils.getDensity(), flags, it.surface, object : VirtualDisplay.Callback() {
        override fun onDisplayAdded(displayId: Int) {
            // Display就绪后再执行截图
            takeScreenshot()
        }
    }, handler
)
  1. 统一使用像素尺寸
    把所有尺寸相关的调用替换为像素单位,避免计算错误:
mImageReader = ImageReader.newInstance(
    deviceScreenUtils.getWidthPixels(),
    deviceScreenUtils.getHeightPixels(),
    PixelFormat.RGBA_8888,
    2
)
  1. 优化ImageReader缓冲区和帧处理
  • 使用use自动关闭Image,避免缓冲区泄漏;
  • 增加空帧重试逻辑:
mImageReader?.setOnImageAvailableListener({ imageReader ->
    val image = imageReader.acquireLatestImage()
    if (image == null) {
        // 空帧时延迟50ms重试
        handler.postDelayed({ takeScreenshot() }, 50)
        return@setOnImageAvailableListener
    }
    // 使用use自动关闭image
    image.use {
        val planes = it.planes
        val buffer = planes[0].buffer
        val pixelStride = planes[0].pixelStride
        val rowStride = planes[0].rowStride
        val rowPadding = rowStride - pixelStride * deviceScreenUtils.getWidthPixels()
        val bitmap = Bitmap.createBitmap(
            deviceScreenUtils.getWidthPixels() + rowPadding / pixelStride,
            deviceScreenUtils.getHeightPixels(),
            Bitmap.Config.ARGB_8888
        )
        bitmap.copyPixelsFromBuffer(buffer)
        
        fileUtil.saveImage(bitmap)?.let { path ->
            lastScreenshot.onNext(path)
        }
    }
    // 单次截图完成后释放资源
    imageReader.setOnImageAvailableListener(null, handler)
    releaseBuffer()
}, handler)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 08:20:27