Telephony解析之Contacts

本文代码基于Android 9.0

Contacts概述

Contacts主要为Android系统提供了有效的联系依据,即:所有的联系人信息的展示、编辑和管理

Contacts架构

Contacts由三个组件构成:

contact01

1.Contacts应用主要负责用户和手机联系人的交互

contact02

2.ContactsProvider为Contacts等提供所有联系人数据的操作(增删改查)

3.contacts2.db数据库,用来存储所有的联系人数据

在联系人应用中的联系人数据加载使用了AsyncTaskLoader

Contacts解析

显示联系人

  • 相关类

packages/apps/Contacts/src/com/android/contacts/activities/PeopleActivity.java
packages/apps/Contacts/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
packages/apps/Contacts/src/com/android/contacts/list/DefaultContactListAdapter.java
packages/apps/Contacts/src/com/android/contacts/list/ContactEntryListFragment.java
packages/apps/Contacts/src/com/android/contacts/list/ContactEntryListAdapter.java
packages/apps/Contacts/src/com/android/contacts/list/DirectoryPartition.java
packages/apps/Contacts/src/com/android/contacts/list/DirectoryListLoader.java
packages/apps/Contacts/src/com/android/contacts/quickcontact/QuickContactActivity.java
packages/apps/Contacts/src/com/android/contacts/model/ContactLoader.java

联系人列表数据加载流程

contacts_list

contacts_list

打开Contacts根目录下的AndroidManifest.xml文件查看PeopleActivity属性如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<activity
android:name=".activities.PeopleActivity"
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
android:resizeableActivity="true"
android:theme="@style/LaunchScreenTheme"
android:visibleToInstantApps="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.APP_CONTACTS"/>
</intent-filter>

PeopleActivity是Contacts应用的LAUNCHER界面,因此打开联系人应用,首先加载PeopleActivity,在createViewsAndFragments()方法中创建联系人界面

setUpListFragment()方法中创建DefaultContactBrowseListFragment所有联系人的Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void setUpListFragment(FragmentManager fragmentManager) {
mContactsListFragment = (DefaultContactBrowseListFragment)
fragmentManager.findFragmentByTag(TAG_ALL);

//判断mContactsListFragment为空则创建Fragment
if (mContactsListFragment == null) {
mContactsListFragment = new DefaultContactBrowseListFragment();
mContactsListFragment.setAnimateOnLoad(true);
fragmentManager.beginTransaction()
.add(R.id.contacts_list_container, mContactsListFragment, TAG_ALL)
.commit();
fragmentManager.executePendingTransactions();
}

//对Fragment进行初始化的一系列设置
mContactsListFragment.setContactsAvailable(areContactsAvailable());
mContactsListFragment.setListType(mContactListFilterController.getFilterListType());
mContactsListFragment.setParameters(/* ContactsRequest */ mRequest,
/* fromOnNewIntent */ false);
}

创建所有联系人界面以后进行数据加载,数据加载使用DirectoryListLoader,该加载器继承自系统的AsyncTaskLoader加载器

DirectoryListLoader加载数据的Uri并非来自DirectoryPartition.mContentUri,而是直接从ContactContacts中的Directory类中获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private static final class DirectoryQuery {
//查询数据排序规则
public static final String ORDER_BY = Directory._ID;

//查询条件规则
public static final String[] PROJECTION = {
Directory._ID,
Directory.PACKAGE_NAME,
Directory.TYPE_RESOURCE_ID,
Directory.DISPLAY_NAME,
Directory.PHOTO_SUPPORT,
};

public static final int ID = 0;
public static final int PACKAGE_NAME = 1;
public static final int TYPE_RESOURCE_ID = 2;
public static final int DISPLAY_NAME = 3;
public static final int PHOTO_SUPPORT = 4;

//获取查询Uri,跟踪代码可知,Directory.ENTERPRISE_CONTENT_URI是Android 25版本之后才加入的
//ContactsUtils.java
//public static final boolean FLAG_N_FEATURE = Build.VERSION.SDK_INT >= 24;
public static Uri getDirectoryUri(int mode) {
if (mode == SEARCH_MODE_DATA_SHORTCUT || mode == SEARCH_MODE_CONTACT_SHORTCUT) {
return Directory.CONTENT_URI;
} else {
return DirectoryCompat.getContentUri();
}
}
}

DirectoryPartition类对联系人数据加载过程信息进行管理,联系人信息加载后在内存中的缓存的封装,内部提供了3种数据加载信息状态

