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
相关产品推荐
相关产品推荐

