You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android ContactsContract存储与读取联系人自定义数据的实现方案咨询

自定义联系人收藏歌曲字段实现方案

1. 前置配置

权限声明

在AndroidManifest.xml中添加读写联系人权限:

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

Android 6.0及以上版本需要在运行时动态申请上述权限,获取用户授权后才可执行后续操作。

常量定义

首先定义全局唯一的自定义MIME类型和本地账户信息,避免和其他应用冲突:

// 自定义MIME类型,格式为vnd.android.cursor.item/[你的应用包名].[自定义字段名]
const val MIME_TYPE_FAVORITE_SONG = "vnd.android.cursor.item/com.yourpackage.favorite_song"
// 本地账户类型,无需同步可直接使用自定义值
const val LOCAL_ACCOUNT_TYPE = "com.yourpackage.local_account"
const val LOCAL_ACCOUNT_NAME = "App Local Account"

2. 给联系人添加收藏歌曲数据

使用ContentProviderOperation批量操作,创建归属当前应用的RawContact并写入自定义数据,自动关联到目标联系人:

import android.content.ContentProviderOperation
import android.content.ContentResolver
import android.provider.ContactsContract

fun addFavoriteSongToContact(resolver: ContentResolver, contactId: String, songName: String): Boolean {
    return try {
        val operations = arrayListOf<ContentProviderOperation>()

        // 第一步:插入属于当前应用的本地RawContact
        val rawContactOp = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, LOCAL_ACCOUNT_TYPE)
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME)
            .withValue(ContactsContract.RawContacts.AGGREGATION_MODE, ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
            .build()
        operations.add(rawContactOp)

        // 第二步:关联RawContact到目标聚合联系人
        val aggregateOp = ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
            .withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
            .withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, 0)
            .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, getRawContactIdByContactId(resolver, contactId))
            .build()
        operations.add(aggregateOp)

        // 第三步:插入自定义收藏歌曲数据
        val dataOp = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(ContactsContract.Data.MIMETYPE, MIME_TYPE_FAVORITE_SONG)
            .withValue(ContactsContract.Data.DATA1, songName)
            .build()
        operations.add(dataOp)

        resolver.applyBatch(ContactsContract.AUTHORITY, operations)
        true
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

// 辅助方法:根据contactId获取对应的原始RawContactId用于聚合
private fun getRawContactIdByContactId(resolver: ContentResolver, contactId: String): Long? {
    val cursor = resolver.query(
        ContactsContract.RawContacts.CONTENT_URI,
        arrayOf(ContactsContract.RawContacts._ID),
        "${ContactsContract.RawContacts.CONTACT_ID} = ?",
        arrayOf(contactId),
        null
    )
    return cursor?.use {
        if (it.moveToFirst()) it.getLong(0) else null
    }
}

3. 查询联系人关联的收藏歌曲

扩展数据类

修改原有ItemContact添加收藏歌曲字段:

data class ItemContact (
    val name: String,
    val number: String,
    val contact_id: String,
    val favoriteSong: String? = null
)

查询自定义数据方法

fun getFavoriteSongByContactId(resolver: ContentResolver, contactId: String): String? {
    val projection = arrayOf(ContactsContract.Data.DATA1)
    val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
    val selectionArgs = arrayOf(contactId, MIME_TYPE_FAVORITE_SONG)
    
    resolver.query(
        ContactsContract.Data.CONTENT_URI,
        projection,
        selection,
        selectionArgs,
        null
    )?.use { cursor ->
        if (cursor.moveToFirst()) {
            return cursor.getString(0)
        }
    }
    return null
}

改造原有联系人查询逻辑

在原有fetchContacts方法中,遍历联系人时调用上述查询方法填充favoriteSong字段即可:

fun fetchContacts(resolver: ContentResolver) : MutableList<ItemContact> {
    val cols = arrayOf(
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER,
        ContactsContract.CommonDataKinds.Phone.CONTACT_ID
    )
    val contactsList = mutableListOf<ItemContact>()
    resolver.query(
        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        cols, null, null,
        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
    )?.use { cursor ->
        while(cursor.moveToNext()){
            val contactId = cursor.getString(2)
            contactsList.add(ItemContact(
                name = cursor.getString(0),
                number = cursor.getString(1),
                contact_id = contactId,
                favoriteSong = getFavoriteSongByContactId(resolver, contactId)
            ))
        }
    }
    return contactsList
}

注意事项

  • 所有联系人相关操作需要放在子线程执行,避免阻塞主线程引发ANR
  • 如果需要修改已保存的收藏歌曲,直接查询到对应Data表的id后执行update操作即可
  • 不需要云端同步的场景下无需实现同步适配器,自定义数据仅保存在本地,其他应用无法访问
  • 卸载应用后,你创建的RawContact和对应的自定义数据会被系统自动清除

内容的提问来源于stack exchange,提问作者Jonathan Fenno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 04:15:03