告别录屏黑屏!Android MediaProjection实战:从权限申请到VirtualDisplay完整避坑指南
Android MediaProjection实战:从权限申请到VirtualDisplay的完整避坑指南
在移动应用开发中,屏幕录制和截图功能已经成为许多应用的核心需求。无论是教育类应用的课程录制、游戏直播,还是企业协作工具的屏幕共享,都离不开这一关键技术。然而,Android平台上的MediaProjection API虽然强大,却隐藏着不少"坑",让不少开发者头疼不已。
1. 权限申请的正确姿势
权限申请是MediaProjection使用的第一步,也是最容易出错的地方。很多开发者在这里就遇到了各种奇怪的问题,比如弹窗不显示、回调不触发等。
1.1 理解MediaProjection的权限机制
与普通权限不同,MediaProjection需要用户显式授权。这意味着:
- 必须通过系统弹窗获取用户同意
- 无法通过普通权限请求方式获取
- 授权是一次性的,每次应用启动都需要重新获取
正确的权限请求代码如下:
private lateinit var mediaProjectionManager: MediaProjectionManager fun requestScreenCapturePermission() { mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager startActivityForResult( mediaProjectionManager.createScreenCaptureIntent(), SCREEN_CAPTURE_REQUEST_CODE ) }1.2 常见问题排查清单
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 弹窗不显示 | 未在主线程调用 | 确保在UI线程执行请求 |
| 回调不触发 | requestCode不匹配 | 检查onActivityResult中的requestCode |
| 立即返回取消 | 应用处于后台 | 确保应用在前台时请求权限 |
提示:Android 10+对后台启动Activity有严格限制,务必确保应用可见时请求权限
2. VirtualDisplay配置的深度解析
创建VirtualDisplay是MediaProjection的核心环节,也是"黑屏"问题的高发区。
2.1 关键参数详解
VirtualDisplay的创建涉及多个重要参数:
val virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenCapture", // 显示名称 width, // 宽度(像素) height, // 高度(像素) densityDpi, // 显示密度 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // 标志位 surface, // 输出Surface null, // 回调 null // Handler )其中最容易出问题的是标志位和Surface配置:
VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR:自动镜像内容,适合大多数场景VIRTUAL_DISPLAY_FLAG_PUBLIC:允许内容被其他应用看到VIRTUAL_DISPLAY_FLAG_SECURE:保护敏感内容
2.2 解决黑屏问题的关键
黑屏通常由以下原因导致:
- Surface未正确初始化:确保Surface有效且尺寸匹配
- 标志位冲突:避免同时使用互斥的标志位
- 生命周期管理不当:MediaProjection和VirtualDisplay需要在适当时候释放
推荐的生命周期管理方式:
override fun onDestroy() { super.onDestroy() virtualDisplay?.release() mediaProjection?.stop() }3. 跨版本兼容性处理
Android不同版本对MediaProjection的实现有细微差别,需要特别注意。
3.1 Android 10+的变化
从Android 10开始,系统对后台启动Activity和权限获取做了更严格的限制:
- 必须在前台服务中运行录屏
- 需要添加
FOREGROUND_SERVICE权限 - 必须显示通知告知用户正在录屏
示例前台服务配置:
<service android:name=".ScreenCaptureService" android:foregroundServiceType="mediaProjection" />3.2 版本适配检查清单
- [ ] 检查
Build.VERSION.SDK_INT判断版本 - [ ] Android 10+添加前台服务类型
- [ ] 处理不同版本下的Surface配置差异
- [ ] 适配不同DPI设备的显示问题
4. 性能优化与高级技巧
4.1 内存与CPU优化
录屏是资源密集型操作,优化至关重要:
- 降低分辨率:不必总是使用屏幕原生分辨率
- 调整帧率:30fps通常足够流畅
- 使用硬件编码:优先选择H.264/H.265硬件编码
// 配置MediaRecorder使用硬件编码 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264) mediaRecorder.setVideoEncodingProfile( CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))4.2 高级应用场景
掌握了基础功能后,还可以实现更复杂的功能:
- 选择性录屏:只录制特定区域或窗口
- 音频同步:将系统声音或麦克风输入与视频同步
- 实时处理:对视频流进行实时滤镜或分析处理
实现音频同步的示例:
// 配置音频源 mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC) mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC) // 确保音频和视频参数匹配 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)5. 实战问题解决方案
在实际项目中,我们经常会遇到一些棘手的问题。以下是几个典型场景的解决方案。
5.1 处理屏幕旋转
屏幕旋转会导致VirtualDisplay配置失效,需要动态调整:
- 监听配置变化
- 重新创建VirtualDisplay
- 保持Surface的有效性
override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) // 重新初始化VirtualDisplay setupVirtualDisplay() }5.2 多显示器支持
对于支持多显示器的设备,需要特别处理:
val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val displays = displayManager.displays // 选择主显示器或其他显示器 val primaryDisplay = displays.find { it.displayId == Display.DEFAULT_DISPLAY }5.3 低延迟优化
对于需要实时性的应用(如游戏直播),可以尝试:
- 使用
MediaCodec直接编码 - 降低GOP长度
- 调整关键帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) mediaFormat.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh)在实际项目中,我发现最容易被忽视的是正确释放资源。不恰当的释放顺序会导致内存泄漏甚至系统稳定性问题。建议按照以下顺序释放:
- 先停止VirtualDisplay
- 然后释放MediaProjection
- 最后关闭Surface
另外,对于需要长时间运行的录屏功能,建议添加心跳检测机制,定期检查各组件状态,确保录屏过程稳定可靠。
