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的机制调整共同导致的,之前版本只是因为系统调度宽松而侥幸正常,具体分析和修复如下:
一、代码本身的核心问题
未等待VirtualDisplay就绪就捕获帧
VirtualDisplay创建后需要和系统 compositor 同步帧数据,直接调用takeScreenshot大概率会拿到未初始化的空帧,Android 12及以下系统同步速度快,这个问题概率低,Android 13的调度延迟放大了这个问题。屏幕尺寸单位混用
代码中混用了deviceScreenUtils.getWidth()和deviceScreenUtils.getWidthPixels(),如果前者返回dp、后者返回px,会导致Bitmap尺寸计算错误,直接出现黑屏或画面错位。ImageReader缓冲区处理不严谨
- 设置的缓冲区数量为2,但回调中处理一次就移除监听器,可能导致缓冲区未正确回收,后续调用会出现帧阻塞。
acquireLatestImage()可能返回null,未做重试处理,直接跳过就会丢失有效帧。
二、Android 13的相关变更
Android 13针对MediaProjection和VirtualDisplay做了以下关键调整,进一步触发了代码缺陷:
- VirtualDisplay帧同步延迟增加:系统为优化后台资源占用,对非活跃VirtualDisplay会延迟帧渲染,第一次获取帧的等待时间变长,空帧概率大幅提升。
- 缓冲区管理逻辑优化:Android 13严格了ImageReader的缓冲区回收机制,之前版本中未正确回收缓冲区的代码,现在会直接导致空帧。
- 后台捕获限制收紧:App在后台调用MediaProjection时,系统会限制帧捕获频率,进一步增加黑屏概率。
三、具体修复方案
- 等待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 )
- 统一使用像素尺寸
把所有尺寸相关的调用替换为像素单位,避免计算错误:
mImageReader = ImageReader.newInstance( deviceScreenUtils.getWidthPixels(), deviceScreenUtils.getHeightPixels(), PixelFormat.RGBA_8888, 2 )
- 优化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
相关产品推荐
相关产品推荐

