You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android通话录音功能代码实现及问题咨询(含示例代码)

Android通话录音功能完善方案

针对你现有代码的问题,结合Android系统规范和兼容性要求,以下是可实现双方通话录音的完整修正方案:

现有代码核心问题

  1. 音频源选择错误:使用MIC仅能录制本地麦克风声音,无法捕获对方通话音频
  2. 组件生命周期不匹配:BroadcastReceiver生命周期极短,执行完onReceive后进程可能被回收,导致录音中断
  3. 文件格式不统一:设置输出格式为THREE_GPP,但文件后缀用.mp3,导致文件无法正常播放
  4. 触发时机不合理:在RINGING(响铃)状态启动录音,此时通话未接通,无实际意义
  5. 存储适配缺失:Android 10+已废弃Environment.getExternalStoragePublicDirectory,未适配Scoped Storage规范
  6. 权限处理不完整:缺少权限请求结果回调,未处理用户拒绝权限的场景
  7. 资源释放错误:stopRecording中重复调用reset,未正确释放MediaRecorder资源

一、更新Manifest权限配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature
        android:name="android.hardware.telephony"
        android:required="false" />

    <!-- 基础录音权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!-- Android 9及以下存储权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    <!-- 监听电话状态 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!-- 监听去电 -->
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <!-- Android 12+ 监听电话状态补充权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CallRecording"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 监听通话状态的广播接收器 -->
        <receiver android:name=".CallReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
            </intent-filter>
        </receiver>

        <!-- 后台录音服务(Android 8.0+需前台服务) -->
        <service android:name=".CallRecordingService"
            android:foregroundServiceType="mediaProjection|microphone" />
    </application>
</manifest>

二、用Service替代BroadcastReceiver管理录音

CallReceiver.kt(仅负责触发服务)

class CallReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        context ?: return
        val action = intent?.action
        when (action) {
            // 通话接通状态
            TelephonyManager.EXTRA_STATE_OFFHOOK -> startRecordingService(context)
            // 通话结束状态
            TelephonyManager.EXTRA_STATE_IDLE -> stopRecordingService(context)
            // 发起去电时启动服务
            Intent.ACTION_NEW_OUTGOING_CALL -> {
                val serviceIntent = Intent(context, CallRecordingService::class.java).apply {
                    putExtra("PHONE_NUMBER", intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER))
                }
                startForegroundService(context, serviceIntent)
            }
        }
    }

    private fun startRecordingService(context: Context) {
        val serviceIntent = Intent(context, CallRecordingService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(serviceIntent)
        } else {
            context.startService(serviceIntent)
        }
    }

    private fun stopRecordingService(context: Context) {
        context.stopService(Intent(context, CallRecordingService::class.java))
    }
}

CallRecordingService.kt(核心录音逻辑)

class CallRecordingService : Service() {
    private var mediaRecorder: MediaRecorder? = null
    private var isRecording = false
    private val NOTIFICATION_ID = 1001

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onCreate() {
        super.onCreate()
        // 启动前台服务,避免被系统回收(Android 8.0+强制要求)
        startForeground(NOTIFICATION_ID, createRecordingNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (!isRecording) startRecording()
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        stopRecording()
    }

    private fun startRecording() {
        try {
            mediaRecorder = MediaRecorder().apply {
                // 适配通话场景的音频源,不同设备可能需调整
                setAudioSource(
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        MediaRecorder.AudioSource.VOICE_COMMUNICATION
                    } else {
                        MediaRecorder.AudioSource.VOICE_CALL
                    }
                )
                // 统一输出格式与文件后缀
                setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                setOutputFile(getRecordingFilePath())
                setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                setAudioSamplingRate(44100)
                setAudioEncodingBitRate(128000)
            }
            mediaRecorder?.prepare()
            mediaRecorder?.start()
            isRecording = true
        } catch (e: IOException) {
            Log.e("CallRec", "录音启动失败: ${e.message}")
            stopSelf()
        }
    }

    private fun stopRecording() {
        if (isRecording) {
            try {
                mediaRecorder?.stop()
            } catch (e: IllegalStateException) {
                Log.e("CallRec", "录音未启动,无法停止")
            } finally {
                mediaRecorder?.reset()
                mediaRecorder?.release()
                mediaRecorder = null
                isRecording = false
            }
        }
        stopForeground(STOP_FOREGROUND_REMOVE)
    }

    private fun getRecordingFilePath(): String {
        val fileName = "Call_${System.currentTimeMillis()}.mp4"
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 10+ 使用MediaStore适配Scoped Storage
            val contentValues = ContentValues().apply {
                put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
                put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp4")
                put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_MUSIC}/CallRecordings")
            }
            val uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
            uri?.let { contentResolver.openFileDescriptor(it, "w")?.use { fd ->
                return fd.fileDescriptor.toString()
            } } ?: throw IOException("无法创建录音文件")
        } else {
            // Android 9及以下使用传统存储路径
            val dir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), "CallRecordings")
            if (!dir.exists()) dir.mkdirs()
            File(dir, fileName).absolutePath
        }
    }

    private fun createRecordingNotification(): Notification {
        val channelId = "CallRecordingChannel"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "通话录音", NotificationManager.IMPORTANCE_LOW).apply {
                setSound(null, null)
            }
            (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
        }
        val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
        val pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE)
        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("正在录制通话")
            .setContentText("点击返回应用")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentIntent(pendingIntent)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()
    }
}

三、完善MainActivity权限处理

class MainActivity : AppCompatActivity() {
    private val PERMISSION_REQUEST_CODE = 123

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val makeCall: Button = findViewById(R.id.makeCall)
        makeCall.setOnClickListener {
            if (hasPermissions()) {
                startActivity(Intent(Intent.ACTION_DIAL))
            } else {
                requestPermissions()
            }
        }

        if (!hasPermissions()) requestPermissions()
    }

    private fun hasPermissions(): Boolean {
        val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.READ_PHONE_NUMBERS
            )
        } else {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_PHONE_STATE
            )
        }
        return permissions.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
    }

    private fun requestPermissions() {
        val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.READ_PHONE_NUMBERS
            )
        } else {
            arrayOf(
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_PHONE_STATE
            )
        }
        ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSION_REQUEST_CODE) {
            val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
            if (!allGranted) {
                Toast.makeText(this, "需授予所有权限才能使用通话录音功能", Toast.LENGTH_LONG).show()
            }
        }
    }
}

关键兼容性说明

  1. 音频源适配:不同厂商设备对音频源支持不同,VOICE_COMMUNICATION是官方推荐的通话场景源,若无法录制对方声音,可尝试VOICE_CALL(部分Android 10+设备可能限制此权限)
  2. 系统限制:小米、华为等厂商对通话录音有系统级限制,普通APP可能无法直接录制双方声音,需引导用户开启系统自带录音功能,或通过无障碍服务实现(需用户手动授权)
  3. 前台服务:Android 8.0+必须将录音服务设为前台服务,否则会被系统强制停止
  4. 存储规范:严格遵循Scoped Storage要求,避免使用已废弃的存储API

内容的提问来源于stack exchange,提问作者Hrithik Rajput

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:15:54