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

Android API 31+ 耳机媒体按键检测方案求助

Android API 31+ 耳机媒体按键可靠接收方案

针对API31+下无法稳定接收耳机媒体按键(尤其是后台/锁屏场景)的问题,先梳理现有方案的核心缺陷,再给出官方标准的可行方案:

现有方案的问题分析

  1. MediaSession配置缺失关键参数:仅激活MediaSession但未请求音频焦点、未设置活跃播放状态,系统不会将媒体按键事件分发给非活跃的媒体会话。
  2. 静态注册BroadcastReceiver失效:API26+(Oreo)后,MEDIA_BUTTON这类隐式广播被禁止静态注册(系统例外广播除外),清单中注册的接收器无法收到事件。
  3. 动态注册BroadcastReceiver稳定性差:APP进入后台后,动态注册的接收器会被系统自动注销,且无音频焦点时系统不会分发事件。
  4. onKeyDown仅适用于前台Activity:该方法仅在Activity获得焦点时生效,锁屏、后台场景完全失效。

官方标准可行方案:前台Service+MediaSession(推荐)

要在后台/锁屏下稳定接收按键,必须让音频会话处于系统认可的活跃状态,同时用前台Service避免被系统回收。以下是完整实现步骤:

1. 创建前台Service托管MediaSession

Service是后台运行的组件,配合前台状态可绕过API26+的后台限制,同时托管MediaSession确保会话持续活跃。

class RecorderMediaService : Service() {
    private lateinit var mediaSession: MediaSession
    private lateinit var audioManager: AudioManager
    private val audioFocusRequest: AudioFocusRequest by lazy {
        // 请求录制音频的焦点,匹配MediaRecorder场景
        AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_RECORD)
            .setOnAudioFocusChangeListener { focusChange ->
                // 处理焦点变化,比如失去焦点时停止录制
                when (focusChange) {
                    AudioManager.AUDIOFOCUS_LOSS -> {
                        // 执行停止录制、释放资源逻辑
                    }
                }
            }
            .build()
    }

    override fun onCreate() {
        super.onCreate()
        audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
        // 先请求音频焦点,获得焦点后再初始化MediaSession
        if (audioManager.requestAudioFocus(audioFocusRequest) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            initMediaSession()
            // 启动前台Service,避免被系统回收
            startForeground(
                1001,
                Notification.Builder(this, "Recorder_Channel")
                    .setContentTitle("音频录制中")
                    .setSmallIcon(R.drawable.ic_recording)
                    .build()
            )
        }
    }

    private fun initMediaSession() {
        mediaSession = MediaSession(this, "RecorderMediaSession")
        mediaSession.setCallback(object : MediaSession.Callback() {
            override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
                val keyEvent = mediaButtonIntent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
                keyEvent?.let {
                    // 处理耳机按键:覆盖媒体按键和通话按键(KEYCODE_HEADSETHOOK)
                    when (it.keyCode) {
                        KeyEvent.KEYCODE_HEADSETHOOK,
                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
                            Log.d("MediaKey", "耳机按键触发,执行录制/暂停")
                            // 添加你的录制控制逻辑
                            return true // 消费事件,避免传递给其他应用
                        }
                        KeyEvent.KEYCODE_MEDIA_NEXT,
                        KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
                            // 处理其他按键需求
                            return true
                        }
                    }
                }
                return super.onMediaButtonEvent(mediaButtonIntent)
            }
        })
        // 设置播放状态为活跃(STATE_PLAYING),告知系统当前会话处于工作状态
        mediaSession.setPlaybackState(
            PlaybackState.Builder()
                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f)
                .build()
        )
        mediaSession.isActive = true
    }

    override fun onDestroy() {
        super.onDestroy()
        // 释放资源
        mediaSession.release()
        audioManager.abandonAudioFocusRequest(audioFocusRequest)
    }

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

2. 清单注册Service

添加必要权限和前台服务类型,适配API31+要求:

<manifest ...>
    <!-- 录制音频权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!-- 前台服务权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

    <application ...>
        <service
            android:name=".RecorderMediaService"
            android:foregroundServiceType="microphone" />
    </application>
</manifest>

3. 启动Service

在需要开始录制的场景(如Activity按钮点击)启动Service:

// 检查录制权限后启动Service
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
    startService(Intent(this, RecorderMediaService::class.java))
}

关键说明

  • 音频焦点:必须请求与场景匹配的音频焦点(录制用AUDIOFOCUS_RECORD),系统只会将媒体按键事件分发给拥有焦点的应用。
  • 前台Service:API26+后后台Service会被系统限制,前台Service可保证组件持续运行,即使锁屏或APP切换到后台。
  • MediaSession状态:设置STATE_PLAYING是核心,系统只会向活跃的媒体会话分发按键事件。
  • 按键覆盖:部分耳机的通话按键对应KEYCODE_HEADSETHOOK,需与媒体按键一同处理。

兼容方案:MediaSessionCompat

若需兼容旧版本API,可改用MediaSessionCompat,核心逻辑一致,调整初始化代码即可:

private lateinit var mediaSessionCompat: MediaSessionCompat

private fun initMediaSessionCompat() {
    mediaSessionCompat = MediaSessionCompat(this, "RecorderMediaSession")
    mediaSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS)
    mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
        override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
            // 按键处理逻辑与上述一致
            return true
        }
    })
    mediaSessionCompat.isActive = true
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:45:39