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

Android联系人号码重复问题:原因分析与解决方法

Android联系人列表重复号码问题分析与解决

问题背景

开发Android应用时,需展示联系人列表及对应电话号码。通过ContentProvider分别获取联系人数据与所有号码,再通过contactId关联后,发现多个联系人存在重复号码(同一号码出现2-4次),添加测试联系人后该问题依然存在。

问题原因

  1. 多存储源重复条目:ContactsContract.CommonDataKinds.Phone会返回同一联系人来自不同存储介质(如Google账户、SIM卡、本地存储)的同号码记录——这些属于不同的RawContact,但会关联到同一个ContactId,导致查询时出现重复。
  2. 号码格式未归一化:同一号码可能存在不同格式(如带+86前缀、含空格/括号、纯数字),未统一格式就直接添加,会被识别为不同号码导致重复。
  3. 代码未做去重处理:当前retrieveAllContactNumbers方法直接将查询到的所有号码存入Map,未对同一contactId下的重复号码做去重逻辑。

解决办法

方案1:查询阶段去重

在查询号码时,通过DISTINCT关键字过滤重复条目,同时指定查询NORMALIZED_NUMBER字段(系统已归一化的号码),避免格式差异导致的重复:

fun Context.retrieveAllContactNumbers(): HashMap<String, ArrayList<String>> {
    val contactsNumberMap = HashMap<String, ArrayList<String>>()
    // 指定查询字段,只取需要的列,提升性能
    val projection = arrayOf(
        ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
        ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
    )
    val phoneCursor = contentResolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        projection,
        null,
        null,
        null,
        // 添加DISTINCT去重
        "DISTINCT"
    )
    phoneCursor?.use { cursor ->
        val contactIdIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID)
        val normalizedNumberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER)
        while (cursor.moveToNext()) {
            val contactId = cursor.getString(contactIdIndex)
            val number = cursor.getString(normalizedNumberIndex) ?: continue
            // 去重后添加
            if (contactsNumberMap.containsKey(contactId)) {
                val numbers = contactsNumberMap[contactId]
                if (!numbers?.contains(number)!!) {
                    numbers.add(number)
                }
            } else {
                contactsNumberMap[contactId] = arrayListOf(number)
            }
        }
    }
    return contactsNumberMap
}

方案2:关联阶段统一格式并去重

在关联联系人与号码时,先对号码做归一化处理,再添加到联系人列表前做去重:

contacts.forEach { contactData ->
    contactNumbers[contactData.contactId]?.let { numbers ->
        val uniqueNumbers = mutableSetOf<String>()
        numbers.forEach { rawNumber ->
            val normalizedNumber = PhoneNumberUtils.normalizeNumber(rawNumber)
            if (normalizedNumber != null && isValidIrisCellphoneNumber(normalizedNumber)) {
                uniqueNumbers.add(normalizedNumber)
            }
        }
        contactData.phoneNumbers.addAll(uniqueNumbers)
    }
}

方案3:优化联系人查询逻辑

可以合并联系人与号码的查询,一次性获取联系人基本信息和号码,减少IO操作同时避免重复关联:

fun Context.retrieveContactsWithNumbers(): List<ContactData> {
    val result = mutableListOf<ContactData>()
    val projection = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
        ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
    )
    contentResolver.query(
        ContactsContract.Data.CONTENT_URI,
        projection,
        "${ContactsContract.Data.MIMETYPE} = ?",
        arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
        "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} ASC"
    )?.use { cursor ->
        val contactIdIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID)
        val nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
        val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER)
        
        val contactMap = mutableMapOf<String, ContactData>()
        while (cursor.moveToNext()) {
            val contactId = cursor.getString(contactIdIndex)
            val name = cursor.getString(nameIndex) ?: ""
            val number = cursor.getString(numberIndex) ?: continue
            
            val contact = contactMap.getOrPut(contactId) {
                val firstName = name.split(" ").firstOrNull() ?: ""
                ContactData(contactId, firstName, getInitials(firstName, ""), mutableListOf(), null)
            }
            
            if (isValidIrisCellphoneNumber(number) && !contact.phoneNumbers.contains(number)) {
                contact.phoneNumbers.add(number)
            }
        }
        result.addAll(contactMap.values)
    }
    return result
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:01:25