Android全局截屏与录屏实战MediaProjection高阶应用指南在移动应用开发中屏幕内容捕获一直是颇具挑战性的技术点。传统View截图方案通过Canvas.draw()方法虽然简单直接但存在明显局限——无法捕获系统状态栏、悬浮窗、视频播放画面等超出View层级的内容。这种残缺的截图体验在需要精确记录屏幕状态的客服系统、教学演示、游戏直播等场景中尤为致命。1. 全局捕获技术选型与架构设计1.1 传统方案的技术瓶颈通过View层级遍历进行截图的方法本质上是对应用窗口的自我复制。这种方案存在三个结构性缺陷内容缺失系统级UI元素状态栏、导航栏不属于应用View树动态内容丢失SurfaceView/TextureView的视频画面、WebGL渲染内容性能损耗复杂View树遍历与重绘的CPU计算开销// 传统View截图示例 - 存在内容缺失风险 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 }1.2 MediaProjection系统架构Android 5.0引入的MediaProjection API提供了系统级的屏幕捕获能力其核心工作原理如下组件作用关键特性MediaProjectionManager入口服务获取用户授权IntentVirtualDisplay虚拟显示层映射屏幕内容到SurfaceImageReader/MediaRecorder数据接收器处理图像/视频流数据流示意图用户授权 → 2. 创建VirtualDisplay → 3. 绑定Surface → 4. 数据回调处理注意从Android 10开始使用MediaProjection必须配合前台服务Foreground Service否则会抛出SecurityException2. 截屏功能完整实现2.1 权限与服务配置AndroidManifest.xml需声明以下关键配置uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / service android:name.ScreenCaptureService android:foregroundServiceTypemediaProjection android:exportedtrue/对于Android 12设备需要额外处理PendingIntent的可变性val pendingIntent if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE) } else { PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) }2.2 图像捕获核心流程初始化ImageReaderval displayMetrics resources.displayMetrics val imageReader ImageReader.newInstance( displayMetrics.widthPixels, displayMetrics.heightPixels, PixelFormat.RGBA_8888, 2 ).apply { setOnImageAvailableListener({ reader - // 图像可用回调 }, handler) }创建VirtualDisplayval virtualDisplay mediaProjection.createVirtualDisplay( ScreenCapture, displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader.surface, null, null )图像数据转换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) } }2.3 内存管理最佳实践使用try-with-resources确保Image对象释放限制最大图像缓存数量ImageReader构造参数及时调用MediaProjection.stop()释放资源image.use { img - // 处理图像数据 } virtualDisplay?.release() mediaProjection?.stop()3. 录屏功能进阶实现3.1 多权限动态申请录屏功能需要额外权限uses-permission android:nameandroid.permission.RECORD_AUDIO / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE /Android 11需添加MANAGE_EXTERNAL_STORAGE权限声明uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGE tools:ignoreScopedStorage /3.2 MediaRecorder配置策略不同Android版本的推荐参数配置参数Android 5-9Android 10视频编码H.264H.265(HEVC)分辨率1080P设备原生分辨率比特率4Mbps动态比特率音频采样44.1kHz48kHzfun configureRecorder(outputFile: File): MediaRecorder { return MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setVideoEncoder(MediaRecorder.VideoEncoder.HEVC) setVideoEncodingProfile( CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH).let { MediaCodecInfo.CodecProfileLevel.HEVCProfileMainHighTierLevel5 } ) setVideoSize(width, height) setVideoFrameRate(30) setOutputFile(outputFile.absolutePath) } }3.3 性能优化技巧帧率控制val display windowManager.defaultDisplay val frameRate display.mode.refreshRate.toInt() recorder.setVideoFrameRate(min(frameRate, 60))动态比特率调整val bitRate when { resolution 1440 - 8_000_000 resolution 1080 - 5_000_000 else - 3_000_000 } recorder.setVideoEncodingBitRate(bitRate)温度监控val thermalManager getSystemService(THERMAL_SERVICE) as ThermalManager thermalManager.addThermalStatusListener { status - when (status) { ThermalManager.THERMAL_STATUS_SEVERE - adjustQuality(50) ThermalManager.THERMAL_STATUS_CRITICAL - stopRecording() } }4. 高版本适配与异常处理4.1 Android 10关键变更前台服务强制要求必须声明foregroundServiceTypemediaProjection需显示持续通知不可关闭服务启动5秒内必须调用startForeground()隐私限制增强截屏时状态栏会显示隐私指示器录屏开始时有系统Toast提示4.2 常见异常解决方案异常类型触发场景解决方案SecurityException未使用前台服务检查service声明和启动顺序IllegalStateExceptionMediaRecorder配置错误确保调用顺序setOutputFormat→setAudioEncoder→prepareRuntimeException权限不足动态检查RECORD_AUDIO权限IOException存储不可用使用MediaStore API而非直接文件路径典型错误处理示例try { mediaRecorder.start() } catch (e: IllegalStateException) { Log.e(TAG, MediaRecorder配置错误, e) resetRecorder() } catch (e: RuntimeException) { if (e.message?.contains(permission) true) { requestPermissions(arrayOf(RECORD_AUDIO), REQUEST_CODE) } }4.3 兼容性处理策略版本分支处理fun createVirtualDisplayCompat(): VirtualDisplay { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { mediaProjection.createVirtualDisplay( /* 参数省略 */, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, surface, null, null ) } else { mediaProjection.createVirtualDisplay( /* 参数省略 */, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null ) } }存储适配方案fun getOutputFile(context: Context, fileName: String): File { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { val contentValues ContentValues().apply { put(MediaStore.Video.Media.DISPLAY_NAME, fileName) put(MediaStore.Video.Media.MIME_TYPE, video/mp4) } val uri context.contentResolver.insert( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues ) File(context.contentResolver.openFileDescriptor(uri!!, w)?.fileDescriptor) } else { File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_MOVIES), fileName) } }在实现全局截屏和录屏功能时最容易被忽视的是VirtualDisplay的生命周期管理。实际测试发现在部分厂商ROM中VirtualDisplay可能会在屏幕旋转时异常断开此时需要重新建立显示通道。建议在onConfigurationChanged中添加重连逻辑同时做好状态保存与恢复