告别Canvas截图:用MediaProjection搞定Android全局截屏(含视频画面)的完整流程
Android全局截屏实战MediaProjection技术解析与避坑指南在移动应用开发中截屏功能的需求远比表面看起来复杂。传统基于Canvas的截图方案虽然简单但当遇到状态栏捕获、视频画面截取等场景时开发者往往会发现View.draw()方法力不从心。我曾在一个电商直播项目中花了三天时间才意识到Canvas方案无法截取主播实时画面——这种挫败感促使我深入研究MediaProjection这套系统级解决方案。1. 为什么Canvas截图不够用许多Android工程师的第一个截屏实现通常是这样的fun captureView(view: View): Bitmap { val bitmap Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) val canvas Canvas(bitmap) view.draw(canvas) return bitmap }这种方案存在三个致命缺陷系统UI盲区状态栏、导航栏等系统级UI元素无法捕获动态内容缺失视频播放画面、WebGL渲染等SurfaceView内容只会显示为黑块跨应用限制无法截取其他应用的界面内容实际测试数据显示在包含视频播放的页面Canvas方案只能捕获约65%的可见内容2. MediaProjection核心机制解析MediaProjection作为Android 5.0引入的系统服务其工作原理与Canvas有本质区别特性Canvas方案MediaProjection方案捕获范围单个View层级全局显示内容包括覆盖层内容类型支持静态视图视频/3D/系统UI权限要求无特殊权限需用户授权性能影响较低中等需要虚拟显示系统版本兼容性全版本支持Android 5.0关键实现类说明MediaProjectionManager入口服务获取用户授权VirtualDisplay虚拟显示层映射到ImageReader的SurfaceImageReader帧数据接收器支持RGBA/YUV格式3. 完整实现流程3.1 基础环境配置首先在AndroidManifest.xml中声明必要权限和服务uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.RECORD_AUDIO / !-- 录屏需要 -- service android:name.ScreenCaptureService android:enabledtrue android:exportedtrue android:foregroundServiceTypemediaProjection /3.2 用户授权流程启动截屏前必须获取用户明确授权private fun startCapture() { val mediaManager getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager startActivityForResult( mediaManager.createScreenCaptureIntent(), REQUEST_CODE_CAPTURE ) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode REQUEST_CODE_CAPTURE resultCode RESULT_OK) { data?.let { handleCaptureIntent(it) } } }重要提示从Android 10开始必须在用户授权后的5秒内启动虚拟显示否则令牌会失效3.3 服务端核心实现创建前台服务处理实际截屏逻辑class ScreenCaptureService : Service() { private lateinit var imageReader: ImageReader private lateinit var mediaProjection: MediaProjection override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForeground(NOTIFICATION_ID, createNotification()) val token intent?.getParcelableExtraMediaProjection(EXTRA_PROJECTION_TOKEN) token?.let { mediaProjection it setupImageReader() } return START_STICKY } private fun setupImageReader() { val metrics Resources.getSystem().displayMetrics imageReader ImageReader.newInstance( metrics.widthPixels, metrics.heightPixels, PixelFormat.RGBA_8888, 2 ).apply { setOnImageAvailableListener({ reader - processImage(reader.acquireLatestImage()) }, Handler(Looper.getMainLooper())) } mediaProjection.createVirtualDisplay( ScreenCapture, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader.surface, null, null ) } private fun processImage(image: Image) { // 图像处理逻辑... } }3.4 图像数据处理关键点正确处理Image的Plane数据是避免黑边的关键fun imageToBitmap(image: Image): Bitmap? { val planes image.planes val buffer planes[0].buffer val pixelStride planes[0].pixelStride val rowStride planes[0].rowStride val rowPadding rowStride - pixelStride * image.width return Bitmap.createBitmap( image.width rowPadding / pixelStride, image.height, Bitmap.Config.ARGB_8888 ).apply { copyPixelsFromBuffer(buffer) } }常见问题处理绿屏现象检查PixelFormat是否匹配RGBA8888 vs YUV图像变形正确计算rowPadding值内存泄漏确保及时关闭Image对象4. 高级优化技巧4.1 性能调优方案对于高频截屏场景如游戏录制建议使用ImageFormat.PRIVATE格式避免CPU拷贝设置合适的ImageReader缓冲区数量通常2-3个采用双缓冲机制避免帧丢失imageReader ImageReader.newInstance( width, height, ImageFormat.PRIVATE, 3 // 三重缓冲 )4.2 Android版本适配要点不同系统版本的特殊处理Android版本注意事项解决方案5.0-6.0虚拟显示方向问题手动应用旋转矩阵7.0-8.1后台服务限制使用前台服务持续通知9.0隐私保护增强动态申请READ_FRAME_BUFFER10前台服务类型要求添加foregroundServiceType12待机模式限制使用WorkManager处理后台任务4.3 企业级实现建议在金融类App中实现安全截屏的实践水印添加在图像处理器中注入用户信息内容过滤识别并模糊敏感字段日志审计记录截屏操作时间戳fun addWatermark(bitmap: Bitmap, text: String): Bitmap { return bitmap.apply { Canvas(this).apply { drawText( text, width * 0.8f, height * 0.95f, Paint().apply { color Color.RED textSize 24f } ) } } }5. 典型问题排查指南问题现象截屏黑屏但状态栏可见可能原因SurfaceView层级问题解决方案在VirtualDisplay创建时添加VIRTUAL_DISPLAY_FLAG_PRESENTATION标志问题现象Android 12上截屏崩溃错误日志SecurityException: Media projections require a foreground service修复方案确保服务声明包含最新属性service android:foregroundServiceTypemediaProjection ... /问题现象图像出现彩色条纹诊断步骤检查Image的Format与PixelStride修正代码对于YUV格式需要特殊处理fun yuvToBitmap(image: Image): Bitmap { // 需要单独处理Y/U/V三个Plane // 具体实现参考libyuv库 }在最近一个车载HMI项目中我们通过MediaProjection实现了驾驶仪表盘的故障快照功能。发现当使用RGBA_8888格式时夜间模式下的低亮度区域会出现色阶断裂。最终解决方案是改用RGB_565格式并配合dither处理虽然牺牲了些许色彩深度但显著提升了暗部细节的保存能力。