Android非过时来电检测方案:获取来电名称、号码及头像(支持静音)
Android 新版来电检测及信息获取实现方案
核心思路
采用Android 10(API 29)引入的TelecomManager.CallCallback实现来电监听,替代过时的PhoneStateListener,同时通过联系人ContentProvider查询获取名称与头像,全程不受静音模式影响。
步骤实现
1. 权限申请
需要动态申请以下权限(适配Android 13+):
READ_PHONE_STATE:获取来电状态与号码READ_CONTACTS:查询联系人名称与头像POST_NOTIFICATIONS(Android 13+):若需通过通知展示来电信息
权限申请代码示例:
private val REQUIRED_PERMISSIONS = arrayOf( Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CONTACTS, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) Manifest.permission.POST_NOTIFICATIONS else "" ).filterNot { it.isEmpty() }.toTypedArray() // 在Activity/Fragment中检查并申请权限 if (ContextCompat.checkSelfPermission(this, REQUIRED_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, 1001) }
2. 注册来电监听
通过TelecomManager添加CallCallback,在来电响铃时触发逻辑:
private lateinit var telecomManager: TelecomManager private lateinit var callCallback: TelecomManager.CallCallback override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager callCallback = object : TelecomManager.CallCallback() { override fun onCallAdded(call: TelecomManager.Call) { super.onCallAdded(call) val phoneNumber = call.details.handle?.schemeSpecificPart val callState = call.details.state // 仅处理响铃状态的来电 if (callState == TelecomManager.CallDetails.STATE_RINGING && phoneNumber != null) { getContactInfo(phoneNumber) { displayName, avatarUri -> // 此处获取到最终显示名称、头像URI、号码 runOnUiThread { // 执行UI更新或自定义来电界面逻辑 } } } } override fun onCallRemoved(call: TelecomManager.Call) { super.onCallRemoved(call) // 通话结束后清理资源 } } } override fun onResume() { super.onResume() telecomManager.addCallListener(callCallback) } override fun onPause() { super.onPause() telecomManager.removeCallListener(callCallback) }
3. 获取联系人信息(名称、头像)
通过ContentProvider查询联系人,同时处理SPAM标识:
private fun getContactInfo(phoneNumber: String, callback: (String, Uri?) -> Unit) { CoroutineScope(Dispatchers.IO).launch { var displayName: String? = null var avatarUri: Uri? = null // 查询联系人数据 val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.PHOTO_URI ) val selection = "${ContactsContract.CommonDataKinds.Phone.NUMBER} LIKE ?" val selectionArgs = arrayOf("%$phoneNumber%") // 模糊匹配适配号码格式差异 contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, selection, selectionArgs, null )?.use { cursor -> if (cursor.moveToFirst()) { displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) avatarUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI))?.let { Uri.parse(it) } } } // 处理SPAM标识:系统标记的垃圾来电会在callerDisplayName中包含SPAM字样 val callDetails = telecomManager.calls.firstOrNull { it.details.handle?.schemeSpecificPart == phoneNumber }?.details val isSpam = callDetails?.callerDisplayName?.contains("SPAM", ignoreCase = true) == true // 最终显示名称:优先取联系人名称,无则用号码,SPAM来电追加标记 val finalDisplayName = when { isSpam -> "SPAM - ${displayName ?: phoneNumber}" else -> displayName ?: phoneNumber } withContext(Dispatchers.Main) { callback(finalDisplayName, avatarUri) } } }
关键说明
- 适配新版Android:
TelecomManager.CallCallback是官方推荐的非过时API,兼容Android 10及以上版本。 - 静音模式兼容:监听基于系统Telecom服务,不受设备音频模式影响,静音状态下仍能正常触发来电回调。
- 号码格式兼容:采用模糊匹配查询联系人,适配不同格式的号码存储(如带区号、空格)。
- SPAM标识处理:直接读取系统返回的来电显示名称,自动识别垃圾来电标记。
内容的提问来源于stack exchange,提问作者Lnight
相关产品推荐
相关产品推荐

