如何在Android设备的Flutter应用中合规录制内部音频?
Android非Root实现Flutter内部音频录制方案
核心可行性说明
Android 10(API 29)及以上版本提供了MediaProjection API,这是官方支持的非Root录屏/内部音频录制方案,无需Root权限,仅需用户通过系统弹窗授权。Android 9及以下版本无官方非Root实现途径,只能引导用户升级系统或使用系统自带录制功能。
Flutter实现方式
1. 自定义原生代码(可控性强,推荐)
通过Flutter的MethodChannel与Android原生层通信,调用MediaProjection API实现录制。
Flutter侧代码
import 'package:flutter/services.dart'; class InternalAudioRecorder { static const MethodChannel _channel = MethodChannel('internal_audio_recorder'); // 请求MediaProjection授权 static Future<bool> requestPermission() async { try { return await _channel.invokeMethod('requestPermission'); } on PlatformException catch (_) { return false; } } // 开始录制(传入输出文件路径) static Future<void> startRecording(String outputPath) async { try { await _channel.invokeMethod('startRecording', {'path': outputPath}); } on PlatformException catch (_) { rethrow; } } // 停止录制 static Future<void> stopRecording() async { try { await _channel.invokeMethod('stopRecording'); } on PlatformException catch (_) { rethrow; } } }
Android原生侧代码(Kotlin)
import android.content.Intent import android.media.MediaRecorder import android.media.projection.MediaProjectionManager import android.os.Build import android.os.Environment import android.widget.Toast import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import java.io.File class MainActivity : FlutterActivity() { private val CHANNEL = "internal_audio_recorder" private var mediaProjectionManager: MediaProjectionManager? = null private var mediaProjection: android.media.projection.MediaProjection? = null private var mediaRecorder: MediaRecorder? = null private val REQUEST_CODE = 1000 override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "requestPermission" -> { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { Toast.makeText(this, "仅支持Android 10及以上版本", Toast.LENGTH_SHORT).show() result.success(false) return@setMethodCallHandler } mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager val intent = mediaProjectionManager?.createScreenCaptureIntent() startActivityForResult(intent, REQUEST_CODE) result.success(true) } "startRecording" -> { val path = call.argument<String>("path") ?: "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)}/internal_audio.mp4" startRecording(path) result.success(null) } "stopRecording" -> { stopRecording() result.success(null) } else -> result.notImplemented() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE) { if (resultCode == RESULT_OK && data != null) { mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, data) } else { Toast.makeText(this, "授权失败,无法录制音频", Toast.LENGTH_SHORT).show() } } } private fun startRecording(outputPath: String) { mediaRecorder = MediaRecorder().apply { // 配置录制参数(必须包含视频源,因为MediaProjection绑定录屏逻辑) setVideoSource(MediaRecorder.VideoSource.SURFACE) setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setVideoEncoder(MediaRecorder.VideoEncoder.H264) setOutputFile(outputPath) setVideoSize(1280, 720) // 任意分辨率,仅为满足录屏要求 setVideoFrameRate(30) try { prepare() } catch (e: Exception) { Toast.makeText(this@MainActivity, "录制初始化失败", Toast.LENGTH_SHORT).show() return } } // 创建虚拟显示,绑定MediaRecorder的Surface mediaProjection?.createVirtualDisplay( "AudioRecordingDisplay", 1280, 720, resources.displayMetrics.densityDpi, android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder?.surface, null, null ) mediaRecorder?.start() Toast.makeText(this, "开始录制", Toast.LENGTH_SHORT).show() } private fun stopRecording() { mediaRecorder?.run { stop() release() } mediaProjection?.stop() mediaRecorder = null mediaProjection = null Toast.makeText(this, "录制结束", Toast.LENGTH_SHORT).show() } }
AndroidManifest权限配置
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android 13+ 必需 -->
2. 第三方插件(快速实现)
可使用现成的Flutter插件如screen_recorder,这类插件已封装好MediaProjection逻辑,只需调用API即可。注意选择维护活跃、支持音频录制的版本,使用前需验证兼容性。
隐私合规与最佳实践
- 明确告知用户:在应用内清晰说明录制内部音频的用途,获取用户主动同意,避免误导。
- 系统授权:必须通过系统的MediaProjection授权弹窗获取权限,禁止绕过系统授权逻辑。
- 录制限制:仅录制用户明确需要的内容,禁止录制敏感信息(如通话、支付界面音频)。
- 文件处理:录制的音频文件仅允许用户本地保存或自行分享,未经用户同意不得上传至服务器。
- 前台服务:Android 10+要求录制过程中显示前台通知,告知用户当前正在录制,避免后台静默录制。
内容的提问来源于stack exchange,提问作者Faiz
相关产品推荐
相关产品推荐

