当前位置: 首页 > news >正文

Android Q以上版本,用MediaProjection录屏时遇到的3个坑和我的填坑记录

Android Q+录屏开发实战:MediaProjection避坑指南与高阶优化

在移动应用开发中,屏幕录制功能的需求日益增长——从游戏精彩时刻保存到在线教育演示制作,再到远程协作技术支持。然而,当你的应用目标平台升级到Android Q及以上版本时,原本"能用"的MediaProjection实现可能突然抛出各种SecurityException,让开发者措手不及。本文将深入剖析三个最具代表性的兼容性问题,并提供经过生产环境验证的解决方案。

1. 权限与服务:Android Q+的强制前台服务机制

Android 10引入的隐私保护政策对屏幕捕获行为进行了严格规范。我们首先遭遇的就是这个经典异常:

java.lang.SecurityException: Media projections require a foreground service...

1.1 前台服务配置要点

在AndroidManifest.xml中需要声明两项关键配置:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <service android:name=".ScreenCaptureService" android:foregroundServiceType="mediaProjection" android:exported="true"/>

常见误区

  • 遗漏foregroundServiceType声明
  • 服务启动后才申请MediaProjection权限
  • 通知渠道不符合Android 8.0+要求

1.2 服务启动时序控制

正确的执行顺序应该是:

  1. 启动前台服务并显示通知
  2. 获取用户授权(createScreenCaptureIntent)
  3. 在onActivityResult中初始化MediaProjection
// 错误示例:先申请权限再启动服务 fun startRecording() { val mediaManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager startActivityForResult(mediaManager.createScreenCaptureIntent(), REQUEST_CODE) // 此时服务尚未启动,必定抛出异常 } // 正确流程 fun safeStartRecording() { val serviceIntent = Intent(this, CaptureService::class.java) startService(serviceIntent) // 先启动服务 bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE) Handler(Looper.getMainLooper()).postDelayed({ val mediaManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager startActivityForResult(mediaManager.createScreenCaptureIntent(), REQUEST_CODE) }, 300) // 确保服务已启动 }

提示:Android 12+要求前台服务通知立即显示,延迟超过5秒可能导致ANR

2. 音频捕获:被忽视的RECORD_AUDIO权限

当录屏需要同步录制系统音频时,另一个隐蔽的陷阱正在等待:

java.lang.RuntimeException: setAudioSource failed

2.1 动态权限管理策略

除了基本的存储权限,音频采集需要额外处理:

private val REQUIRED_PERMISSIONS = arrayOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE ) fun checkPermissions() { val ungranted = REQUIRED_PERMISSIONS.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (ungranted.isNotEmpty()) { ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), PERMISSION_REQUEST_CODE) } else { startRecordingWorkflow() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { startRecordingWorkflow() } else { showPermissionDeniedDialog() } } }

2.2 MediaRecorder配置优化

针对不同Android版本的最佳参数配置:

参数项Android 5-9 推荐值Android 10+ 推荐值注意事项
视频编码器H.264H.265 (HEVC)需检查设备支持情况
音频采样率44.1kHz48kHz影响音质与文件大小
关键帧间隔2秒1秒影响视频seek性能
比特率控制模式CQ (恒定质量)VBR (动态比特率)平衡质量与文件大小
fun setupMediaRecorder(outputFile: File): MediaRecorder { return MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { setAudioEncoder(MediaRecorder.AudioEncoder.AAC_ELD) setVideoEncoder(MediaRecorder.VideoEncoder.HEVC) setVideoEncodingProfile( MediaCodecInfo.CodecProfileLevel.HEVCProfileMain, MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31 ) } else { setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setVideoEncoder(MediaRecorder.VideoEncoder.H264) } setOutputFile(outputFile.absolutePath) prepare() } }

3. Android 12+的PendingIntent新规

当应用目标API升级到31时,这个运行时崩溃会让许多开发者困惑:

java.lang.IllegalArgumentException: Targeting S+ requires one of FLAG_IMMUTABLE...

3.1 兼容性处理方案

通知栏PendingIntent的创建需要区分版本:

fun createNotificationPendingIntent(): PendingIntent { val intent = Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } return PendingIntent.getActivity(this, 0, intent, flags) }

3.2 虚拟显示配置进阶技巧

Android 12对虚拟显示的行为也有细微调整:

fun createVirtualDisplay( mediaProjection: MediaProjection, width: Int, height: Int, dpi: Int, surface: Surface ): VirtualDisplay { val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION or DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY } else { DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC } return mediaProjection.createVirtualDisplay( "ScreenCapture", width, height, dpi, flags, surface, null, null ) }

4. 性能优化与异常处理实战

4.1 内存泄漏防护体系

MediaProjection相关组件必须严格管理生命周期:

