Android联系人Provider中实现不区分重音的LIKE查询
嘿,这个坑我之前踩过!确实,Android里COLLATE LOCALIZED在=比较时能正常处理重音,但放到LIKE里就完全失效了——这其实是SQLite本身的限制,它的LIKE运算符不会应用排序规则的重音忽略逻辑,系统联系人应用之所以能做到,是用了专门的搜索URI或者预处理逻辑,给你两个靠谱的解决方案:
方案一:用系统内置的搜索URI(推荐)
Android的联系人ContentProvider提供了专门的过滤URI,已经内置了不区分重音、大小写的模糊搜索逻辑,和系统联系人应用的行为完全一致,用法超简单:
// 你的搜索关键词 String searchQuery = "jose"; // 比如要搜索带重音的José // 构造搜索URI,自动处理重音和模糊匹配 Uri searchUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(searchQuery) ); // 查询联系人,不需要自己写LIKE条件 Cursor cursor = getContentResolver().query( searchUri, new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY, ContactsContract.CommonDataKinds.Phone.NUMBER }, null, null, null );
这个方法的好处是不用自己处理各种语言的重音字符,系统已经帮你搞定了,而且效率比自定义查询高很多。
方案二:自定义归一化处理(适合特殊场景)
如果因为某些原因必须用自己的LIKE查询,那可以先对搜索关键词做重音归一化,把带重音的字符转换成普通字符,再进行查询。比如用Normalizer类把字符分解成基础字符+重音标记,然后去掉重音部分:
// 归一化搜索关键词的工具方法 private String normalizeSearchQuery(String query) { String normalized = Normalizer.normalize(query, Normalizer.Form.NFD); // 去掉所有重音标记(Unicode范围\\p{Mn}) return normalized.replaceAll("\\p{Mn}", "").toLowerCase(); }
然后查询的时候,你可以先把搜索词归一化,再用LIKE匹配,不过因为ContentProvider的查询语句里没法直接调用归一化函数,所以可以先查询所有符合基础LIKE条件的结果,再在内存里用归一化后的关键词二次过滤:
String rawQuery = "maria"; String normalizedQuery = normalizeSearchQuery(rawQuery); String selection = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY + " LIKE ?"; String[] selectionArgs = new String[]{"%" + rawQuery + "%"}; Cursor cursor = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY, ContactsContract.CommonDataKinds.Phone.NUMBER }, selection, selectionArgs, null ); // 内存里二次过滤,只保留归一化后匹配的结果 List<Contact> matchedContacts = new ArrayList<>(); if (cursor != null && cursor.moveToFirst()) { do { String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY)); String normalizedName = normalizeSearchQuery(name); if (normalizedName.contains(normalizedQuery)) { // 加入匹配结果 matchedContacts.add(new Contact(name, cursor.getString(1))); } } while (cursor.moveToNext()); cursor.close(); }
不过这个方法的缺点是如果联系人很多,先查询再过滤会影响性能,所以除非特殊需求,优先用方案一。
最后补充一下为什么COLLATE LOCALIZED在LIKE里无效:SQLite官方文档明确说明,LIKE运算符不会使用列的排序规则,而是默认按二进制比较,所以即使你加了COLLATE LOCALIZED,也不会对LIKE查询起作用,这是底层的限制,没法通过修改查询语句解决。
内容的提问来源于stack exchange,提问作者Petrakeas

