Android通讯录去重问题:带区号与无区号号码未被识别为重复
解决Android通讯录RecyclerView中带区号与不带区号号码的去重问题
嗨!新年快乐呀~ 你的问题我完全get到了:同一个联系人的号码,带+91区号和不带区号的版本被当成了两个独立条目,导致HashSet去重失效对吧?咱们一步步来修复这个问题。
先分析现有代码的核心问题
- equals方法逻辑错误:你用
==比较字符串(Java里字符串比较必须用equals()),而且条件写反了——应该是当两个联系人的号码和姓名匹配时返回true,而不是false;另外contains的判断逻辑也不合理,比如短号码可能被长号码包含,但其实不是同一个联系人。 - hashCode方法未标准化:你直接用原始号码的hashCode,带
+91和不带的hash值肯定不一样,HashSet自然会认为是不同对象。 - 号码未统一格式:查询到的原始号码格式不统一,没有提前处理成标准化的格式,这是问题的根源。
解决方案:标准化号码+修正equals/hashCode
第一步:编写号码标准化工具方法
把所有号码转换成统一格式(比如去掉所有非数字字符,再移除开头的91前缀),这样+919566191161和9566191161都会变成9566191161:
private String normalizePhoneNumber(String rawPhoneNumber) { // 去掉所有非数字字符(包括空格、+、括号、短横线等) String cleanedNumber = rawPhoneNumber.replaceAll("[^0-9]", ""); // 如果号码以91开头(对应+91的区号),移除前缀 if (cleanedNumber.startsWith("91")) { cleanedNumber = cleanedNumber.substring(2); } return cleanedNumber; }
第二步:修改ContactVO类,基于标准化号码实现equals和hashCode
在ContactVO中添加标准化号码的字段(推荐提前初始化,避免重复计算):
import java.util.Objects; public class ContactVO { private String imageUri; private String contactName; private String contactNumber; private String normalizedContactNumber; // 新增标准化号码字段 // 构造函数中初始化标准化号码 public ContactVO(String imageUri, String contactName, String contactNumber) { this.imageUri = imageUri; this.contactName = contactName; this.contactNumber = contactNumber; this.normalizedContactNumber = normalizePhoneNumber(contactNumber); } // 内部实现号码标准化 private String normalizePhoneNumber(String rawPhoneNumber) { String cleanedNumber = rawPhoneNumber.replaceAll("[^0-9]", ""); if (cleanedNumber.startsWith("91")) { cleanedNumber = cleanedNumber.substring(2); } return cleanedNumber; } // 重写equals方法,基于标准化号码和姓名判断 @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; ContactVO other = (ContactVO) obj; return Objects.equals(this.normalizedContactNumber, other.normalizedContactNumber) && Objects.equals(this.contactName, other.contactName); } // 重写hashCode方法,基于标准化号码和姓名生成 @Override public int hashCode() { return Objects.hash(normalizedContactNumber, contactName); } // 其他getter方法 public String getImageUri() { return imageUri; } public String getContactName() { return contactName; } public String getContactNumber() { return contactNumber; } }
第三步:修改联系人查询代码,直接使用标准化逻辑
在把号码存入ContactVO时,构造函数会自动处理标准化,无需额外操作:
Cursor phones = getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); while (phones.moveToNext()) { String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String rawPhone = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); String imageUri = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); // 直接创建ContactVO,构造函数自动处理号码标准化 contactVOList.add(new ContactVO(imageUri, name, rawPhone)); } // 去重逻辑保持不变 Set<ContactVO> contactSet = new HashSet<>(contactVOList); contactVOList = new ArrayList<>(contactSet); // 后续Adapter初始化代码不变 mAdapter = new AllContactsAdapter(getContext(), contactVOList, userId, mobilenumber); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext()); rvContacts.setLayoutManager(mLayoutManager); rvContacts.setItemAnimator(new DefaultItemAnimator()); rvContacts.setAdapter(mAdapter);
额外提示
如果你的应用需要支持其他国家区号,可以扩展标准化方法,比如根据用户所在地区自动识别并移除对应前缀,或者统一保留国际格式(比如所有号码都加上+91),只要保证同一个号码的不同格式最终转换成相同的字符串即可。
内容的提问来源于stack exchange,提问作者Kanagalingam




