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

WearOS应用后台调用AudioRecord.startRecording()报错问题排查

WearOS后台AudioRecord.startRecording()失败问题

我正在开发一款WearOS应用,用于录制周围音频并计算分贝值,使用android.media.AudioRecord类实现音频录制。应用在前台运行时一切正常,但切换到后台后,startRecording()函数会返回错误,不过应用并未崩溃。应用首次启动时已获取AUDIO_RECORD权限,请问可能是什么原因?

附带代码

audioRunnable = object : Runnable {
            override fun run() {
                Log.i("Test", "Inside audio thread.")

                if (::soundMeter.isInitialized) {
                    soundMeter.startRecording()
                    Thread.sleep(4000)//wait time for soundMeter to get value
                    soundLevel = soundMeter.getDecibelValue()//get soundMeter value
                    Log.d(TAG, "dB2 update: $soundLevel")
                    soundMeter.stopRecording()//stop soundMeter

                    runOnUiThread { // UI code goes here
                        val floatTextView = findViewById<TextView>(R.id.soundValue)
                        floatTextView.text = soundLevel.roundToInt().toString()

                        when {
                            soundLevel > 60 -> {
                                floatTextView.setTextColor(Color.RED)
                            } else -> {
                                floatTextView.setTextColor(Color.GREEN)
                            }
                        }
                    }
                }

                audioHandler.postDelayed(this, 10000)//repeat every 10 seconds
            }
        }


    fun startRecording() { //needs to have permission check before call, which is done in MainActivity
        Log.i("AudioRecord", "Recording about to start.")

        if (Objects.nonNull(audioRecord) && audioRecord.state != AudioRecord.STATE_UNINITIALIZED)
            audioRecord.startRecording()

        Log.i("AudioRecord", "Recording completed.")
    }

    fun stopRecording() { //needs to be called after 10 second collecting period to get most recent max value
        if (Objects.nonNull(audioRecord) && audioRecord.state != AudioRecord.STATE_UNINITIALIZED)
            audioRecord.stop()
    }

    fun getDecibelValue(): Float { //returns the max amplitude in decibels
        return if (Objects.nonNull(audioRecord) && audioRecord.state != AudioRecord.STATE_UNINITIALIZED) {
            val buffer = ShortArray(bufferSize)
            audioRecord.read(buffer, 0, bufferSize)
            var maxAmplitude = 0 //max amplitude in the buffer
            for (i in buffer.indices) { //finds the max amplitude in the buffer by iterating through the buffer
                maxAmplitude = maxAmplitude.coerceAtLeast(kotlin.math.abs(buffer[i].toInt()))
            }

            20 * log10(maxAmplitude.toDouble()).toFloat() //math to be changed for calibration(to nearest whole number)

        } else {
            0f
        }
    }

原因分析及解决方案

1. WearOS后台麦克风访问限制(核心原因)

WearOS遵循Android的隐私与功耗规则,从Android 12开始,后台应用即使持有RECORD_AUDIO权限,也无法直接启动录音。穿戴设备的后台限制更严格,目的是保护隐私和节省电量。

2. 未使用前台服务

当前代码仅通过Handler在后台线程执行录音,但未将录音逻辑绑定到前台服务。Android要求需要持续访问麦克风的后台操作必须运行在前台服务中,且必须显示通知告知用户应用正在使用麦克风。

3. 线程生命周期不稳定

应用切后台后,audioHandler所属的主线程可能被系统挂起,导致录音线程无法正常执行,甚至AudioRecord实例被回收进入不可用状态。


具体修复步骤

1. 迁移录音逻辑到前台服务

创建前台服务处理录音,启动时调用startForeground()显示通知:

class AudioRecordService : Service() {
    private lateinit var soundMeter: SoundMeter
    private lateinit var audioHandler: Handler
    private lateinit var audioRunnable: Runnable

    override fun onCreate() {
        super.onCreate()
        soundMeter = SoundMeter()
        audioHandler = Handler(Looper.getMainLooper())
        audioRunnable = object : Runnable {
            override fun run() {
                soundMeter.startRecording()
                Thread.sleep(4000)
                val soundLevel = soundMeter.getDecibelValue()
                Log.d("AudioService", "dB update: $soundLevel")
                soundMeter.stopRecording()
                // 可通过广播/LiveData将分贝值同步到UI
                audioHandler.postDelayed(this, 10000)
            }
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 创建前台通知
        val notification = NotificationCompat.Builder(this, "RECORD_CHANNEL")
            .setContentTitle("音频监测中")
            .setContentText("正在计算周围分贝值")
            .setSmallIcon(R.drawable.ic_mic)
            .build()
        // 启动前台服务,指定服务类型为麦克风
        startForeground(1, notification)
        audioHandler.post(audioRunnable)
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        audioHandler.removeCallbacks(audioRunnable)
        soundMeter.stopRecording()
    }

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

同时在Manifest中注册服务并声明必要权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

<service
    android:name=".AudioRecordService"
    android:foregroundServiceType="microphone" />

2. 增加错误日志定位问题

修改startRecording()方法,捕获startRecording()的返回值,通过错误码精准定位问题:

fun startRecording() {
    Log.i("AudioRecord", "Recording about to start.")
    if (audioRecord != null && audioRecord.state != AudioRecord.STATE_UNINITIALIZED) {
        val result = audioRecord.startRecording()
        if (result != AudioRecord.SUCCESS) {
            Log.e("AudioRecord", "Start recording failed, error code: $result")
        }
    }
    Log.i("AudioRecord", "Recording completed.")
}

3. 优化线程逻辑

避免使用Thread.sleep(4000)阻塞线程,改用Handler延迟读取数据,减少线程被系统回收的概率。


内容的提问来源于stack exchange,提问作者Sumit Dhungana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:27:48