Android 9.0+设备中BroadcastReceiver无法获取来电号码问题
这个问题我之前帮开发者排查过好几次,核心原因是Android 9(API 28)及以上版本的权限体系和电话状态监听机制发生了变化,导致你通过传统BroadcastReceiver拿不到来电号码。下面给你一步步拆解原因和解决方案:
问题根源
从Android 9开始,系统对来电号码的获取权限做了严格限制:
- API 28(Android 9):
READ_PHONE_STATE权限还能勉强拿到号码,但部分厂商的ROM已经提前收紧了权限; - API 29(Android 10)及以上:必须申请
READ_CALL_LOG权限才能获取来电号码,READ_PHONE_STATE不再提供这个能力; - Android 12(API 31)+:还要额外申请
READ_PHONE_NUMBERS权限。
另外,静态注册的BroadcastReceiver在高版本系统中,即使能触发,也可能因为权限优先级问题无法获取到Bundle里的号码数据。
解决方案
1. 先配置必要权限
在AndroidManifest.xml中添加权限声明:
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <!-- Android 12+ 需添加 --> <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
2. 动态申请危险权限
这些权限属于危险权限,必须在运行时向用户申请,不能只靠Manifest声明。示例代码:
private static final int PERMISSION_REQUEST_CODE = 101; private void requestCallPermissions() { String[] requiredPermissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { requiredPermissions = new String[]{ Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_PHONE_NUMBERS }; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { requiredPermissions = new String[]{Manifest.permission.READ_CALL_LOG}; } else { requiredPermissions = new String[]{Manifest.permission.READ_PHONE_STATE}; } // 检查权限是否已授予 boolean allGranted = true; for (String perm : requiredPermissions) { if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (!allGranted) { ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE); } }
3. 推荐:改用TelephonyCallback替代BroadcastReceiver
从Android 10开始,官方不再推荐用BroadcastReceiver监听电话状态,而是改用TelephonyCallback,它的可靠性更高,能直接拿到来电号码,不需要解析Bundle。示例代码:
private TelephonyManager telephonyManager; private TelephonyCallback callStateCallback; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); // 创建电话状态回调 callStateCallback = new TelephonyCallback() { @Override public void onCallStateChanged(int state, String phoneNumber) { super.onCallStateChanged(state, phoneNumber); switch (state) { case TelephonyManager.CALL_STATE_RINGING: // 这里直接获取到来电号码 Log.d("IncomingCall", "来电号码:" + phoneNumber); break; case TelephonyManager.CALL_STATE_OFFHOOK: // 电话接通 break; case TelephonyManager.CALL_STATE_IDLE: // 电话挂断或无通话 break; } } }; // 检查权限后注册回调 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { telephonyManager.registerTelephonyCallback(getMainExecutor(), callStateCallback); } else { telephonyManager.registerTelephonyCallback(callStateCallback); } } } @Override protected void onDestroy() { super.onDestroy(); // 记得注销回调,避免内存泄漏 if (callStateCallback != null && telephonyManager != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { telephonyManager.unregisterTelephonyCallback(callStateCallback); } else { telephonyManager.unregisterTelephonyCallback(callStateCallback); } } }
4. 如果坚持用BroadcastReceiver
如果一定要保留原有的BroadcastReceiver实现,除了确保权限已授予,建议改用动态注册的方式(静态注册在高版本系统中可能被限制):
private PhoneStateReceiver phoneStateReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); phoneStateReceiver = new PhoneStateReceiver(); // 动态注册广播 IntentFilter filter = new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED); registerReceiver(phoneStateReceiver, filter); } @Override protected void onDestroy() { super.onDestroy(); // 注销广播 unregisterReceiver(phoneStateReceiver); } // 自定义BroadcastReceiver class PhoneStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) { // 权限授予后就能拿到号码 String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); Log.d("IncomingCall", "来电号码:" + phoneNumber); } } }
额外注意事项
- 部分国产ROM会有额外的隐私限制,比如需要手动在应用权限设置中开启“读取通话记录”“读取电话状态”权限;
- 测试时尽量用真实设备的来电,模拟器的模拟来电可能不会传递真实号码;
- 确保权限申请成功后再进行监听,否则依然拿不到号码。
内容的提问来源于stack exchange,提问作者Het Thummar
相关产品推荐
相关产品推荐

