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

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);
}

问题根源分析

  1. 过早终止服务:处理完第一张图片后直接调用stopProjection()和stopSelf(),会直接终止MediaProjection和VirtualDisplay,自然无法接收后续截图回调。
  2. Image资源未释放:获取到的Image对象使用后未调用image.close(),会耗尽ImageReader的缓冲区,即使设置maxImages=20,也会很快无法接收新图片。
  3. Bitmap回收不彻底:裁剪后的croppedBitmap使用完未回收,易造成内存泄漏,长期截图可能触发OOM。
  4. 同步文件操作阻塞线程:在回调线程中同步执行文件写入,会阻塞后续图片的接收逻辑。

解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 23:40:28