如何使用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
相关产品推荐
相关产品推荐

