Android中MediaProjection场景下ImageReader拍摄多张图片异常问题
MediaProjection 多截图时 ImageAvailableListener 停止执行的问题解决
问题描述
在MediaProjection场景下尝试拍摄多张截图,发现ImageAvailableListener仅执行一次后就不再继续,推测卡在等待下一张图片的状态,尝试使用acquireNextImage()也未解决该问题。
问题代码
ImageAvailableListener 实现
class ImageAvailableListener implements ImageReader.OnImageAvailableListener { @Override public void onImageAvailable(ImageReader reader) { Image image = null; FileOutputStream fos = null; bitmap = null; try { image = mImageReader.acquireLatestImage(); Log.d("servicecheck", "image" + image); Log.d("imagecheck", "" + image); if (image != null) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[2].getBuffer(); int pixelStride = planes[2].getPixelStride(); int rowStride = planes[2].getRowStride(); int rowPadding = rowStride - pixelStride * mWidth; // 创建Bitmap bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); // 裁剪多余宽度 Bitmap croppedBitmap; try { croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, mWidth, mHeight); } catch (OutOfMemoryError e) { croppedBitmap = bitmap; } if (croppedBitmap != bitmap) { bitmap.recycle(); } // 保存图片到文件 storeDirectory = new File(mStoreDir); storeDirectory.mkdir(); fos = new FileOutputStream(storeDirectory.getAbsolutePath() + "/myscreen_" + Calendar.getInstance().getTime() + ".png"); croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); stopProjection(); stopSelf(); scanFile(getApplicationContext(), Uri.fromFile(storeDirectory)); Log.d("stoptaskcheck", "stopSelf"); } else { Log.d("servicecheck", "null image" + image); } } catch (Exception e) { if (bitmap != null) { bitmap.recycle(); } e.printStackTrace(); } } }
VirtualDisplay 创建代码(已设置maxImages为20)
@SuppressLint("WrongConstant") private void createVirtualDisplay() { // 初始化截图Reader mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 20); mVirtualDisplay = mMediaProjection.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight, mDensity, getVirtualDisplayFlags(), mImageReader.getSurface(), null, null); mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), null); }
问题根源分析
- 过早终止服务:处理完第一张图片后直接调用
stopProjection()和stopSelf(),会直接终止MediaProjection和VirtualDisplay,自然无法接收后续截图回调。 - Image资源未释放:获取到的
Image对象使用后未调用image.close(),会耗尽ImageReader的缓冲区,即使设置maxImages=20,也会很快无法接收新图片。 - Bitmap回收不彻底:裁剪后的croppedBitmap使用完未回收,易造成内存泄漏,长期截图可能触发OOM。
- 同步文件操作阻塞线程:在回调线程中同步执行文件写入,会阻塞后续图片的接收逻辑。
解决方案
1. 控制服务终止时机
设置截图计数,达到指定数量后再调用stopProjection()和stopSelf(),而非处理完单张就终止。
2. 强制释放Image资源
在finally块中调用image.close(),确保每次获取的Image都能及时释放缓冲区。
3. 完善Bitmap资源回收
使用完所有Bitmap对象后,调用recycle()释放内存。
4. 异步处理文件保存
将文件写入操作放到子线程执行,避免阻塞ImageReader的回调线程。
修改后的代码示例
class ImageAvailableListener implements ImageReader.OnImageAvailableListener { // 截图计数与最大截图数 private int captureCount = 0; private final int MAX_CAPTURES = 5; @Override public void onImageAvailable(ImageReader reader) { Image image = null; Bitmap bitmap = null; Bitmap croppedBitmap = null; try { image = reader.acquireLatestImage(); if (image != null) { captureCount++; Log.d("servicecheck", "第" + captureCount + "张图片"); Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[2].getBuffer(); int pixelStride = planes[2].getPixelStride(); int rowStride = planes[2].getRowStride(); int rowPadding = rowStride - pixelStride * mWidth; // 创建Bitmap bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); // 裁剪多余宽度 try { croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, mWidth, mHeight); } catch (OutOfMemoryError e) { croppedBitmap = bitmap; } if (croppedBitmap != bitmap) { bitmap.recycle(); bitmap = null; } // 异步保存图片 new Thread(() -> { FileOutputStream fos = null; try { File storeDirectory = new File(mStoreDir); if (!storeDirectory.exists()) { storeDirectory.mkdirs(); } String fileName = "/myscreen_" + System.currentTimeMillis() + ".png"; fos = new FileOutputStream(storeDirectory.getAbsolutePath() + fileName); croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); scanFile(getApplicationContext(), Uri.fromFile(new File(storeDirectory, fileName))); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) fos.close(); } catch (IOException e) { e.printStackTrace(); } if (croppedBitmap != null) { croppedBitmap.recycle(); } } }).start(); // 达到最大截图数后停止服务 if (captureCount >= MAX_CAPTURES) { stopProjection(); stopSelf(); Log.d("stoptaskcheck", "达到最大截图数,停止服务"); } } else { Log.d("servicecheck", "获取图片为空"); } } catch (Exception e) { e.printStackTrace(); } finally { // 释放Image资源 if (image != null) { image.close(); } // 回收剩余Bitmap if (bitmap != null) { bitmap.recycle(); } } } }
额外注意事项
- 截图格式:使用
PixelFormat.RGBA_8888时,保存为PNG格式更能保证截图清晰度,避免JPEG压缩失真。 - 权限检查:Android 10及以上需使用MediaStore或申请
MANAGE_EXTERNAL_STORAGE权限,确保文件写入正常。 - 帧率控制:如需定时截图,可通过Handler添加定时逻辑,或调整VirtualDisplay的帧率参数控制截图间隔。
内容的提问来源于stack exchange,提问作者Subham Kumar
相关产品推荐
相关产品推荐

