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

Android Content Provider查询手机号无法匹配带区号号码的解决方法

解决通讯录号码格式不匹配的问题

问题核心在于通讯录中存储的号码格式多样(带+号、空格、分隔符等),而输入的是纯数字号码,直接用原始字段匹配会失败。通过号码规范化统一格式后再匹配,是最有效的解决思路,以下分版本提供具体实现:


方案1:API 21及以上(推荐)

利用系统提供的Phone.NORMALIZED_NUMBER字段(系统预先规范化的E.164标准号码),直接匹配输入的规范化号码:

修改ContactRepositoryImpl.kt代码:

class ContactRepositoryImpl @Inject constructor(
    @ApplicationContext private val context: Context
) : ContactRepository {

    override suspend fun getContacts(mobileNumbers: List<String?>): Result<List<Contact>> {
        val contactList = mutableListOf<Contact>()
        val validNumbers = mobileNumbers.filterNotNull().filter { it.isNotBlank() }
        if (validNumbers.isEmpty()) return Result.success(emptyList())

        return runCatching {
            val contentResolver = context.contentResolver
            // 获取设备默认国家码(用于号码规范化,如孟加拉国为BD)
            val countryIso = getDefaultCountryIso()

            // 将输入号码转换为E.164标准格式
            val normalizedInputNumbers = validNumbers.mapNotNull { rawNumber ->
                PhoneNumberUtils.formatNumberToE164(rawNumber, countryIso)
            }
            if (normalizedInputNumbers.isEmpty()) return@runCatching emptyList()

            val uri = Phone.CONTENT_URI
            val projection = arrayOf(
                Phone.NUMBER,
                Phone.DISPLAY_NAME,
                Phone.PHOTO_URI,
                Phone.NORMALIZED_NUMBER // 加入系统规范化号码字段
            )
            // 用规范化号码作为查询条件
            val selection = "${Phone.NORMALIZED_NUMBER} IN (${normalizedInputNumbers.joinToString(",") { "?" }})"
            val selectionArgs = normalizedInputNumbers.toTypedArray()

            val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)

            cursor?.use {
                val phoneNumberIndex = it.getColumnIndex(Phone.NUMBER)
                val nameIndex = it.getColumnIndex(Phone.DISPLAY_NAME)
                val photoUriIndex = it.getColumnIndex(Phone.PHOTO_URI)

                while (it.moveToNext()) {
                    val number = it.getString(phoneNumberIndex) ?: ""
                    val name = it.getString(nameIndex) ?: ""
                    val photoUri = it.getString(photoUriIndex)

                    contactList.add(
                        Contact(
                            name = name,
                            number = number,
                            photoUri = photoUri
                        )
                    )
                }
            }

            contactList
        }
    }

    // 获取设备默认国家码
    private fun getDefaultCountryIso(): String {
        val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        return telephonyManager.networkCountryIso.uppercase()
    }
}

方案2:兼容API 21以下

如果需要支持更低版本,可先查询所有候选号码,再用PhoneNumberUtils.compare()在内存中判断匹配(该方法自动忽略格式、前缀差异):

class ContactRepositoryImpl @Inject constructor(
    @ApplicationContext private val context: Context
) : ContactRepository {

    override suspend fun getContacts(mobileNumbers: List<String?>): Result<List<Contact>> {
        val contactList = mutableListOf<Contact>()
        val validNumbers = mobileNumbers.filterNotNull().filter { it.isNotBlank() }
        if (validNumbers.isEmpty()) return Result.success(emptyList())

        return runCatching {
            val contentResolver = context.contentResolver

            val uri = Phone.CONTENT_URI
            val projection = arrayOf(
                Phone.NUMBER,
                Phone.DISPLAY_NAME,
                Phone.PHOTO_URI
            )
            // 先过滤出包含输入号码数字的记录,减少内存处理量
            val cleanInputNumbers = validNumbers.map { it.replace(Regex("\\D"), "") }.joinToString("%")
            val selection = "${Phone.NUMBER} LIKE ?"
            val selectionArgs = arrayOf("%$cleanInputNumbers%")

            val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)

            cursor?.use {
                val phoneNumberIndex = it.getColumnIndex(Phone.NUMBER)
                val nameIndex = it.getColumnIndex(Phone.DISPLAY_NAME)
                val photoUriIndex = it.getColumnIndex(Phone.PHOTO_URI)

                while (it.moveToNext()) {
                    val contactNumber = it.getString(phoneNumberIndex) ?: ""
                    val name = it.getString(nameIndex) ?: ""
                    val photoUri = it.getString(photoUriIndex)

                    // 判断当前通讯录号码是否匹配任意输入号码
                    validNumbers.any { inputNumber ->
                        PhoneNumberUtils.compare(inputNumber, contactNumber)
                    }.takeIf { it }?.let {
                        contactList.add(
                            Contact(
                                name = name,
                                number = contactNumber,
                                photoUri = photoUri
                            )
                        )
                    }
                }
            }

            // 去重(同一联系人可能有多个匹配号码)
            contactList.distinctBy { it.number }
        }
    }
}

关键说明

  • PhoneNumberUtils.formatNumberToE164():将号码转换为标准E.164格式(如+8801866985338),需传入对应国家码确保转换准确。
  • Phone.NORMALIZED_NUMBER:API 21+专属字段,系统自动维护的规范化号码,避免手动处理格式差异。
  • PhoneNumberUtils.compare():跨版本兼容,自动忽略格式、区号、分隔符等差异,判断两个号码是否指向同一联系人。
  • 权限要求:确保AndroidManifest.xml中已添加<uses-permission android:name="android.permission.READ_CONTACTS"/>,并在运行时动态申请权限。

内容的提问来源于stack exchange,提问作者Aminul Haque Aome

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:21:06