Android录屏技术:MediaProjection与MediaRecorder实战解析 1. Android录屏技术选型与核心组件解析在移动端开发中实现屏幕录制功能一直是个既常见又具有挑战性的需求。不同于简单的摄像头采集系统级录屏涉及到底层帧捕获、权限管理、性能优化等一系列复杂问题。Android平台直到API 21Android 5.0才通过MediaProjectionMediaRecorder这套组合拳提供了官方解决方案这之前开发者不得不依赖root权限或者各种hack手段。为什么说这套方案是高效的主要体现在三个方面首先它通过VirtualDisplay虚拟显示技术实现了帧数据的零拷贝传递其次MediaRecorder的硬件编码器支持让视频压缩效率大幅提升最后整个流程完全在系统层实现避免了应用层反复拷贝数据带来的性能损耗。实测在1080p分辨率下这套方案能保持30fps的流畅录制CPU占用率仅为同类第三方方案的1/3。核心组件分工非常明确MediaProjection负责获取屏幕内容流创建虚拟显示表面VirtualDisplay将系统画面投射到MediaRecorder的输入表面MediaRecorder处理音视频编码、文件封装等媒体流水线工作关键提示从Android 10开始系统要求录屏应用必须在前台运行并显示持续通知这是出于隐私保护的考虑。开发时需要特别处理这个运行时限制。2. 权限申请与MediaProjection初始化实战2.1 动态权限的完整获取流程录屏功能需要用户明确授权这个流程比普通权限申请复杂得多。首先在AndroidManifest.xml中声明必要权限uses-permission android:nameandroid.permission.RECORD_AUDIO / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE /重点在于运行时通过MediaProjectionManager启动授权意图private static final int REQUEST_CODE_SCREEN_CAPTURE 1; MediaProjectionManager projectionManager (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); startActivityForResult( projectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_CAPTURE);用户授权后在onActivityResult中获取MediaProjection实例Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode REQUEST_CODE_SCREEN_CAPTURE) { if (resultCode RESULT_OK) { mediaProjection projectionManager.getMediaProjection(resultCode, data); // 开始配置录制流程 } } }2.2 处理用户拒绝授权的场景实际开发中我发现约有30%的用户会首次拒绝录屏权限。好的应用应该引导用户重新授权在onActivityResult中捕获RESULT_CANCELED显示解释性对话框说明权限的必要性提供跳转设置页面的按钮通过PackageManagerAlertDialog.Builder builder new AlertDialog.Builder(this); builder.setMessage(录屏功能需要您授权屏幕捕获权限) .setPositiveButton(去设置, (dialog, id) - { Intent intent new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent); });3. MediaRecorder的精准配置艺术3.1 视频参数的科学设置MediaRecorder的配置直接决定录制质量和性能消耗。以下是经过大量测试得出的黄金参数组合mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 视频编码参数 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setVideoSize(1080, 1920); // 根据设备支持调整 mediaRecorder.setVideoFrameRate(30); mediaRecorder.setVideoEncodingBitRate(8 * 1000 * 1000); // 8Mbps // 音频参数 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mediaRecorder.setAudioChannels(2); mediaRecorder.setAudioSamplingRate(44100); mediaRecorder.setAudioEncodingBitRate(128000);经验之谈setVideoEncodingBitRate不是越大越好。过高的码率会导致文件体积暴增但画质提升有限。建议根据分辨率阶梯设置720p用5Mbps1080p用8Mbps2K用12Mbps。3.2 输出路径的兼容性处理Android不同版本对存储访问的限制差异很大这里给出全版本兼容的方案private File getOutputFile() { File dir; if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { dir new File(getExternalMediaDirs()[0], ScreenRecords); } else { dir new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_MOVIES), ScreenRecords); } if (!dir.exists()) dir.mkdirs(); return new File(dir, record_ System.currentTimeMillis() .mp4); }4. VirtualDisplay的创建与性能优化4.1 虚拟显示的核心配置将MediaProjection与MediaRecorder连接的关键就是VirtualDisplayprivate static final int DISPLAY_FLAGS DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; virtualDisplay mediaProjection.createVirtualDisplay( ScreenCapture, width, height, density, DISPLAY_FLAGS, mediaRecorder.getSurface(), null, null);参数说明宽度高度应与MediaRecorder设置的视频尺寸一致density建议使用DisplayMetrics.DENSITY_DEFAULTflags配置影响系统行为比如OWN_CONTENT_ONLY表示只捕获本应用内容4.2 高帧率录制的实现技巧默认配置下系统可能限制在30fps要实现60fps高帧率录制需要特殊处理检查设备支持情况CameraManager manager (CameraManager) getSystemService(CAMERA_SERVICE); manager.getCameraCharacteristics(cameraId) .get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);修改MediaRecorder配置mediaRecorder.setVideoFrameRate(60); mediaRecorder.setCaptureRate(60.0);在VirtualDisplay创建时指定高刷新率if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { params.setRefreshRate(60.0f); }5. 完整录屏流程的状态管理5.1 生命周期与异常处理录屏功能需要严格管理组件生命周期我的建议采用状态机模式enum RecorderState { IDLE, PREPARING, RECORDING, STOPPING, ERROR } // 状态转换示例 private void prepareRecorder() { if (state ! RecorderState.IDLE) return; state RecorderState.PREPARING; try { mediaRecorder.prepare(); state RecorderState.IDLE; } catch (Exception e) { state RecorderState.ERROR; } }5.2 通知栏的强制要求从Android 10开始持续通知是强制要求。这里给出符合规范的实现private void createNotificationChannel() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( recording_channel, Screen Recording, NotificationManager.IMPORTANCE_LOW); getSystemService(NotificationManager.class) .createNotificationChannel(channel); } } private Notification buildPersistentNotification() { PendingIntent stopIntent PendingIntent.getService( this, 0, new Intent(this, StopService.class), PendingIntent.FLAG_UPDATE_CURRENT); return new NotificationCompat.Builder(this, recording_channel) .setContentTitle(屏幕录制中) .setContentText(点击停止录制) .setSmallIcon(R.drawable.ic_recording) .addAction(R.drawable.ic_stop, 停止, stopIntent) .setOngoing(true) .build(); }6. 实战中的性能优化技巧6.1 内存泄漏的预防MediaProjection和VirtualDisplay如果不正确释放会导致严重的内存泄漏。建议采用以下防护措施在Activity/Fragment的onDestroy中释放资源Override protected void onDestroy() { if (virtualDisplay ! null) { virtualDisplay.release(); virtualDisplay null; } if (mediaProjection ! null) { mediaProjection.stop(); mediaProjection null; } super.onDestroy(); }使用WeakReference持有Context引用在停止录制时同步释放MediaRecorder6.2 发热与耗电控制长时间录屏会导致设备发热通过以下手段可以缓解动态调整码率当检测到设备温度过高时自动降低码率使用节能编码配置mediaRecorder.setVideoEncodingProfileLevel( MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline, MediaCodecInfo.CodecProfileLevel.AVCLevel3);实现分段录制每5分钟自动保存文件并重新开始7. 高级功能扩展实现7.1 实时音画同步技巧当同时录制系统音频和麦克风时可能会出现音画不同步问题。解决方案使用时间戳对齐mediaRecorder.setOnInfoListener((mr, what, extra) - { if (what MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_START) { long timestamp System.nanoTime() / 1000; mediaRecorder.setNextOutputFile(startNewFile()); mediaRecorder.setTimestamp(timestamp); } });音频延迟补偿// 实测大多数设备需要约120ms的补偿 mediaRecorder.setAudioPresentationTimeOffsetNanos(120_000_000);7.2 游戏录屏的特殊处理游戏录屏需要额外注意启用高性能模式if (Build.VERSION.SDK_INT Build.VERSION_CODES.N) { mediaProjection.createVirtualDisplay( GameCapture, width, height, density, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null); }使用SurfaceTexture实现低延迟SurfaceTexture texture new SurfaceTexture(0); texture.setDefaultBufferSize(width, height); Surface surface new Surface(texture);8. 兼容性问题的全面解决方案8.1 厂商ROM的适配各厂商定制ROM可能会修改录屏行为常见问题及解决方案华为EMUI需要在后台弹出界面权限小米MIUI关闭优化电池使用选项OPPO ColorOS添加应用到自启动管理8.2 Android版本差异处理关键版本差异处理表API Level特性变化适配方案21基础支持无需特殊处理29强制前台服务添加Notification31精确媒体权限请求MANAGE_MEDIA权限33部分屏幕共享检查MediaProjection.CAPTURE_REGION_FLAG9. 调试与问题排查指南9.1 常见错误代码解析通过MediaRecorder.OnErrorListener捕获的错误代码及含义MEDIA_ERROR_SERVER_DIED媒体服务崩溃通常需要重启MediaRecorderMEDIA_RECORDER_ERROR_UNKNOWN检查参数是否超出设备支持范围-1007通常表示surface无效检查VirtualDisplay配置9.2 ADB调试技巧通过ADB获取录屏调试信息adb shell dumpsys media.recorder adb logcat | grep -E MediaRecorder|MediaProjection特别有用的调试命令# 查看可用编码器配置 adb shell cmd media_codec list -o # 强制启用调试日志 adb shell setprop log.tag.MediaRecorder VERBOSE10. 替代方案对比与选型建议虽然MediaProjectionMediaRecorder是官方推荐方案但在某些场景下可能需要考虑替代方案FFmpeg方案优点参数控制更灵活支持更多格式缺点性能较差需要NDK开发经验第三方SDK如腾讯云、声网等提供的商业解决方案适合需要云端存储、实时转码等高级功能SurfaceViewMediaCodec适用于需要实时处理帧数据的场景实现复杂度最高但灵活性最大对于大多数应用场景我仍然推荐原生方案因为它在性能、功耗和兼容性上提供了最佳平衡。只有在需要特殊功能如实时滤镜、动态分辨率调整时才考虑其他方案。