Android开发:如何从其他应用更新通讯录App的用户头像与姓名
Android跨应用更新系统通讯录User Profile头像与姓名实现方案
前置权限要求
- 应用需要在清单文件声明
WRITE_CONTACTS权限,针对Android 6.0(API 23)及以上版本,必须动态向用户申请该危险权限,拿到授权后才可执行修改操作 - 无需申请已废弃的
WRITE_PROFILE权限,该权限从API 23开始仅系统应用可获取,第三方应用申请后也不会被授予
注意:仅设备主用户空间下的应用可修改系统User Profile,工作资料、访客空间、多用户场景下的子用户无操作权限,执行代码前可先判断当前用户是否为主用户避免报错。
具体实现步骤
- 第一步:查询User Profile对应的RawContact ID
系统User Profile不是普通联系人条目,必须使用专属URI查询,不能直接用普通联系人的查询地址:
val profileRawContactUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI val cursor = contentResolver.query( profileRawContactUri, arrayOf(ContactsContract.RawContacts._ID), null, null, null ) var targetRawContactId = -1L cursor?.use { if (it.moveToFirst()) { targetRawContactId = it.getLong(it.getColumnIndexOrThrow(ContactsContract.RawContacts._ID)) } }
- 第二步:初始化Profile条目(仅查询不到ID时需要)
部分设备出厂或首次开机时未初始化个人资料条目,直接写入数据会报错,需要先创建基础RawContact:
if (targetRawContactId == -1L) { val insertValues = ContentValues().apply { // 本地账户类型,不需要关联第三方账户 put(ContactsContract.RawContacts.ACCOUNT_TYPE, null) put(ContactsContract.RawContacts.ACCOUNT_NAME, null) put(ContactsContract.RawContacts.IS_USER_PROFILE, 1) } val insertResult = contentResolver.insert(profileRawContactUri, insertValues) targetRawContactId = ContentUris.parseId(insertResult!!) }
- 第三步:写入/更新用户姓名
姓名数据存储在联系人Data表中,对应结构化姓名的数据类型,写入前先判断是否已有存量数据,避免重复插入多条姓名记录:
val nameMimeType = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE val nameContentValues = ContentValues().apply { put(ContactsContract.Data.RAW_CONTACT_ID, targetRawContactId) put(ContactsContract.Data.MIMETYPE, nameMimeType) put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "你要设置的用户显示名") // 按需可拆分设置名、姓、拼音等字段 // put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "名") // put(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, "姓") } // 查询已有姓名条目 val nameQueryCursor = contentResolver.query( ContactsContract.Data.CONTENT_URI, arrayOf(ContactsContract.Data._ID), "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?", arrayOf(targetRawContactId.toString(), nameMimeType), null ) nameQueryCursor?.use { if (it.moveToFirst()) { // 已有条目执行更新 val existEntryId = it.getLong(it.getColumnIndexOrThrow(ContactsContract.Data._ID)) val updateUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, existEntryId) contentResolver.update(updateUri, nameContentValues, null, null) } else { // 无条目执行插入 contentResolver.insert(ContactsContract.Data.CONTENT_URI, nameContentValues) } }
- 第四步:写入/更新用户头像
头像同样存储在Data表中,注意传入的图片字节数组不要过大,建议先将图片裁剪为720*720分辨率、压缩到500KB以内,否则系统通讯录可能加载失败:
val photoMimeType = ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE // 将目标头像Bitmap压缩为JPEG格式字节数组 val byteArrayOutputStream = ByteArrayOutputStream() targetAvatarBitmap.compress(Bitmap.CompressFormat.JPEG, 85, byteArrayOutputStream) val photoByteArray = byteArrayOutputStream.toByteArray() val photoContentValues = ContentValues().apply { put(ContactsContract.Data.RAW_CONTACT_ID, targetRawContactId) put(ContactsContract.Data.MIMETYPE, photoMimeType) put(ContactsContract.CommonDataKinds.Photo.PHOTO, photoByteArray) } // 查询已有头像条目 val photoQueryCursor = contentResolver.query( ContactsContract.Data.CONTENT_URI, arrayOf(ContactsContract.Data._ID), "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?", arrayOf(targetRawContactId.toString(), photoMimeType), null ) photoQueryCursor?.use { if (it.moveToFirst()) { val existEntryId = it.getLong(it.getColumnIndexOrThrow(ContactsContract.Data._ID)) val updateUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, existEntryId) contentResolver.update(updateUri, photoContentValues, null, null) } else { contentResolver.insert(ContactsContract.Data.CONTENT_URI, photoContentValues) } }
常见问题说明
- 不要直接对
ContactsContract.Contacts层级的Profile URI执行写入操作,该地址是系统聚合生成的只读视图,直接写入会抛出权限异常 - 部分深度定制ROM会对User Profile修改做额外限制,写入完成后可以主动查询一次显示名和头像字段,验证写入结果,若写入失败可提示用户手动在系统通讯录中修改
- 写入完成后不需要主动发送广播刷新通讯录,系统会自动监听ContactsProvider的数据变化更新UI
- Android 13及以上版本、Android 14版本的细分联系人权限不影响该操作,即使用户只授予了应用部分联系人访问权限,依然可以正常修改User Profile条目
内容的提问来源于stack exchange,提问作者vinit
相关产品推荐
相关产品推荐

