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

如何使用MediaProjection API实现屏幕截图?

入门MediaProjection API指南及屏幕截图实现

一、入门学习步骤

针对你图形知识有限的情况,按以下顺序逐步掌握:

  • 先搞定授权流程:MediaProjection必须获取用户授权,核心是用MediaProjectionManager发起授权请求,处理返回结果拿到MediaProjection实例,这是所有操作的前提。
  • 理解核心组件:MediaProjection是捕获屏幕内容的入口,VirtualDisplay负责把屏幕内容投射到目标载体,ImageReader是截图场景下获取帧数据的关键工具。
  • 基础图像转换:不用深入图形学,先掌握Image对象转Bitmap的常规逻辑,比如处理YUV或RGBA格式的帧数据转换。
  • 资源管理:重点记住MediaProjection、VirtualDisplay、ImageReader这些对象用完必须释放,避免内存泄漏。

二、屏幕截图完整代码示例

1. 权限声明与授权请求

先在AndroidManifest.xml中添加必要声明:

<!-- Android 10+ 无需存储权限,旧版本需要以下权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

在Activity中实现授权逻辑:

private static final int REQUEST_SCREEN_CAPTURE = 100;
private MediaProjection mMediaProjection;

// 触发授权弹窗
private void requestCapturePermission() {
    MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    Intent captureIntent = manager.createScreenCaptureIntent();
    startActivityForResult(captureIntent, REQUEST_SCREEN_CAPTURE);
}

// 处理授权结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_SCREEN_CAPTURE && resultCode == RESULT_OK) {
        mMediaProjection = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE))
                .getMediaProjection(resultCode, data);
        // 授权成功,执行截图
        takeScreenshot();
    }
}

2. 截图核心逻辑

用ImageReader捕获屏幕帧并转换为Bitmap:

private void takeScreenshot() {
    DisplayMetrics metrics = getResources().getDisplayMetrics();
    int screenWidth = metrics.widthPixels;
    int screenHeight = metrics.heightPixels;
    int density = metrics.densityDpi;

    // 创建ImageReader,指定宽高、图像格式(RGBA_8888方便转Bitmap)
    ImageReader imageReader = ImageReader.newInstance(screenWidth, screenHeight, ImageFormat.RGBA_8888, 1);

    // 创建VirtualDisplay,将屏幕内容投射到ImageReader的Surface
    VirtualDisplay virtualDisplay = mMediaProjection.createVirtualDisplay("ScreenshotDisplay",
            screenWidth, screenHeight, density,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            imageReader.getSurface(), null, null);

    // 监听图像可用事件,确保获取到有效帧
    imageReader.setOnImageAvailableListener(reader -> {
        try (Image image = reader.acquireLatestImage()) {
            if (image == null) 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 * screenWidth;

            // 创建Bitmap并填充数据
            Bitmap bitmap = Bitmap.createBitmap(screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888);
            bitmap.copyPixelsFromBuffer(buffer);
            // 裁剪掉多余的行填充
            Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight);
            bitmap.recycle();

            // 处理截图结果:保存到本地或显示
            saveScreenshot(croppedBitmap);
            croppedBitmap.recycle();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放所有资源
            imageReader.close();
            virtualDisplay.release();
            mMediaProjection.stop();
        }
    }, null);
}

// 保存截图到本地
private void saveScreenshot(Bitmap bitmap) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 用MediaStore存储,无需权限
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, "screenshot_" + System.currentTimeMillis() + ".png");
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyScreenshots");

        Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        if (uri != null) {
            try (OutputStream os = getContentResolver().openOutputStream(uri)) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
                Toast.makeText(this, "截图保存成功", Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    } else {
        // 旧版本处理,需提前申请存储权限
        File screenshotDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyScreenshots");
        if (!screenshotDir.exists()) screenshotDir.mkdirs();
        File file = new File(screenshotDir, "screenshot_" + System.currentTimeMillis() + ".png");
        try (FileOutputStream fos = new FileOutputStream(file)) {
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            Toast.makeText(this, "截图保存成功", Toast.LENGTH_SHORT).show();
            // 通知图库刷新
            MediaScannerConnection.scanFile(this, new String[]{file.getAbsolutePath()}, null, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、关键注意事项

  • 授权有效期:MediaProjection实例在设备旋转或应用后台可能失效,需要重新请求授权。
  • 图像格式:如果选择YUV_420_888格式,需要额外处理YUV转RGBA的逻辑,比直接用RGBA_8888复杂,新手优先选后者。
  • 线程问题:图像处理和保存操作建议放在子线程执行,避免阻塞主线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:03:35