Android O(API 27)中监听通话状态的BroadcastReceiver工作异常
这个问题我之前也踩过坑,核心原因就是Android 8.0(API 26)推出的后台执行限制,直接限制了静态注册广播接收器的能力。
问题根源
在Android N及更早版本,你可以通过AndroidManifest.xml静态注册广播接收器来监听ACTION_PHONE_STATE_CHANGED,并完整接收所有通话状态。但从Android O开始,系统对静态注册的广播接收器做了严格管控:除了少数豁免的系统广播外,大部分隐式广播无法再通过静态注册接收。
虽然ACTION_PHONE_STATE_CHANGED属于豁免列表,但当通话结束进入CALL_STATE_IDLE状态时,你的APP大概率已经退到后台,这时候静态注册的接收器就会被系统限制,无法接收到这个状态的广播——而呼出、呼入、CALL_STATE_OFFHOOK状态触发时,APP可能还处于前台,所以能正常接收。
解决方案:动态注册广播接收器
解决这个问题的关键是动态注册广播接收器,在APP处于活跃状态时注册,退出时注销,这样就能绕过静态注册的限制,完整接收所有通话状态。
步骤1:移除静态注册(如果有)
先删掉AndroidManifest.xml中对应的广播接收器声明,比如:
<!-- 删掉这段静态注册的代码 --> <receiver android:name=".PhoneStateReceiver"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> </receiver>
步骤2:动态注册接收器并处理权限
在你的Activity或Service中,动态注册接收器,同时别忘了处理READ_PHONE_STATE的动态权限(Android 6.0+必须)。下面是完整的Kotlin示例代码:
private lateinit var phoneStateReceiver: BroadcastReceiver private val PERMISSION_REQUEST_READ_PHONE_STATE = 1001 private fun startListening(context: Context) { // 先检查是否拥有读取电话状态的权限 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { // 动态申请权限 ActivityCompat.requestPermissions(context as Activity, arrayOf(Manifest.permission.READ_PHONE_STATE), PERMISSION_REQUEST_READ_PHONE_STATE) return } // 初始化广播接收器 phoneStateReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val state = intent?.getStringExtra(TelephonyManager.EXTRA_STATE) when (state) { TelephonyManager.EXTRA_STATE_IDLE -> { // 终于能接收到通话结束的状态了! println("通话结束:CALL_STATE_IDLE") } TelephonyManager.EXTRA_STATE_OFFHOOK -> { println("通话接通:CALL_STATE_OFFHOOK") } TelephonyManager.EXTRA_STATE_RINGING -> { println("有电话呼入:CALL_STATE_RINGING") } } } } // 注册接收器,监听电话状态变化 val filter = IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED) context.registerReceiver(phoneStateReceiver, filter) } // 在Activity销毁时注销接收器,避免内存泄漏 override fun onDestroy() { super.onDestroy() try { unregisterReceiver(phoneStateReceiver) } catch (e: IllegalArgumentException) { // 防止重复注销抛出异常 e.printStackTrace() } } // 处理权限申请结果 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_READ_PHONE_STATE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限申请成功,开始监听 startListening(this) } else { // 权限被拒绝,无法监听电话状态 println("需要授予读取电话状态权限才能正常工作") } } }
额外注意点
如果你的监听逻辑需要在后台持续运行,比如APP退到后台后仍要监听通话状态,那你需要把接收器注册在前台Service中——因为Android O+对后台Service有严格的限制,前台Service可以保持活跃状态,确保接收器能正常接收广播。
内容的提问来源于stack exchange,提问作者Jemo Mgebrishvili




