Android 13 Media Projection截图偶尔黑屏问题求助
Android 13 MediaProjection 偶尔黑屏问题排查与修复
核心问题分析
你的代码存在几个关键问题,直接导致了偶尔黑屏的现象:
- 过早停止MediaProjection:在
acquireLatestImage()之前就调用mediaProjection.stop(),此时系统可能还没完成帧捕获,直接终止投影会导致获取空帧或黑屏帧。 - ImageReader配置不合理:
maxImages设为1,当系统帧队列有延迟时,旧帧被覆盖但新帧未就绪,容易出现空帧。 - 资源释放逻辑混乱:重复调用
mediaProjection.stop(),且Image的关闭时机晚于部分资源处理,可能引发帧数据异常。 - 未处理帧获取失败场景:当
acquireLatestImage()返回null时,没有对应的清理逻辑,可能导致投影资源泄漏。
针对性修复方案
1. 调整MediaProjection停止时机
必须在成功获取并处理完Image之后,再停止MediaProjection,避免中断帧捕获流程。
2. 优化ImageReader配置
将maxImages调整为2-3,给系统预留帧缓冲空间,减少帧丢失概率。
3. 规范资源释放顺序
确保Image在Bitmap处理完成后立即关闭,避免占用系统帧资源;同时避免重复调用mediaProjection.stop()。
4. 增加异常处理与重试机制
针对Android 13的系统特性,增加帧获取失败的重试逻辑,最多重试2次,避免单次失败直接返回黑屏。
修改后的完整代码
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getRealSize(size); final int width = size.x, height = size.y; // 调整maxImages为2,增加帧缓冲 final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2); final int[] retryCount = {0}; // 重试计数器 imageReader.setOnImageAvailableListener(reader -> { Image image = null; try { image = reader.acquireLatestImage(); if (image == null) { // 帧获取失败,重试一次(最多2次) if (retryCount[0] < 2) { retryCount[0]++; // 延迟100ms重试,给系统时间准备帧 new Handler(Looper.getMainLooper()).postDelayed(() -> { if (mediaProjection != null) { // 重新创建VirtualDisplay触发帧捕获 createVirtualDisplay(mediaProjection, imageReader.getSurface(), width, height); } }, 100); } return; } Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * width; Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); // 裁剪掉padding部分,得到正确尺寸的截图 Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); bitmap.recycle(); // 释放原bitmap资源 String fileName = "Screenshot_" + System.currentTimeMillis() + ".jpg"; String destinationPath = context.getExternalFilesDir(null) + "/screenshots/" + fileName; File imageFile = new File(destinationPath); // 确保目录存在 imageFile.getParentFile().mkdirs(); try (FileOutputStream outputStream = new FileOutputStream(imageFile)) { croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); } String mediaPath = Environment.DIRECTORY_PICTURES + File.separator + "Screenshots/myapp" + File.separator; ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); values.put(MediaStore.Images.Media.IS_PENDING, 0); values.put(MediaStore.Images.Media.RELATIVE_PATH, mediaPath); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.SIZE, imageFile.length()); values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); Uri path = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (path != null) { try (OutputStream imageOutStream = context.getContentResolver().openOutputStream(path)) { croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream); } } croppedBitmap.recycle(); // 裁剪后的bitmap也及时释放 } catch (IOException e) { e.printStackTrace(); } finally { // 确保Image资源被释放 if (image != null) { image.close(); } // 处理完成后再停止MediaProjection if (mediaProjection != null) { mediaProjection.stop(); mediaProjection = null; } // 关闭ImageReader if (reader != null) { reader.close(); } } }, null); // 单独提取创建VirtualDisplay的方法,方便重试调用 private void createVirtualDisplay(MediaProjection mediaProjection, Surface surface, int width, int height) { if (mediaProjection != null) { mediaProjection.createVirtualDisplay("ScreenshotDisplay", width, height, Resources.getSystem().getDisplayMetrics().densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); } }
额外注意事项(Android 13适配)
- 权限检查:确保已经申请
POST_NOTIFICATIONS权限(Android 13+要求),否则MediaProjection可能无法正常启动。 - 后台限制:Android 13对后台启动MediaProjection有更严格的限制,确保截图操作在前台触发(比如用户点击按钮时执行)。
- Surface生命周期:VirtualDisplay的Surface要和ImageReader的Surface绑定,确保在ImageReader就绪后再创建VirtualDisplay。
内容的提问来源于stack exchange,提问作者Jayce
相关产品推荐
相关产品推荐

