屏幕录制是跨平台开发中技术壁垒较高、底层差异极大的模块。各操作系统出于隐私保护与安全考量对录屏权限的管控极为严格且数据流转路径完全不同。以下是 Android、iOS 与鸿蒙HarmonyOS三大平台调用系统录屏 API 的架构解析与实战指南一、 核心架构各平台录屏机制的底层差异Android 端MediaProjection API自 Android 5.0 引入核心流程是“用户显式授权”。应用需通过MediaProjectionManager发起系统级授权弹窗获取MediaProjection实例后创建VirtualDisplay虚拟显示屏将屏幕画面镜像到Surface再由MediaCodec进行硬件编码最后通过MediaMuxer封装为 MP4 文件。iOS 端ReplayKit 框架苹果从 iOS 10 引入 ReplayKitiOS 11 升级为 ReplayKit2。其核心设计理念是“强沙盒隔离”。录制过程运行在独立的系统受控进程中App 无法直接访问原始帧缓冲区。若需直播推流需创建Broadcast Upload Extension通过扩展程序获取视频流。鸿蒙端Media Kit鸿蒙原生支持录屏核心依赖Media Kit中的AVScreenCaptureRecorder模块。开发者需创建录屏实例配置音视频参数如传入文件描述符fd即可控制录屏的开始与停止。高阶场景下支持通过 Native (C/C) 接口获取原始音视频码流用于直播推流或远程桌面。二、 Android 端MediaProjection 与前台服务保活Android 录屏不仅涉及复杂的底层组件协同还受限于严格的后台执行策略。权限与授权机制必须在AndroidManifest.xml中声明FOREGROUND_SERVICE和RECORD_AUDIO若需录音权限。运行时需动态请求麦克风权限并通过createScreenCaptureIntent()获取用户的录屏授权。前台服务Foreground Service保活为防止用户在录制过程中将应用切到后台导致录制中断录屏逻辑必须封装在前台服务中并在通知栏持续显示录制状态与停止按钮。零拷贝数据通路通过MediaProjection.createVirtualDisplay()将屏幕画面输出到MediaCodec创建的输入 Surface全程由 GPU 硬件加速处理避免原始帧数据复制到 Java 堆内存极大降低功耗。// ScreenRecordService.kt import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent import android.hardware.display.DisplayManager import android.media.MediaRecorder import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build import android.os.IBinder import android.view.Surface class ScreenRecordService : Service() { private var mediaRecorder: MediaRecorder? null private var mediaProjection: MediaProjection? null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 1. 启动前台服务保活 startForeground(1, createNotification()) // 2. 获取 MediaProjection 并配置 MediaRecorder val resultCode intent?.getIntExtra(resultCode, 0) ?: 0 val data intent?.getParcelableExtraIntent(data) val projectionManager getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager mediaProjection projectionManager.getMediaProjection(resultCode, data) initRecorder() mediaProjection?.createVirtualDisplay( ScreenRecord, 1080, 1920, 300, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder?.surface, null, null ) mediaRecorder?.start() return START_STICKY } private fun initRecorder() { mediaRecorder MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setVideoEncoder(MediaRecorder.VideoEncoder.H264) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setOutputFile(${getExternalFilesDir(null)?.absolutePath}/record_${System.currentTimeMillis()}.mp4) prepare() } } override fun onDestroy() { mediaRecorder?.stop() mediaRecorder?.release() mediaProjection?.stop() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? null }三、 iOS 端ReplayKit2 扩展与沙盒文件管理iOS 的录屏开发高度依赖 Extension 机制与沙盒安全模型。Broadcast Upload Extension若需实现应用内录屏或推流必须在 Xcode 中创建Broadcast Upload Extension。系统会将录屏数据流回调给扩展程序中的SampleHandler开发者在此初始化推流器或编码器。沙盒文件导出原生录屏生成的视频默认保存在系统受管的私有缓存区。录制结束后通过stopRecordingWithHandler:触发异步导出获取指向沙盒内可读写路径如NSTemporaryDirectory()的 URL开发者方可对其进行重命名、移动或分享。麦克风权限强制声明即使应用仅录制屏幕画面不录音也必须在Info.plist中配置NSMicrophoneUsageDescription因为系统默认会启用麦克风混音通道。四、 鸿蒙端AVScreenCaptureRecorder 与 Picker 模式鸿蒙的录屏 API 设计更为现代化兼顾了易用性与隐私保护。ArkTS 基础录屏通过media.createAVScreenCaptureRecorder()创建实例传入具有写权限的文件描述符fd进行配置随后调用startRecording()即可开始录制。Picker 隐私选择器在 HarmonyOS 6.1.0 及以上版本支持通过setPickerMode调起系统级选择器让用户自主选择录制全屏、特定应用或特定窗口避免全屏隐私泄露。后台长时任务若配置了采集麦克风音频或需要在后台持续录制必须在module.json5中声明ohos.permission.MICROPHONE权限并申请长时任务KEEP_BACKGROUND_RUNNING防止录屏被系统挂起。// HarmonyScreenRecorder.ets import { media } from kit.MediaKit; import { fileIo } from kit.CoreFileKit; export class HarmonyScreenRecorder { private screenCapture: media.AVScreenCaptureRecorder | null null; async startRecording() { // 1. 创建录屏实例 this.screenCapture await media.createAVScreenCaptureRecorder(); // 2. 准备沙盒文件描述符 (fd) const filePath getContext().filesDir /record_${Date.now()}.mp4; const file fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); // 3. 配置参数并启动 const config: media.AVScreenCaptureRecordConfig { fd: file.fd, frameWidth: 1080, frameHeight: 1920, videoBitrate: 5000000, audioSampleRate: 48000, audioChannelCount: 2, audioBitrate: 96000 }; await this.screenCapture.init(config); await this.screenCapture.startRecording(); } async stopRecording() { await this.screenCapture?.stopRecording(); this.screenCapture?.release(); } }五、 跨平台架构与工程提效原生插件封装跨平台框架如 Flutter、uni-app无法直接调用底层录屏 API。必须通过原生插件Android 用 KotliniOS 用 Swift鸿蒙用 ArkTS封装录屏逻辑并通过MethodChannel向 Dart/TS 层暴露startRecording、stopRecording等统一接口。状态机与生命周期管理录屏是一个长耗时的状态机过程。跨平台封装层必须严格监听应用的生命周期如进入后台、锁屏在异常中断时自动释放底层编码器资源防止文件损坏或内存泄漏。合规与隐私保护所有平台的录屏功能在启动时均必须有明确的 UI 提示或系统级授权弹窗。对于敏感内容如 DRM 保护视频、密码输入框系统底层会自动进行黑屏或遮罩处理开发者需在 UI 层给予用户相应的反馈。六、 Android 端前台服务保活与硬件编码协同 (Kotlin)Android 录屏必须依赖前台服务Foreground Service防止进程被系统回收并通过MediaCodec与VirtualDisplay协同实现零拷贝硬件编码。前台服务与持续通知在启动录屏时必须调用startForeground()并绑定一个包含“停止录制”按钮的持续通知Notification。这不仅是为了保活更是 Android 系统的强制合规要求。硬件编码器协同通过MediaCodec.createEncoderByType(video/avc)初始化 H.264 硬件编码器获取其输入Surface。随后将该Surface传入MediaProjection.createVirtualDisplay()。屏幕画面会直接由 GPU 渲染到编码器中全程无需将视频帧拷贝到 Java 堆内存极大降低功耗与发热。音视频轨道同步使用MediaMuxer将MediaCodec输出的视频轨道与MediaRecorder或 AudioRecord输出的音频轨道合并。需在onOutputBufferAvailable回调中精准处理BufferInfo的时间戳防止音画不同步。// AndroidScreenRecordService.kt import android.app.* import android.content.Intent import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaMuxer import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build import android.os.IBinder import android.view.Surface class AndroidScreenRecordService : Service() { private var mediaProjection: MediaProjection? null private var virtualDisplay: VirtualDisplay? null private var mediaCodec: MediaCodec? null private var mediaMuxer: MediaMuxer? null private var inputSurface: Surface? null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 1. 启动前台服务并绑定持续通知强制合规要求 startForeground(1, createRecordingNotification()) // 2. 获取 MediaProjection 实例 val resultCode intent?.getIntExtra(resultCode, 0) ?: 0 val data intent?.getParcelableExtraIntent(data) val mgr getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager mediaProjection mgr.getMediaProjection(resultCode, data) // 3. 初始化 H.264 硬件编码器 initHardwareEncoder() // 4. 创建 VirtualDisplay 将屏幕画面直接输出到编码器 Surface零拷贝 virtualDisplay mediaProjection?.createVirtualDisplay( ScreenRecorder, 1080, 1920, 300, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null ) return START_STICKY } private fun initHardwareEncoder() { val format android.media.MediaFormat.createVideoFormat(video/avc, 1080, 1920).apply { setInteger(android.media.MediaFormat.KEY_BIT_RATE, 5000000) setInteger(android.media.MediaFormat.KEY_FRAME_RATE, 30) setInteger(android.media.MediaFormat.KEY_I_FRAME_INTERVAL, 1) } mediaCodec MediaCodec.createEncoderByType(video/avc) mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) inputSurface mediaCodec?.createInputSurface() mediaCodec?.start() // TODO: 启动异步线程读取 mediaCodec.outputBuffer 并写入 MediaMuxer } override fun onDestroy() { // 异常中断与资源安全释放 virtualDisplay?.release() mediaCodec?.stop() mediaCodec?.release() mediaMuxer?.stop() mediaMuxer?.release() mediaProjection?.stop() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? null }七、 iOS 端ReplayKit 扩展与沙盒文件导出 (Swift)iOS 的录屏逻辑被强制隔离在 Extension 中跨平台通信与文件导出需遵循严格的沙盒规范。Broadcast Upload Extension 桥接在 Xcode 中创建Broadcast Upload Extension重写SampleHandler。系统会将录屏的 CMSampleBuffer 实时回调给该扩展。若需将数据传回主 App 进行 UI 展示必须通过DarwinNotificationCenter或 App Groups 共享内存进行跨进程通信。沙盒文件安全导出系统默认将录屏文件存放在 Extension 的临时目录中。录制结束后必须在broadcastFinished()或stopRecording的回调中使用FileManager将文件移动到主 App 的沙盒目录如Documents或调用UIActivityViewController触发系统分享面板否则文件会在 Extension 销毁时被系统清理。麦克风权限强制声明即便业务逻辑上不需要录音也必须在主 App 的Info.plist中配置NSMicrophoneUsageDescription因为 ReplayKit 默认会捕获系统音频与麦克风混音通道缺失该声明会导致录屏启动直接崩溃。// IOSScreenRecorder.swift import ReplayKit import UIKit class IOSScreenRecorder { private let recorder RPScreenRecorder.shared() var onRecordingFinished: ((URL?) - Void)? func startRecording() { guard recorder.isAvailable else { return } // 注意即便不录音Info.plist 也必须声明 NSMicrophoneUsageDescription recorder.isMicrophoneEnabled true recorder.startRecording { error in if let error error { print(录屏启动失败: \(error.localizedDescription)) } } } func stopRecording() { recorder.stopRecording { previewController, error in guard error nil else { return } // 导出到沙盒可读写路径 if let preview previewController { preview.previewControllerDelegate self as? RPPreviewViewControllerDelegate // 实际业务中通常通过 RPPreviewViewController 让用户选择保存到相册 } } } }八、 跨平台录屏服务统一封装 (Flutter/Dart)为避免业务代码中充斥各平台原生录屏的条件编译强烈建议在跨平台层构建一套标准化的录屏服务 SDK。接口抽象与状态机管理定义统一的IScreenRecorder接口规范包含requestPermission()、startRecording()、stopRecording()等核心契约。在封装层内部实现严格的状态机如 Idle - Requesting - Recording - Saving防止因用户快速连续点击导致的底层状态异常。统一数据模型与路径回传各平台录屏结束后的文件路径获取方式不同。在封装层内部应定义跨平台统一的RecordingResult模型包含 filePath、duration、fileSize并在底层完成路径转换彻底屏蔽 Android 的 ContentResolver URI 与 iOS 的沙盒绝对路径差异。1. 接口抽象与状态机管理// screen_recorder_provider.dart enum RecordState { idle, requesting, recording, saving } class RecordingResult { final String filePath; final Duration duration; RecordingResult({required this.filePath, required this.duration}); } abstract class IScreenRecorder { Futurebool requestPermission(); Futurevoid startRecording(); FutureRecordingResult? stopRecording(); RecordState get currentState; }2. 业务层封装与异常中断处理// screen_recorder_service.dart class ScreenRecorderService { final IScreenRecorder _recorder; ScreenRecorderService(this._recorder); Futurevoid start() async { if (_recorder.currentState ! RecordState.idle) return; final granted await _recorder.requestPermission(); if (!granted) throw Exception(用户拒绝了录屏权限); await _recorder.startRecording(); } FutureRecordingResult? stop() async { if (_recorder.currentState ! RecordState.recording) return null; try { return await _recorder.stopRecording(); } catch (e) { // 异常中断时释放底层资源防止文件损坏 print(录屏异常中断: $e); return null; } } }九、 性能优化与合规避坑指南DRM 黑屏保护机制各平台底层均内置了版权保护机制。当录制包含 DRM 保护的内容如 Netflix、爱奇艺视频或密码输入框时系统会自动将该区域渲染为黑屏。开发者需在 UI 层给予用户明确的视觉反馈避免用户误认为录屏功能失效。鸿蒙 Picker 隐私模式在鸿蒙端开发时优先使用setPickerMode引导用户选择特定应用或窗口进行录制而非全屏录制。这不仅符合最新的隐私合规要求还能大幅降低编码器的性能开销。异常中断与资源释放录屏是高耗电、高内存占用的操作。跨平台封装层必须监听应用的生命周期与系统异常如来电中断、内存警告。在异常发生时必须强制调用底层的stop()与release()方法防止视频文件损坏未写入 moov atom或后台持续占用摄像头/麦克风导致其他应用无法调用。
屏幕录制:调用系统录屏API(138)
屏幕录制是跨平台开发中技术壁垒较高、底层差异极大的模块。各操作系统出于隐私保护与安全考量对录屏权限的管控极为严格且数据流转路径完全不同。以下是 Android、iOS 与鸿蒙HarmonyOS三大平台调用系统录屏 API 的架构解析与实战指南一、 核心架构各平台录屏机制的底层差异Android 端MediaProjection API自 Android 5.0 引入核心流程是“用户显式授权”。应用需通过MediaProjectionManager发起系统级授权弹窗获取MediaProjection实例后创建VirtualDisplay虚拟显示屏将屏幕画面镜像到Surface再由MediaCodec进行硬件编码最后通过MediaMuxer封装为 MP4 文件。iOS 端ReplayKit 框架苹果从 iOS 10 引入 ReplayKitiOS 11 升级为 ReplayKit2。其核心设计理念是“强沙盒隔离”。录制过程运行在独立的系统受控进程中App 无法直接访问原始帧缓冲区。若需直播推流需创建Broadcast Upload Extension通过扩展程序获取视频流。鸿蒙端Media Kit鸿蒙原生支持录屏核心依赖Media Kit中的AVScreenCaptureRecorder模块。开发者需创建录屏实例配置音视频参数如传入文件描述符fd即可控制录屏的开始与停止。高阶场景下支持通过 Native (C/C) 接口获取原始音视频码流用于直播推流或远程桌面。二、 Android 端MediaProjection 与前台服务保活Android 录屏不仅涉及复杂的底层组件协同还受限于严格的后台执行策略。权限与授权机制必须在AndroidManifest.xml中声明FOREGROUND_SERVICE和RECORD_AUDIO若需录音权限。运行时需动态请求麦克风权限并通过createScreenCaptureIntent()获取用户的录屏授权。前台服务Foreground Service保活为防止用户在录制过程中将应用切到后台导致录制中断录屏逻辑必须封装在前台服务中并在通知栏持续显示录制状态与停止按钮。零拷贝数据通路通过MediaProjection.createVirtualDisplay()将屏幕画面输出到MediaCodec创建的输入 Surface全程由 GPU 硬件加速处理避免原始帧数据复制到 Java 堆内存极大降低功耗。// ScreenRecordService.kt import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent import android.hardware.display.DisplayManager import android.media.MediaRecorder import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build import android.os.IBinder import android.view.Surface class ScreenRecordService : Service() { private var mediaRecorder: MediaRecorder? null private var mediaProjection: MediaProjection? null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 1. 启动前台服务保活 startForeground(1, createNotification()) // 2. 获取 MediaProjection 并配置 MediaRecorder val resultCode intent?.getIntExtra(resultCode, 0) ?: 0 val data intent?.getParcelableExtraIntent(data) val projectionManager getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager mediaProjection projectionManager.getMediaProjection(resultCode, data) initRecorder() mediaProjection?.createVirtualDisplay( ScreenRecord, 1080, 1920, 300, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder?.surface, null, null ) mediaRecorder?.start() return START_STICKY } private fun initRecorder() { mediaRecorder MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setVideoEncoder(MediaRecorder.VideoEncoder.H264) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setOutputFile(${getExternalFilesDir(null)?.absolutePath}/record_${System.currentTimeMillis()}.mp4) prepare() } } override fun onDestroy() { mediaRecorder?.stop() mediaRecorder?.release() mediaProjection?.stop() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? null }三、 iOS 端ReplayKit2 扩展与沙盒文件管理iOS 的录屏开发高度依赖 Extension 机制与沙盒安全模型。Broadcast Upload Extension若需实现应用内录屏或推流必须在 Xcode 中创建Broadcast Upload Extension。系统会将录屏数据流回调给扩展程序中的SampleHandler开发者在此初始化推流器或编码器。沙盒文件导出原生录屏生成的视频默认保存在系统受管的私有缓存区。录制结束后通过stopRecordingWithHandler:触发异步导出获取指向沙盒内可读写路径如NSTemporaryDirectory()的 URL开发者方可对其进行重命名、移动或分享。麦克风权限强制声明即使应用仅录制屏幕画面不录音也必须在Info.plist中配置NSMicrophoneUsageDescription因为系统默认会启用麦克风混音通道。四、 鸿蒙端AVScreenCaptureRecorder 与 Picker 模式鸿蒙的录屏 API 设计更为现代化兼顾了易用性与隐私保护。ArkTS 基础录屏通过media.createAVScreenCaptureRecorder()创建实例传入具有写权限的文件描述符fd进行配置随后调用startRecording()即可开始录制。Picker 隐私选择器在 HarmonyOS 6.1.0 及以上版本支持通过setPickerMode调起系统级选择器让用户自主选择录制全屏、特定应用或特定窗口避免全屏隐私泄露。后台长时任务若配置了采集麦克风音频或需要在后台持续录制必须在module.json5中声明ohos.permission.MICROPHONE权限并申请长时任务KEEP_BACKGROUND_RUNNING防止录屏被系统挂起。// HarmonyScreenRecorder.ets import { media } from kit.MediaKit; import { fileIo } from kit.CoreFileKit; export class HarmonyScreenRecorder { private screenCapture: media.AVScreenCaptureRecorder | null null; async startRecording() { // 1. 创建录屏实例 this.screenCapture await media.createAVScreenCaptureRecorder(); // 2. 准备沙盒文件描述符 (fd) const filePath getContext().filesDir /record_${Date.now()}.mp4; const file fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); // 3. 配置参数并启动 const config: media.AVScreenCaptureRecordConfig { fd: file.fd, frameWidth: 1080, frameHeight: 1920, videoBitrate: 5000000, audioSampleRate: 48000, audioChannelCount: 2, audioBitrate: 96000 }; await this.screenCapture.init(config); await this.screenCapture.startRecording(); } async stopRecording() { await this.screenCapture?.stopRecording(); this.screenCapture?.release(); } }五、 跨平台架构与工程提效原生插件封装跨平台框架如 Flutter、uni-app无法直接调用底层录屏 API。必须通过原生插件Android 用 KotliniOS 用 Swift鸿蒙用 ArkTS封装录屏逻辑并通过MethodChannel向 Dart/TS 层暴露startRecording、stopRecording等统一接口。状态机与生命周期管理录屏是一个长耗时的状态机过程。跨平台封装层必须严格监听应用的生命周期如进入后台、锁屏在异常中断时自动释放底层编码器资源防止文件损坏或内存泄漏。合规与隐私保护所有平台的录屏功能在启动时均必须有明确的 UI 提示或系统级授权弹窗。对于敏感内容如 DRM 保护视频、密码输入框系统底层会自动进行黑屏或遮罩处理开发者需在 UI 层给予用户相应的反馈。六、 Android 端前台服务保活与硬件编码协同 (Kotlin)Android 录屏必须依赖前台服务Foreground Service防止进程被系统回收并通过MediaCodec与VirtualDisplay协同实现零拷贝硬件编码。前台服务与持续通知在启动录屏时必须调用startForeground()并绑定一个包含“停止录制”按钮的持续通知Notification。这不仅是为了保活更是 Android 系统的强制合规要求。硬件编码器协同通过MediaCodec.createEncoderByType(video/avc)初始化 H.264 硬件编码器获取其输入Surface。随后将该Surface传入MediaProjection.createVirtualDisplay()。屏幕画面会直接由 GPU 渲染到编码器中全程无需将视频帧拷贝到 Java 堆内存极大降低功耗与发热。音视频轨道同步使用MediaMuxer将MediaCodec输出的视频轨道与MediaRecorder或 AudioRecord输出的音频轨道合并。需在onOutputBufferAvailable回调中精准处理BufferInfo的时间戳防止音画不同步。// AndroidScreenRecordService.kt import android.app.* import android.content.Intent import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaMuxer import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build import android.os.IBinder import android.view.Surface class AndroidScreenRecordService : Service() { private var mediaProjection: MediaProjection? null private var virtualDisplay: VirtualDisplay? null private var mediaCodec: MediaCodec? null private var mediaMuxer: MediaMuxer? null private var inputSurface: Surface? null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 1. 启动前台服务并绑定持续通知强制合规要求 startForeground(1, createRecordingNotification()) // 2. 获取 MediaProjection 实例 val resultCode intent?.getIntExtra(resultCode, 0) ?: 0 val data intent?.getParcelableExtraIntent(data) val mgr getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager mediaProjection mgr.getMediaProjection(resultCode, data) // 3. 初始化 H.264 硬件编码器 initHardwareEncoder() // 4. 创建 VirtualDisplay 将屏幕画面直接输出到编码器 Surface零拷贝 virtualDisplay mediaProjection?.createVirtualDisplay( ScreenRecorder, 1080, 1920, 300, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null ) return START_STICKY } private fun initHardwareEncoder() { val format android.media.MediaFormat.createVideoFormat(video/avc, 1080, 1920).apply { setInteger(android.media.MediaFormat.KEY_BIT_RATE, 5000000) setInteger(android.media.MediaFormat.KEY_FRAME_RATE, 30) setInteger(android.media.MediaFormat.KEY_I_FRAME_INTERVAL, 1) } mediaCodec MediaCodec.createEncoderByType(video/avc) mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) inputSurface mediaCodec?.createInputSurface() mediaCodec?.start() // TODO: 启动异步线程读取 mediaCodec.outputBuffer 并写入 MediaMuxer } override fun onDestroy() { // 异常中断与资源安全释放 virtualDisplay?.release() mediaCodec?.stop() mediaCodec?.release() mediaMuxer?.stop() mediaMuxer?.release() mediaProjection?.stop() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? null }七、 iOS 端ReplayKit 扩展与沙盒文件导出 (Swift)iOS 的录屏逻辑被强制隔离在 Extension 中跨平台通信与文件导出需遵循严格的沙盒规范。Broadcast Upload Extension 桥接在 Xcode 中创建Broadcast Upload Extension重写SampleHandler。系统会将录屏的 CMSampleBuffer 实时回调给该扩展。若需将数据传回主 App 进行 UI 展示必须通过DarwinNotificationCenter或 App Groups 共享内存进行跨进程通信。沙盒文件安全导出系统默认将录屏文件存放在 Extension 的临时目录中。录制结束后必须在broadcastFinished()或stopRecording的回调中使用FileManager将文件移动到主 App 的沙盒目录如Documents或调用UIActivityViewController触发系统分享面板否则文件会在 Extension 销毁时被系统清理。麦克风权限强制声明即便业务逻辑上不需要录音也必须在主 App 的Info.plist中配置NSMicrophoneUsageDescription因为 ReplayKit 默认会捕获系统音频与麦克风混音通道缺失该声明会导致录屏启动直接崩溃。// IOSScreenRecorder.swift import ReplayKit import UIKit class IOSScreenRecorder { private let recorder RPScreenRecorder.shared() var onRecordingFinished: ((URL?) - Void)? func startRecording() { guard recorder.isAvailable else { return } // 注意即便不录音Info.plist 也必须声明 NSMicrophoneUsageDescription recorder.isMicrophoneEnabled true recorder.startRecording { error in if let error error { print(录屏启动失败: \(error.localizedDescription)) } } } func stopRecording() { recorder.stopRecording { previewController, error in guard error nil else { return } // 导出到沙盒可读写路径 if let preview previewController { preview.previewControllerDelegate self as? RPPreviewViewControllerDelegate // 实际业务中通常通过 RPPreviewViewController 让用户选择保存到相册 } } } }八、 跨平台录屏服务统一封装 (Flutter/Dart)为避免业务代码中充斥各平台原生录屏的条件编译强烈建议在跨平台层构建一套标准化的录屏服务 SDK。接口抽象与状态机管理定义统一的IScreenRecorder接口规范包含requestPermission()、startRecording()、stopRecording()等核心契约。在封装层内部实现严格的状态机如 Idle - Requesting - Recording - Saving防止因用户快速连续点击导致的底层状态异常。统一数据模型与路径回传各平台录屏结束后的文件路径获取方式不同。在封装层内部应定义跨平台统一的RecordingResult模型包含 filePath、duration、fileSize并在底层完成路径转换彻底屏蔽 Android 的 ContentResolver URI 与 iOS 的沙盒绝对路径差异。1. 接口抽象与状态机管理// screen_recorder_provider.dart enum RecordState { idle, requesting, recording, saving } class RecordingResult { final String filePath; final Duration duration; RecordingResult({required this.filePath, required this.duration}); } abstract class IScreenRecorder { Futurebool requestPermission(); Futurevoid startRecording(); FutureRecordingResult? stopRecording(); RecordState get currentState; }2. 业务层封装与异常中断处理// screen_recorder_service.dart class ScreenRecorderService { final IScreenRecorder _recorder; ScreenRecorderService(this._recorder); Futurevoid start() async { if (_recorder.currentState ! RecordState.idle) return; final granted await _recorder.requestPermission(); if (!granted) throw Exception(用户拒绝了录屏权限); await _recorder.startRecording(); } FutureRecordingResult? stop() async { if (_recorder.currentState ! RecordState.recording) return null; try { return await _recorder.stopRecording(); } catch (e) { // 异常中断时释放底层资源防止文件损坏 print(录屏异常中断: $e); return null; } } }九、 性能优化与合规避坑指南DRM 黑屏保护机制各平台底层均内置了版权保护机制。当录制包含 DRM 保护的内容如 Netflix、爱奇艺视频或密码输入框时系统会自动将该区域渲染为黑屏。开发者需在 UI 层给予用户明确的视觉反馈避免用户误认为录屏功能失效。鸿蒙 Picker 隐私模式在鸿蒙端开发时优先使用setPickerMode引导用户选择特定应用或窗口进行录制而非全屏录制。这不仅符合最新的隐私合规要求还能大幅降低编码器的性能开销。异常中断与资源释放录屏是高耗电、高内存占用的操作。跨平台封装层必须监听应用的生命周期与系统异常如来电中断、内存警告。在异常发生时必须强制调用底层的stop()与release()方法防止视频文件损坏未写入 moov atom或后台持续占用摄像头/麦克风导致其他应用无法调用。