一. 简介
在Android 中获取联系人信息通过ContentProvider 来实现,联系人的Provider 提供了大量的API 和相关接口,来方便我们获取联系人信息。
联系人信息的表结构(来自官网):

这三个表通常以其协定类的名称命名。这些类定义表所使用的Content 的 URI、列名称及列值相应的常量:
-
ContactsContract.Contacts 表
表示不同联系人的行,基于聚合的原始联系人行。 -
ContactsContract.RawContacts 表
包含联系人数据摘要的行,针对特定用户帐户和类型。 -
ContactsContract.Data 表
包含原始联系人详细信息(例如电子邮件地址或电话号码)的行。
由 ContactsContract 中的协定类表示的其他表是辅助表,联系人提供程序利用它们来管理其操作,或为设备的联系人或电话应用中的特定功能提供支持。
二. 原始联系人
一个原始联系人表示来自某一帐户类型和账户名称、有关某个联系人的数据,由于联系人的Provider 允许将多个在线服务作为某一联系人的数据源,因此她允许同一联系人对应多个原始联系人,借助支持多个联系人的特性,用户还可以将某一联系人在帐户类型相同的多个帐户中的数据进行合并。
原始联系人的大部分数据并不存储在 ContactsContract.RawContacts 表内,而是存储在 ContactsContract.Data 表中的一行或多行内。每个数据行都有一个 Data.RAW_CONTACT_ID 列,其中包含其父级 ContactsContract.RawContacts 行的 RawContacts._ID 值。
重要的原始联系人列
列名称 | 用途 | 说明 |
---|---|---|
ACCOUNT_NAME | 作为该原始联系人来源的帐户类型的帐户名称。 例如,Google 帐户的帐户名称是设备所有者的某个 Gmail 地址。如需了解详细信息,请参阅有关ACCOUNT_TYPE 的下一条目。 | 此名称的格式专用于其帐户类型。它不一定是电子邮件地址。 |
ACCOUNT_TYPE | 作为该原始联系人来源的帐户类型。例如,Google 帐户的帐户类型是 com.google。 请务必使用您拥有或控制的域的域标识符限定您的帐户类型。 这可以确保您的帐户类型具有唯一性。 | 提供联系人数据的帐户类型通常关联有同步适配器,用于与联系人提供程序进行同步。 |
DELETED | 原始联系人的“已删除”标志。 | 此标志让联系人提供程序能够在内部保留该行,直至同步适配器能够从服务器删除该行,然后再从存储区中最终删除该行。 |
说明
以下是关于 ContactsContract.RawContacts 表的重要说明:
-
原始联系人的姓名并不存储其在 ContactsContract.RawContacts 中的行内,而是存储在 ContactsContract.Data 表的ContactsContract.CommonDataKinds.StructuredName 行内。一个原始联系人在 ContactsContract.Data 表中只有一个该类型的行。
-
注意:要想在原始联系人行中使用自己的帐户数据,必须先在 AccountManager 中注册帐户。 为此,请提示用户将帐户类型及其帐户名称添加到帐户列表。 如果不这样做,联系人提供程序将自动删除我们的原始联系人行。
例如,如果想让应用为域名是 com.example.dataservice、基于网络的服务保留联系人数据,并且我们的服务的用户帐户是 becky.smart@dataservice.example.com,则用户必须先添加帐户“类型”(com.example.dataservice) 和帐户“名称”(becky.smart@dataservice.example.com),然后应用才能添加原始联系人行。
三. 联系人
联系人Provider 通过将所有帐户类型和帐户名称的原始联系人行合并来形成联系人。 这可以为显示和修改用户针对某一联系人收集的所有数据提供便利。 联系人提供程序管理新联系人行的创建,以及原始联系人与现有联系人行的合并。 系统不允许应用或同步适配器添加联系人,并且联系人行中的某些列是只读列。
如果添加的新原始联系人不匹配任何现有联系人,联系人provider 会相应地创建新联系人。 如果某个现有原始联系人的数据发生了变化,不再匹配其之前关联的联系人,则provider 也会执行此操作。 如果应用或同步适配器创建的新原始联系人的确匹配某位现有联系人,则新原始联系人将与现有联系人合并。
联系人提供程序通过 Contacts 表中联系人行的 _ID 列将联系人行与其各原始联系人行链接起来。 原始联系人表 ContactsContract.RawContacts 的 CONTACT_ID 列包含对应于每个原始联系人行所关联联系人行的 _ID 值。
ContactsContract.Contacts 表 还有一个 LOOKUP_KEY 列,它是一个指向联系人行的“永久性”链接。 由于联系人提供程序会自动维护联系人,因此可能会在合并或同步时相应地更改联系人行的 _ID 值。 即使发生这种情况,合并了联系人 LOOKUP_KEY 的内容 URI CONTENT_LOOKUP_URI 仍将指向联系人行,这样,您就能使用 LOOKUP_KEY 保持指向“最喜爱”联系人的链接,以及执行其他操作。 该列具有其自己的格式,与 _ID 列的格式无关。
三个表的关系如下图:

