2,拨号盘搜索联系人
拨号盘搜索联系人流程图如下,
拨号界面对应的fragment为DialpadFragment,并且DialpadFragment实现了TextWatcher接口,
afterTextChanged方法如下,
if (mDialpadQueryListener != null) {
mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
}
调用mDialpadQueryListener监听器mDialpadQueryListener的onDialpadQueryChanged方法,
DialtactsActivity的onDialpadQueryChanged方法主要逻辑如下,
1,首先调用SmartDialNameMatcher的normalizeNumber方法进行字符转化,其实还是数字,
final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
2,然后将匹配后的字符为mSearchView控件赋值,
if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
•••
mSearchView.setText(normalizedQuery);
在DialtactsActivity的onCreate方法中,为mSearchView添加了监听器,
mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
mPhoneSearchQueryTextListener是TextWatcher接口的匿名实现类。
当mSearchView的值发生变化时,就会调用mPhoneSearchQueryTextListener的onTextChanged方法,onTextChanged方法主要逻辑如下,
1,如果查询字符和上次的值相同,就直接返回,
if (newText.equals(mSearchQuery)) {
return;
}
2,将查询字符保存在mSearchQuery中,
mSearchQuery = newText;
3,如果当前是SmartDialSearchFragment界面,就调用其setQueryString方法开始查询;
否则调用RegularSearchFragment的setQueryString方法开始查询。
if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
} else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
}
实际只有父类ContactEntryListFragment实现了setQueryString方法,逻辑如下,
1,保存查询字符,并将模式设置为查询模式,
mQueryString = queryString;
setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
2,重新加载查询联系人数据库,
mAdapter.setQueryString(queryString);
reloadData();
reloadData方法会做一些设置,并且调用startLoading方法,最后会调用LoaderManager的initLoader方法就开始查询。
1.1 初始化
SmartDialSearchFragment的createListAdapter方法如下,
SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
adapter.setUseCallableUri(super.usesCallableUri());
adapter.setQuickContactEnabled(true);
// Set adapter's query string to restore previous instance state.
adapter.setQueryString(getQueryString());
return adapter;
因此,对应的adapter 为SmartDialNumberListAdapter。
onCreateLoader方法如下,
final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();
SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
adapter.configureLoader(loader);
return loader;
首先构造SmartDialCursorLoader对象,然后调用SmartDialNumberListAdapter的configureLoader方法进行设置。
对应的loader为SmartDialCursorLoader, SmartDialCursorLoader的configureLoader方法调用流程图如下,
configureLoader方法如下,
if (getQueryString() == null) { // 查询关键字为空
loader.configureQuery("");
mNameMatcher.setQuery("");
} else {
loader.configureQuery(getQueryString());
mNameMatcher.setQuery(PhoneNumberUtils.normalizeNumber(getQueryString()));
}
调用SmartDialCursorLoader的configureQuery方法进行转换。
mNameMatcher 是SmartDialNameMatcher变量,是在SmartDialNumberListAdapter方法中构造的。
SmartDialCursorLoader的configureQuery方法逻辑如下,
首先调用SmartDialNameMatcher的normalizeNumber进行转化,
然后利用mQuery构造SmartDialNameMatcher对象。
mQuery = SmartDialNameMatcher.normalizeNumber(query, SmartDialPrefix.getMap());
/** Constructs a name matcher object for matching names. */
mNameMatcher = new SmartDialNameMatcher(mQuery, SmartDialPrefix.getMap());
并且分别保存在mQuery 和 mNameMatcher变量中。
1.2 查询过程
查询过程主要看SmartDialCursorLoader,继承于AsyncTaskLoader,并且实现了loadInBackground/onCanceled/deliverResult等方法.其中最主要的是loadInBackground方法,这个方法当然是在子线程中执行的。调用流程图如下,
loadInBackground方法的主要逻辑如下,
1,构造DialerDatabaseHelper对象,然后调用其getLooseMatches方法查询结果,
final DialerDatabaseHelper dialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(
mContext);
final ArrayList<ContactNumber> allMatches = dialerDatabaseHelper.getLooseMatches(mQuery,
mNameMatcher);
2,将查询结果封装在MatrixCursor中, MatrixCursor其实也是继承于Cursor,
final MatrixCursor cursor = new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
Object[] row = new Object[PhoneQuery.PROJECTION_PRIMARY.length];
for (ContactNumber contact : allMatches) {
row[PhoneQuery.PHONE_ID] = contact.dataId;
row[PhoneQuery.PHONE_NUMBER] = contact.phoneNumber;
•••
cursor.addRow(row);
}
return cursor;
DialerDatabaseHelper的getLooseMatches方法逻辑如下,
1,对dialer.db 数据库的smartdial_table和prefix_table进行查询,
final Cursor cursor = db.rawQuery("SELECT " +
SmartDialDbColumns.DATA_ID + ", " +
SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " +
SmartDialDbColumns.PHOTO_ID + ", " +
SmartDialDbColumns.NUMBER + ", " +
SmartDialDbColumns.CONTACT_ID + ", " +
SmartDialDbColumns.LOOKUP_KEY +
" FROM " + Tables.SMARTDIAL_TABLE + " WHERE " +
SmartDialDbColumns.CONTACT_ID + " IN " +
" (SELECT " + PrefixColumns.CONTACT_ID +
" FROM " + Tables.PREFIX_TABLE +
" WHERE " + Tables.PREFIX_TABLE + "." + PrefixColumns.PREFIX +
" LIKE '" + looseQuery + "')" +
" ORDER BY " + SmartDialSortingOrder.SORT_ORDER,
new String[] {currentTimeStamp});
此次查询分为2个步骤,首先获取prefix_table表中prefix字段和数字匹配的对应的contact_id字段,然后从smartdial_table表中获取对应contact_id字段的Cursor对象。
例如,在拨号盘按下1或者2,都可以搜到联系人为abc的相关信息, prefix_table表 和 smartdial_table表 是通过contact_id唯一对应起来的。如下,
2,利用查询的Cursor对象逐个获取联系人信息,
while ((cursor.moveToNext()) && (counter < MAX_ENTRIES)) {
final long dataID = cursor.getLong(columnDataId);
final String displayName = cursor.getString(columnDisplayNamePrimary);
•••
MAX_ENTRIES 为20,定义如下,
private static final int MAX_ENTRIES = 20;
也就是说,最多搜多出20条匹配结果。
3,构造ContactMatch对象,封装匹配的联系人信息,
final ContactMatch contactMatch = new ContactMatch(lookupKey, id);
if (duplicates.contains(contactMatch)) {
continue;
}
对于联系人来说, lookupKey是唯一的,但是一个联系人可以有多个号码,并且多个号码都可以匹配。如果去掉上面的代码,联系人的每个匹配信息都可以当做单独的联系人显示,否则的话,只显示第一个匹配的号码。
4,首先检查姓名和号码匹配的联系人,然后封装为ContactNumber对象,最后保存在result中, result是ArrayList类型,也是最后返回的结果,
final boolean nameMatches = nameMatcher.matches(displayName);
final boolean numberMatches =
(nameMatcher.matchesNumber(phoneNumber, query) != null);
if (nameMatches || numberMatches) {
/** If a contact has not been added, add it to the result and the hash set.*/
duplicates.add(contactMatch);
result.add(new ContactNumber(id, dataID, displayName, phoneNumber, lookupKey,
photoId));
counter++; // 记录匹配的联系人数目
•••
关键是SmartDialNameMatcher的matches 和matchesNumber方法,这2个方法还可以仔细分析。
RegularSearchFragment查询的是contacts2.db数据库,并且通过PhoneNumberListAdapter的configureLoader方法设置Uri等查询信息。onCreateLoader是调用父类ContactEntryListFragment的方法创建的匿名CursorLoader进行查询的,并且数据库也是调用系统的标准数据库。
这个和SmartDialNumberListAdapter完全不同。