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:nameandroid.permission.FOREGROUND_SERVICE / service android:name.ScreenCaptureService android:foregroundServiceTypemediaProjection android:exportedtrue/常见误区遗漏foregroundServiceType声明服务启动后才申请MediaProjection权限通知渠道不符合Android 8.0要求1.2 服务启动时序控制正确的执行顺序应该是启动前台服务并显示通知获取用户授权createScreenCaptureIntent在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秒可能导致ANR2. 音频捕获被忽视的RECORD_AUDIO权限当录屏需要同步录制系统音频时另一个隐蔽的陷阱正在等待java.lang.RuntimeException: setAudioSource failed2.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: Arrayout 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录屏功能的稳定性和用户体验。建议在开发过程中建立完整的设备测试矩阵特别关注各厂商旗舰机型的行为差异。