1
2
3
public static final int STATUS_NOT_LOADED = 0; //还未加载数据
public static final int STATUS_LOADING = 1; //正在加载数据
public static final int STATUS_LOADED = 2; //数据已加载完毕

联系人详情页加载流程

contacts_detail

联系人详情的界面类是QuickContactActivity,一般情况下联系人详情是通过其他入口启动的,启动时必须通过Intent传递lookupUri到QuickContactActivity,否则将调用finish()关闭界面

联系人详情数据加载流程中的加载器ContactLoader和列表一样都继承自AsyncTaskLoader,可查看代码了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void processIntent(Intent intent) {
//这里各种判断中调用finish(),就是对加载联系人合法性的判断失败后自己关闭界面
......
//使用oldLookupUri,对加载过的联系人进行缓存,以便再次打开能更快加载
mLookupUri = lookupUri;
mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
if (oldLookupUri == null) {
// Should not log if only orientation changes.
mShouldLog = !mIsRecreatedInstance;
mContactLoader = (ContactLoader) getLoaderManager().initLoader(
LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
} else if (oldLookupUri != mLookupUri) {
// Should log when reload happens, regardless of orientation change.
mShouldLog = true;
// After copying a directory contact, the contact URI changes. Therefore,
// we need to reload the new contact.
destroyInteractionLoaders();
mContactLoader = (ContactLoader) (Loader<?>) getLoaderManager().getLoader(
LOADER_CONTACT_ID);
mContactLoader.setNewLookup(mLookupUri);
mCachedCp2DataCardModel = null;
}
//最后通过ContactLoader开启联系人数据加载
mContactLoader.forceLoad();
}

编辑联系人

  • 相关类

packages/apps/Contacts/src/com/android/contacts/activities/ContactEditorActivity.java
packages/apps/Contacts/src/com/android/contacts/editor/ContactEditorFragment.java
packages/apps/Contacts/src/com/android/contacts/ContactSaveService.java
packages/apps/Contacts/src/com/android/contacts/editor/RawContactEditorView.java
packages/apps/Contacts/src/com/android/contacts/model/RawContactDeltaList.java

编辑联系人界面从8.0开始修改为ContactEditorActivity,但同时兼容旧的联系人编辑界面CompactContactEditorActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- Edit or create a contact with only the most important fields displayed initially. -->
<activity
android:name=".activities.ContactEditorActivity"
android:theme="@style/EditorActivityTheme">

<intent-filter>
<action android:name="android.intent.action.INSERT"/>
<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="vnd.android.cursor.dir/person"/>
<data android:mimeType="vnd.android.cursor.dir/contact"/>
<data android:mimeType="vnd.android.cursor.dir/raw_contact"/>
</intent-filter>
</activity>

<!-- Keep support for apps that expect the Compact editor -->
<activity-alias
android:name="com.android.contacts.activities.CompactContactEditorActivity"
android:exported="true"
android:targetActivity=".activities.ContactEditorActivity">
<intent-filter android:priority="-1">
<action android:name="android.intent.action.INSERT"/>
<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="vnd.android.cursor.dir/person"/>
<data android:mimeType="vnd.android.cursor.dir/contact"/>
<data android:mimeType="vnd.android.cursor.dir/raw_contact"/>
</intent-filter>
</activity-alias>

一般情况下编辑联系人界面的启动分两种情况,existing_contact 和 new_contact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);

......

if (Intent.ACTION_EDIT.equals(action)) {
mActionBarTitleResId = R.string.contact_editor_title_existing_contact;
} else {
mActionBarTitleResId = R.string.contact_editor_title_new_contact;
}

......
}

ContactEditorActivity的onCreate方法中创建并调用了ContactEditorFragment,由该Fragment负责整个编辑界面的操作和维护,ContactEditorFragment中主要是用自定义ViewRawContactEditorView来展示和编辑联系人数据,从而形成了完整的用户交互

联系人数据编辑完成后的后续保存工作由ContactSaveService类来负责完成,需要注意的是ContactSaveService是一个IntentServer,用在此处恰到好处,当快速处理完Intent任务就会立马释放资源,具体妙处可查看IntentServer相关

搜索和选择联系人

  • 相关类

packages/apps/Contacts/src/com/android/contacts/activities/ActionBarAdapter.java
packages/apps/Contacts/src/com/android/contacts/list/ContactEntryListFragment.java
packages/providers/ContactsProvider/src/com/android/providers/contacts/NameNormalizer.java