四. 读写联系人
1. 获取联系人信息
先从Contacts 表中获取联系人的contact_id,再根据contact_id 来查询联系人的详细信息
private fun loadContact() {
val callback = object : LoaderManager.LoaderCallbacks<Cursor> {
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val projection = arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)
return CursorLoader(this@ContactTestActivity, ContactsContract.Contacts.CONTENT_URI, projection, null, null, null)
}
override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) {
data?.let {
while (it.moveToNext()) {
val contactId = it.getString(it.getColumnIndex(ContactsContract.Contacts._ID)) //contact id
val name = it.getString(it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) //联系人姓名
val uri = it.getString(it.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)) //头像缩略图uri
val phoneCursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null, null)
val number = StringBuilder()
while (phoneCursor.moveToNext()) {
number.append(phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))) //电话号码
number.append("\n")
}
phoneCursor.close()
dataList.add(ContactListAdapter.ListData(uri, name, number.toString()))
}
data.close()
adapter.setData(dataList)
}
}
override fun onLoaderReset(loader: Loader<Cursor>?) {
}
}
loaderManager.initLoader(233, null, callback)
}
联系人查询是一个数据库操作,可能会耗时,所以上例使用Loader 来实现。
2. 新建联系人
1)可以使用Intent 跳转到新建联系人页新建
val intent = Intent(Intents.Insert.ACTION).apply { //跳到新建联系人页的action
type = ContactsContract.RawContacts.CONTENT_TYPE
putExtra(Intents.Insert.NAME, name) //姓名
putExtra(Intents.Insert.EMAIL, email) //邮件
putExtra(Intents.Insert.EMAIL_TYPE, ContactsContract.CommonDataKinds.Email.TYPE_OTHER) //邮件类型
putExtra(Intents.Insert.PHONE, tel) //电话
putExtra(Intents.Insert.PHONE_TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_HOME) //电话类型
}
startActivity(intent)
2)直接向数据库插入
private fun addContactDirect() {
val name = contact_name.text.toString()
val tel = contact_tel.text.toString()
val email = contact_email.text.toString()
val address = contact_address.text.toString()
// 创建一个空的ContentValues
val values = ContentValues()
// 向RawContacts.CONTENT_URI空值插入,
// 先获取Android系统返回的rawContactId
// 后面要基于此id插入值
val rawContactUri = contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values)
val rawContactId = ContentUris.parseId(rawContactUri)
values.clear()
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
// 内容类型
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
// 联系人名字
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name)
// 向联系人URI添加联系人名字
contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
values.clear()
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
// 联系人的电话号码
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, tel)
// 电话类型
values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
// 向联系人电话号码URI添加电话号码
contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
values.clear()
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
// 联系人的Email地址
values.put(ContactsContract.CommonDataKinds.Email.DATA, email)
// 电子邮件的类型
values.put(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
// 向联系人Email URI添加Email数据
contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
ToastCompat.makeTextCompat(this, "创建联系人成功", Toast.LENGTH_SHORT).show()
}
网友评论