再谈Contacts中姓氏多音字排序错误问题

时间:2021-07-05 20:52:57

说到中国人的名字,那就不得不考虑多音字的问题,比如'单',在作为姓氏时应该读作'shan'而不是'dan'.但是在Contacts程序中却使用的是'D'来作为bucket label!
这是为什么?如何解决这种多音字姓氏的问题?

从4.3版本开始,HanziToPinyin.java(ContactsProvider中)改为直接调用ICU的Transliterator来对汉字进行transliterate(详见类中mPinyinTransliterator变量的使用).我们知道,ICU中的Han_Latin_Names.txt中保存了一些汉字在作为姓氏时的读音,如果在创建Transliterator对象时使用Han-Latin/Names作为id(可参考mPinyinTransliterator变量的初始化),将可以正确的获得汉字作为姓氏时的读音(仅限于Han_Latin_Names.txt中包含的汉字).也就是说,HanziToPinyin这个类在处理作为姓氏的多音字时是可以获得正确获得其读音的.如在使用HanziToPinyin获取'单田芳'的读音为'shan tian fang'.就算这样姓名为'单田芳'的联系人依然被排在'D'下面!这是为什么呢?
通过解析代码可以发现,联系人排在哪个字母下面是由raw_contacts表中的phonebook_label的值来决定的,而数据库中phonebook_label的值确实是'D',主要是因为在获得phonebook_label时使用的是AlphabeticIndex$ImmutableIndex的getBucketLabel函数,而这个函数是没有对姓氏多音字做任何特殊处理的,所以最后得到的是'D'(phonebook_label的处理流程见附一).

目前,如果想比较省力的解决这个问题,可以在获取phonebook_label时避免通过使用AlphabeticIndex$ImmutableIndex的getBucketLabel函数来获取,而是改为用HanziToPinyin去获得sortKeyPrimary的第一个字符的拼音(sortKeyAlternative暂不考虑).

贴一下代码修改(基于4.4.4_r2)
-----------------------------------------------------------------------------
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -78,6 +78,7 @@ import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
 
 import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.providers.contacts.HanziToPinyin.Token;
 import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
 import com.android.providers.contacts.database.ContactsTableUtil;
 import com.android.providers.contacts.database.DeletedContactsTableUtil;
@@ -85,6 +86,7 @@ import com.android.providers.contacts.database.MoreDatabaseUtils;
 import com.android.providers.contacts.util.NeededForTesting;
 import com.google.android.collect.Sets;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Set;
@@ -5388,8 +5390,25 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
         ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance();
 
         if (sortKeyPrimary != null) {
-            phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary);
-            phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary);
+            boolean flag = false;
+            if (displayNameStyle == FullNameStyle.CHINESE
+                    && HanziToPinyin.getInstance().hasChineseTransliterator()) {
+                String target = sortKeyPrimary.substring(0, 1);
+                ArrayList<Token> tokens = HanziToPinyin.getInstance().get(target);
+                if (tokens != null && tokens.size() > 0) {
+                    char label = tokens.get(0).target.charAt(0);
+                    if (label >= 'A' && label <= 'Z') {
+                        phonebookLabelPrimary = String.valueOf(label);
+                        phonebookBucketPrimary = localeUtils.getBucketIndex(phonebookLabelPrimary);
+                        flag = true;
+                    }
+                }
+            }
+
+            if (!flag) {
+                phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary);
+                phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary);
+            }
         }
         if (sortKeyAlternative != null) {
             phonebookBucketAlternative = localeUtils.getBucketIndex(sortKeyAlternative);

-----------------------------------------------------------------------------
通过上面的分析可以发现一个问题,那就是在Contacts主页面用'dan tian fang'搜索联系人时是搜不到的,只能用'shan
tian
fang'来搜索,但是在显示时确是将该联系人放在了'D'分组下面!根本原因就是NameLookupBuilder在调用
appendNameShorthandLookup时实际上使用了HanziToPinyin来获取读音,这时得到的是作为姓氏时的读音,而
phonebook_label是使用AlphabeticIndex$ImmutableIndex的getBucketLabel函数来获取的,而它
没有对姓氏多音字做任何特殊处理,这就导致了前面说的问题.
另外,用HanziToPinyin的一个问题是所有的可作为姓氏的汉字的读音都是姓氏的读音,如'单位单'的拼音是'shan wei shan'.如何解决这个问题呢?其实现有条件下没有一个好的办法(加词库的方法除外),只能尽量规避.我的方法是中文情况下名字中的第一个汉字作为姓氏去获取读音(使用Han-Latin/Names; Latin-Ascii; Any-Upper创建的Transliterator对象),而其它汉字则作为普通汉字去获取读音(使用Han-Latin; Latin-Ascii; Any-Upper创建的Transliterator对象,需在HanziToPinyin中再新建一个Transliterator对象).

附一
raw_contacts表中的phonebook_label的获取流程:
raw_contacts表中的phonebook_label是通过调用ContactsDatabaseHelper的updateRawContactDisplayName函数来完成插入/更新的.使用的是phonebookLabelPrimary变量的值.而phonebookLabelPrimary是ContactLocaleUtils中getBucketLabel获得的,getBucketLabel中实际调用的是ICU中ImmutableIndex的getBucketLabel来完成的().