搜索和选择联系人模式切换流程

搜索联系人和选择联系人都在ActionBarAdapter类中发起

ActionBarAdapter.Listener中定义了onAction()和onUpButtonPressed()方法进行搜索和选择联系人的监听

ActionBarAdapter.Listener.Action中定义了对联系人列表的一系列操作模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface Listener {
public abstract class Action {
//当搜文本索框文字发生改变,触发此Action,根据输入文字进行联系人查询筛选
public static final int CHANGE_SEARCH_QUERY = 0;
//开启搜索联系人模式,调整界面,显示搜索框和搜索界面,进入搜索模式
public static final int START_SEARCH_MODE = 1;
//开启选择联系人模式,调整界面,显示选择框组件,进入选择模式
public static final int START_SELECTION_MODE = 2;
//关闭搜索或选择模式,恢复界面到联系人列表
public static final int STOP_SEARCH_AND_SELECTION_MODE = 3;
//关闭搜索或选择模式,运行tab按钮动画
public static final int BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE = 4;
}

//发送启动模式Action,启动搜索或选择模式
void onAction(int action);

//返回按钮监听,取消搜索或选择模式
void onUpButtonPressed();
}

搜索联系人主要是CHANGE_SEARCH_QUERY动作发起,由代码调用跟到ContactEntryListFragment类中的setQueryString()方法,在此处设置全局查询关键词,并调用了reloadData()方法进行数据搜索

1
2
3
4
5
6
7
8
9
10
11
12
// TODO: the paramter delaySelection is not in use, and let's remove it.
public void setQueryString(String queryString, boolean delaySelection) {
if (!TextUtils.equals(mQueryString, queryString)) {
......
if (mAdapter != null) {
//设置搜索关键词
mAdapter.setQueryString(queryString);
//启动加载器开始加载联系人数据
reloadData();
}
}
}

搜索联系人ContactsProvider调用流程

加载器调用ContactsProvider搜索联系人调用使用了ContactsProvider2.query()方法,使用的Uri是Contacts.ENTERPRISE_CONTENT_FILTER_URI

创建联系人的时候在contacts2.db数据库中的name_lookup表中存储了normallized_name列,该列中的数据来源是NameNormalize.normalize()对displayname的转换

搜索联系人时主要通过normallized_name列数据进行模糊匹配,此方法中的转换算法和匹配算法可自行研究

1
2
3
4
5
6
7
8
/**
* Converts the supplied name to a string that can be used to perform approximate matching
* of names. It ignores non-letter, non-digit characters, and removes accents.
*/
public static String normalize(String name) {
CollationKey key = getCompressingCollator().getCollationKey(lettersAndDigitsOnly(name));
return Hex.encodeHex(key.toByteArray(), true);
}

SIM卡联系人操作

  • 相关类

frameworks/opt/telephony/src/java/com/android/internal/telephony/IccProvider.java
frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccController.java

SIM卡联系人主要存储在SIM卡内部存储文件,包括adn、fdn、sdn。主要提供简单的字段用于表示联系人数据。并通过IccProvider提供的接口进行数据的增加、删除、修改、查询操作。

SIM卡联系人数据操作可查看SIM卡联系人数据操作

SIM卡的初始化加载流程和卡状态变化监听可查看Android系统UICC库解析

SD卡联系人数据处理(VCard)

联系人应用提供了联系人导入/导出功能,此功能是将联系人数据转换成VCard格式数据保存到SD卡中,以方便联系人备份或移动

关于具体的联系人VCard操作部分可查看联系人备份之VCard

Google联系人同步

其他

分享联系人

联系人快捷方式

ContactsProvider解析

ContactsProvider概述

ContactsProvider是一个强大而又灵活的 Android 组件,用于管理设备上联系人相关数据的中央存储区。 ContactsProvider是我们在手机的Contacts应用中看到的数据源,我们也可以在自己的应用中访问其数据,并可在设备与在线服务之间传送数据。 ContactsProvider储存有多种数据源,由于它会试图为每个联系人管理尽可能多的数据,因此造成其组织结构非常复杂。 为此,ContactsProvider的 API 包含丰富的协定类和接口,为数据检索和修改提供便利。

关于ContactsProvider

contacts2.db数据结构

该数据库中存储了所有的联系人信息,共37张表,如下图所示:

contact03