class ScreenCaptureService : Service() { private var mediaProjection: MediaProjection? = null private var virtualDisplay: VirtualDisplay? = null override fun onDestroy() { virtualDisplay?.release() mediaProjection?.stop() super.onDestroy() } fun cleanUp() { virtualDisplay?.let { it.release() virtualDisplay = null } mediaProjection?.let { it.stop() mediaProjection = null } } }

4.2 帧率稳定方案

通过SurfaceTexture实现帧率控制:

fun setupFrameRateController(width: Int, height: Int): SurfaceTexture { val surfaceTexture = SurfaceTexture(0).apply { setDefaultBufferSize(width, height) } val surface = Surface(surfaceTexture) // 使用Choreographer控制帧采样 val choreographer = Choreographer.getInstance() val callback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { surfaceTexture.updateTexImage() // 处理帧数据... if (isRecording) { choreographer.postFrameCallback(this) } } } choreographer.postFrameCallback(callback) return surfaceTexture }

4.3 设备兼容性矩阵

不同厂商设备的特殊处理:

厂商已知问题解决方案
小米后台服务被杀概率高启用自启动权限引导
华为虚拟显示黑屏关闭"智能分辨率"设置
OPPO音频采集失败使用VOICE_RECOGNITION作为音源
三星视频编码器不支持HEVC自动降级到H.264

在真实项目中,我们还需要考虑以下增强功能点:

  • 动态码率调整(Network Aware Encoding)
  • 多轨道录制(视频+音频+传感器数据同步)
  • 低功耗模式(针对长时间录制场景)
  • 实时预览与编辑功能集成

通过系统化的异常预防和处理机制,可以显著提升MediaProjection录屏功能的稳定性和用户体验。建议在开发过程中建立完整的设备测试矩阵,特别关注各厂商旗舰机型的行为差异。

http://www.jsqmd.com/news/848756/

相关文章:

  • 四川沃美利建材:四川沃美利建材有限公司联系/四川玻璃钢格栅厂家/玻璃钢格栅花纹盖板/玻璃钢格栅厂家/玻璃钢格栅/选择指南 - 优质品牌商家
  • 手把手教你搞定ThingWorx Connectivity后台驱动:解决PLC连接报错与许可过期问题
  • OpenMV+STM32串口通信避坑指南:手把手教你搞定Apriltag数据打包与解析
  • 2026年当前河北高压电缆回收市场:专业服务商选择与价值变现指南 - 2026年企业推荐榜
  • 2026年废旧设备回收TOP5推荐:工厂设备回收/废旧金属回收/废铜回收/设备回收多少钱/设备拆除回收公司/锅炉回收/选择指南 - 优质品牌商家
  • 伊犁盛夏赴花海,霍城紫浪漫卷天山脚下
  • 2026年文献翻译格式全丢?研究生亲测5款工具,只有Scholaread能完美保留公式图表(附对比)
  • Midscene.js:为什么视觉驱动的UI自动化是跨平台测试的未来?
  • 建造者模式与Lombok
  • 2026年口碑好的开放式管焊机厂家联系方式/靠谱的封闭式管焊机厂家/全位置管板焊机公司 - 品牌推广大师
  • 2026年西南地区静止无功发生器厂家地域分布解析:低压有源滤波器、工业有源滤波器、工业静止无功发生器、有源滤波器柜选择指南 - 优质品牌商家
  • 生物医学英文文献去哪查?
  • Windows APK安装器终极指南:让安卓应用在电脑上完美运行
  • 外卡收单成功率低?Antom教你优化支付路由,挽回30%流失订单
  • Python GUI开发的终极解决方案:Pygubu Designer完整使用教程
  • 【仅限前500名技术决策者】:Perplexity设计灵感查询的专利级Query Embedding架构图(含TensorFlow Lite轻量化部署路径)
  • 美股历史数据api限频后,如何分时段分批次抓取?
  • 超越AlphaFold2?聊聊ESM系列模型在蛋白质设计中的独特优势与实战思考
  • 终极指南:CircuitJS1浏览器电路仿真工具完整教程
  • 不同版本Python安装常见问题与解决方案
  • Bamtone班通:国产在线铜厚测量优选方案
  • c++生产者消费者者模式学习笔记-2内存积压
  • 宁夏软件定制开发行业竞争力榜单:主流平台技术机制与工程交付能力权威评选
  • 通过Hermes Agent快速对接Taotoken大模型服务的配置方法
  • Hermes Agent 整体架构详解:AI Agent、Memory、Skills、MCP、工具调用、自我改进闭环全解析
  • 如何系统性地、可量化地评估 RAG 的效果?小白程序员必备收藏指南!
  • Hotkey Detective:终极Windows热键冲突检测工具,3步快速定位“按键劫持“元凶
  • 如何用Translumo轻松玩转多语言游戏和视频?5分钟掌握终极免费屏幕翻译神器!
  • 成都不良资产收包出包难?专业处置破局存量盘活困境
  • 南昌做定制网站小程序app开发的公司