Android、联系人合约:存储和检索自定义数据

Android, Contacts Contract: store and retrieve custom data

为了了解 androids ContacsContract 的工作原理,我尝试向联系人添加自定义条目。一整天阅读文档、教程和观看 youtube 视频后,我仍然没有进一步完成这项任务。

我得到的最接近的是这份文件https://developer.android.com/reference/kotlin/android/provider/ContactsContract.Data 说明

For example, if you add a data row for "favorite song" to a raw contact owned by a Google account, it will not get synced to the server, because the Google sync adapter does not know how to handle this data kind. Thus new data kinds are typically introduced along with new account types, i.e. new sync adapters.

他们写了我正在尝试的东西,但不幸的是没有提供如何实现这个任务的解决方案。如果有人能提供一个 将最喜欢的歌曲的数据行添加到联系人并通过代码检索它的简单示例,那就太好了

__

我自己得到的:

获取基本联系信息的方法:

import android.content.ContentResolver
import android.database.Cursor
import android.provider.ContactsContract

fun fetchContacts(resolver: ContentResolver) : MutableList<ItemContact> {
    var cols = listOf<String>(
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER,
        ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
        ContactsContract.CommonDataKinds.Phone._ID,
    ).toTypedArray()
    var cursor : Cursor? = resolver.query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            cols, null, null,
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
    )
    var contactsList : MutableList<ItemContact> 
            = emptyList<ItemContact>().toMutableList()
    if (cursor != null && cursor.count > 0) {
        while(cursor.moveToNext()){
            contactsList.add(ItemContact(
                name = cursor.getString(0),
                number = cursor.getString(1),
                contact_id = cursor.getString(2),
            ))
        }
    }
    return contactsList
}

及其对应的数据class

data class ItemContact (
    val name: String,
    val number: String,
    val contact_id: String,
)

根据我目前的理解,我需要向给定的联系人添加一个代表我的应用程序的新 RawContact,并在创建它时将最喜欢的歌曲添加为单个数据条目。此 RawContact 应该能够连接到具有检索到的 contact_id 的联系人。然后我需要检查,代表我的应用程序的 RawContact 是否存在于联系人中,如果存在,我将能够检索存储的歌曲,否则我在 UI 中留下占位符文本,歌曲仍然需要被选中。不知何故,这涉及到自定义 Mimetype,但我仍然不确定这是什么以及如何创建一个。

首先,让我们回顾一下 ContactsContract DB 的组织方式:

  1. 联系人table - 每个联系人包含一行,但几乎没有任何信息
  2. RawContacts table - 可以有多行,每行分配给一个联系人 ID(来自之前的 table),包含多个数据行的逻辑组,通常用于单个 SyncProvider 这样的作为 Google.
  3. Data table - 包含 RawContact 的实际数据,每一行都有一个 MIMETYPE 列,说明该行的数据类型(phone、电子邮件、姓名等) + 15 个数据列来保存信息本身。

还有一些伪 table,例如您在代码中查询的 ContactsContract.CommonDataKinds.Phone,它基本上查询数据 table 但具有特定的 MIMETYPE 值,对于示例 Phone.CONTENT_ITEM_TYPE.

如果您想实现自己的 SyncProvider,您通常会创建自己的 RawContact 行,将其添加到现有的 Contact_ID 并使用新的 RAW_CONTACT_ID.

添加数据行

然后设备上的 People/Contacts 应用程序,当他们想要呈现有关某个联系人的数据时,将获取它的所有 RawContacts 的列表(包括你的),然后获取所有数据所有这些 RawContacts。

如果您只想向现有联系人添加一小段自定义数据,例如向联系人添加“最喜欢的歌曲”,则不必为此创建新的 RawContact,而是可以创建一个新的数据行并将其附加到具有自定义 MIMETYPE 的现有 RawContact,但请记住,您的项目不会同步到 Google 的服务器,但它仍然可以在本地设备上使用。

我假设您正在寻找第二个选项,所以这里有一个示例代码(未测试):

// you need to get a RawContact ID of the contact you want to add info to
fun addFavoriteSong(context: Context, rawContactId: Long) {
    val resolver = context.contentResolver
    val ops = ArrayList<ContentProviderOperation>()
    
    ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI, true)
                .withValue(Data.RAW_CONTACT_ID, rawContactId)
                .withValue(Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.example.favorite_song")
                .withValue(Data.DATA1, "Paranoid Android")
                .withValue(Data.DATA2, "Radiohead")
                .withValue(Data.DATA3, "OK Computer")
                .build())

    try {
        val results = resolver.applyBatch(ContactsContract.AUTHORITY, ops)
        if (results.isEmpty())
            return
    } catch (e: Exception) {
        e.printStackTrace()
    }

    Log.i("Songs Added", "success!");
}

然后查询该信息,以及其他信息,例如姓名和 phone:

fun fetchContacts(resolver: ContentResolver) : MutableCollection<ItemContact> {
    var cols = arrayOf(
        Data.CONTACT_ID,
        Data.MIMETYPE,
        Data.DISPLAY_NAME,
        Phone.NUMBER,
        Data.DATA1,
        Data.DATA2,
        Data.DATA3,
    )
    
    // get only rows of MIMETYPE phone and your new custom MIMETYPE
    var selection = Data.MIMETYPE + " IN (" + Phone.CONTENT_ITEM_TYPE + ", " + "vnd.android.cursor.item/vnd.com.example.favorite_song" + ")"

    var cursor : Cursor? = resolver.query(Data.CONTENT_URI, cols, null, null, Data.CONTACT_ID)
    val map = hashMapOf<Long, ItemContact>()
    
    while(cursor != null && cursor.moveToNext()) {
        val contactId = cursor.getLong(0)
        val mimetype = cursor.getString(1)

        // gets the existing ItemContact from the map or if not found, puts an empty one in the map
        val contact = map.getOrPut(contactId) { ItemContact() }

        with(contact) {
            if (mimetype == Phone.CONTENT_ITEM_TYPE) {
                contact_id = contactId
                name = cursor.getString(2)
                number = cursor.getString(3)
            } else {
                contact_id = contactId
                song = cursor.getString(4)
                band = cursor.getString(5)
                album = cursor.getString(6)
            }
        }
    }
    return map.values
}