产品资讯

在 Android 设备上添加和修改联系人的方式更可靠

阅读用时:6 分钟
查看 Roxanna Aliabadi Walker 的个人资料
Roxanna Aliabadi Walker Android 产品经理

具体变化

以即将发布的 Android 版本为目标平台的应用将无法再使用 WRITE_CONTACTS 权限直接写入联系人。您可以改用标准联系人 intent(ACTION_INSERT、ACTION_EDIT 和 ACTION_INSERT_OR_EDIT)。如需查看完整的参考实现,请参阅有关如何使用 intent 修改联系人的开发者指南。

在搭载即将发布的 Android 版本的设备上,这三个联系人 intent 的行为将保持一致,并且根本不需要任何权限。如果您的应用以即将发布的 Android 版本或更高版本为目标平台,并尝试直接写入通讯录,这些写入操作将被拒绝,并导致 SecurityException 或权限遭拒。对于需要广泛访问权限来创建和修改联系人的少数应用,Android 17 QPR2(2026 年第 4 季度)中将推出一项新的特殊应用访问权限:MANAGE_CONTACTS。Google Play 今天发布了更新后的通讯录访问权限指南,包括MANAGE_CONTACTS。

这样做的原因

目前,如果您的应用需要保存哪怕一个联系人,您可能也需要请求 WRITE_CONTACTS 权限。但该权限会授予广泛的访问权限,允许应用在后台对设备上的通讯录进行广泛更改,甚至无需用户互动。大多数应用都不需要如此宽泛的权限。

虽然有替代方案,但我们了解到,这些方案需要在整个生态系统中更加标准化。以前,联系人 intent 在不同设备上的行为有所不同。某些编辑器会丢弃您传入的数据,例如联系人照片。在某些设备上,返回按钮会在用户无意中保存联系人,或者“创建新联系人”选项很难找到。因此,请求权限是合理的选择。我们将在即将推出的 Android 版本中修复所有这些问题,并要求搭载该版本的设备遵循相同的标准。

替代方案

添加或修改联系人:使用联系人 intent

当您使用这些 intent(ACTION_INSERT、ACTION_EDIT 和 ACTION_INSERT_OR_EDIT)时,系统会处理繁重的工作,呈现简洁、标准化的联系人编辑器体验。它会打开一个快速半页表单,供用户输入新条目,或允许用户轻松搜索现有联系人并向其添加信息。用户可以顺畅地将其展开为全页编辑器,以填写更多详细信息,而您的应用完全不需要直接写入权限。请查看以下暂定用户体验模拟:

Figure01_01_Quick_Insert_Half_Sheet_Prefilled.png
屏幕截图 2 - 标题:创建新联系人的全屏模拟:Android 界面暂定设计的屏幕截图,显示了全屏联系人编辑器,其中包含头像选择器、姓名、电话和电子邮件字段。设计上标有“UX not Final”(用户体验尚未最终确定)。

图 1:创建新联系人  *用户体验并非最终版本*

Figure02_01_Add_To_Existing_0_Search_State_Half_Sheet.png
屏幕截图 2 - 标题:全屏模拟“向现有联系人添加信息”:Android 界面暂定设计的屏幕截图,显示了全屏联系人编辑器,其中包含头像选择器、姓名、电话和邮箱字段。全页设计采用渐进式披露条状标签来显示次要字段。设计上标有“UX not Final”(用户体验尚未最终确定)。

图 2:向现有联系人添加信息 *用户体验并非最终版本*


注意:不同的设备制造商可能会修改用户体验,使其与自己的操作系统风格保持一致,但行为将保持一致。


您可以在 intent 中传递可选数据载荷(称为 extra),从而进一步简化创建流程:

  • EXTRA_ACCOUNT 会建议将联系人保存到哪个账号,这在应用同步联系人时很有帮助。此 extra 之前已存在,但支持情况因设备而异。从即将发布的 Android 版本开始,所有设备都将采用标准化支持。
  • EXTRA_PHOTO_URI 会传递一张照片,用于预填充联系人照片。编辑器会为您处理 URI 读取权限。这是即将发布的 Android 版本中的新功能,将在整个生态系统中得到支持。

之前:直接使用 WRITE_CONTACTS 写入(一旦您的应用以即将发布的 Android 版本为目标平台,就会导致 SecurityException 或权限遭拒。)

 

// Requires android.permission.WRITE_CONTACTS
fun saveContactDirectly(context: Context, name: String, phone: String, account: Account) {
    val ops = arrayListOf(
        ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
            .build(),
        ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(
                ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
            )
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
            .build(),
        ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(
                ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
            )
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .build()
    )
    context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
}

 

 

之后:打开联系人编辑器(无需权限。)

 

fun saveContactWithEditor(
    context: Context,
    name: String,
    phone: String,
    account: Account,
    photoUri: Uri
) {
    val intent = Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI).apply {
        putExtra(ContactsContract.Intents.Insert.NAME, name)
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
        // Suggest which account the contact is saved to
        putExtra(ContactsContract.Intents.Insert.EXTRA_ACCOUNT, account)
        // New in the upcoming Android release: pre-fill the contact photo
        putExtra(ContactsContract.Intents.Insert.EXTRA_PHOTO_URI, photoUri)
        clipData = ClipData.newRawUri("", photoUri)
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    context.startActivity(intent)
}

 

之前:直接更新现有联系人

 

// Requires android.permission.WRITE_CONTACTS
fun addPhoneDirectly(context: Context, rawContactId: Long, phone: String) {
    val values = ContentValues().apply {
        put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
        put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
        put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
    }
    context.contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
}

之后:让用户选择要更新的联系人或创建新联系人

fun addPhoneWithEditor(context: Context, phone: String) {
    val intent = Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
        type = ContactsContract.Contacts.CONTENT_ITEM_TYPE
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
    }
    context.startActivity(intent)
}

如果您已经知道要更新哪个联系人,请将 ACTION_EDIT 与该联系人的 URI 搭配使用:

fun editKnownContact(context: Context, contactUri: Uri, phone: String) {
    val intent = Intent(Intent.ACTION_EDIT, contactUri).apply {
        putExtra(ContactsContract.Intents.Insert.PHONE, phone)
    }
    context.startActivity(intent)
}

 

选择联系人:使用联系人选择工具

如果您只需要读取用户选择的联系人,请使用联系人选择工具(自 Android 17 起提供)。它不需要任何权限。

通话应用和即时通讯应用:使用 People Directory

某些应用会向联系人添加“使用…通话”或“使用…发消息”操作,以便用户可以从“电话”或“通讯录”应用发起通话或聊天。如果您的应用目前通过在用户通讯录中写入额外条目来实现此功能,请改用人员目录,该功能将于 Android QPR2(2026 年第 4 季度)推出。这是一个新的系统目录,您的应用可以在其中发布用户如何在您的应用中相互联系,而无需修改用户的联系人。

应用迁移示例

为了让您了解这些替代方案在实践中的运作方式,下面列举了几个示例,说明不同类型的应用如何处理通讯录访问权限:

应用类型示例(并非详尽无遗)。推荐的迁移路径
社交、P2P 支付和拼车应用如果您的应用可让用户查找好友、分摊账单或分享行程状态,则根本不需要通讯录访问权限。使用联系人选择工具进行一次性联系人选择,并在用户点按个人资料中的“保存到通讯录”时启动 ACTION_INSERT(使用 EXTRA_PHOTO_URI)。
商家名片扫描仪和活动社交应用如果您的应用会扫描工牌或二维码并保存新联系人,或者会向现有同事的条目中添加工作电话号码,请使用 ACTION_INSERT 或 ACTION_INSERT_OR_EDIT。标准化的半页编辑器会预填充解析出的字段(姓名、公司、电话、邮箱和照片),以便用户一键验证并保存,而无需授予后台权限。
VoIP 通话和即时通讯应用请勿请求 WRITE_CONTACTS 将自定义的“使用…通话”或“使用…发消息”行注入用户的通讯簿,而是通过 People Directory 发布可达性元数据,并在用户从聊天会话中添加新联系人时使用 ACTION_INSERT。
企业 CRM 和云同步应用如果您的应用与云端或企业账号执行持续的双向后台联系人同步,而无法通过 intent 界面提示用户处理每条记录,则您的应用有资格声明 MANAGE_CONTACTS 

要检查的更改

  • 编辑器不支持自定义数据类型。如果您传递的 Insert.DATA 行包含标准 ContactsContract.CommonDataKinds 类型以外的 MIME 类型,编辑器会静默忽略这些 MIME 类型。
  • SHOW_OR_CREATE_CONTACT 不由系统编辑器处理。设备自带的通讯录应用仍可处理该 intent。如需在各种设备上实现一致的行为,请使用 ACTION_INSERT_OR_EDIT。
  • 默认情况下,写入操作会失败。当您以即将发布的 Android 版本为目标平台时,任何不使用 MANAGE_CONTACTS 的直接写入都会导致 SecurityException or permission denial。测试。

我需要MANAGE_CONTACTS吗?

一般不会。如果您的应用是应用户要求保存或修改联系人,则上述 intent 即可满足您的需求,无需任何权限,也无需在 Play 管理中心内声明。

MANAGE_CONTACTS 适用于核心用途是管理联系人且 intent 不足以满足需求的应用。例如:

您的应用…需要MANAGE_CONTACTS?
保存来自聊天、商家名片或订单的联系人。否,使用 ACTION_INSERT
将号码或邮箱添加到现有联系人。不,请使用 ACTION_INSERT_OR_EDIT 或 ACTION_EDIT
让用户选择要邀请、分享或付款的联系人。否,使用联系人选择工具
为联系人添加通话或消息操作。不,使用人员目录
拥有自己的品牌化联系人编辑界面。不,仅有自定义界面是不符合条件的
是专用的通讯录管理器或拨号器替代应用。可能
是一款来电显示和骚扰电话防范应用,可批量更新通讯簿条目。可能
在后台将联系人与企业账号或云账号同步。可能
执行批量联系人备份/恢复或设备间手机数据传输。可能
为各个联系人设置自定义铃声。可能

工作方式:

  • MANAGE_CONTACTS 是一项特殊应用访问权限。它不是运行时对话框。用户可在设置 > 特殊应用权限 > 管理联系人下开启此功能。
// Opens Settings > Apps > Special app access > Manage contacts
val intent = Intent(Settings.ACTION_MANAGE_CONTACTS, Uri.fromParts("package", packageName, null))
context.startActivity(intent)
  • 这包括读取、添加、修改和删除联系人、账号和人员目录数据。您也无需请求 READ_CONTACTS。
  • 如需在 Google Play 上发布使用 MANAGE_CONTACTS 的应用,您需要在 Play 管理中心内提交声明,说明为何 intent 不足以支持应用的核心功能。

迁移步骤

  • 查找联系人写入。在代码中搜索 WRITE_CONTACTS、ContactsContract.RawContacts.CONTENT_URI、ContactsContract.Data.CONTENT_URI 和 applyBatch(ContactsContract.AUTHORITY, …)。
  • 将每个用户驱动的写入操作替换为 intent: ACTION_INSERT(对于新联系人)以及 ACTION_EDIT 或 ACTION_INSERT_OR_EDIT(对于更新)。
  • 在 Insert.DATA 中仅使用标准数据类型。将通话或消息操作移至“人员目录”。
  • 根据上表决定您是否确实需要 MANAGE_CONTACTS。如果需要,请规划您的 Play 管理中心声明。
  • 当没有代码路径需要 WRITE_CONTACTS 时,请从清单中移除 该权限。

时间轴

时间结果
2026 年 10 月 21 日Google Play 宣布推出MANAGE_CONTACTS政策
2026 年(Android 17 季度平台版本 2)MANAGE_CONTACTS 权限变为可用
2027(即将发布的 Android 版本)WRITE_CONTACTS 不再向以即将发布的 Android 版本为目标平台的应用授予写入权限。联系人 intent 在各种设备上均已标准化
2027 年,不早于即将发布的 Android 版本Play 管理中心要求使用 MANAGE_CONTACTS 的应用提交声明
2027 年,不早于即将发布的 Android 版本必须遵守 Play 政策

常见问题解答

  • 这是否会影响所有应用?
    • WRITE_CONTACTS 弃用变更仅适用于以即将发布的 Android 版本及更高版本为目标平台的应用。以旧版本为目标平台的应用会保持其当前行为,但新的标准化联系人编辑器将适用于在新操作系统上运行的所有应用,无论其目标 SDK 是什么。
  • 我的应用能否通过 intent 删除联系人?
    • 没有。没有用于删除联系人的 intent。用户可在其通讯录应用中删除联系人。具有 MANAGE_CONTACTS 权限的应用可以直接删除联系人。
  • 我已有自己的联系人编辑界面。我可以保留它吗?
    • 仅当您的应用符合MANAGE_CONTACTS的条件时。想要保留自定义设计本身并不是一个符合条件的理由。
  • MANAGE_CONTACTS 是否会取代 READ_CONTACTS?
    • MANAGE_CONTACTS 包含读取权限,因此您无需同时请求这两项权限,但许多应用符合 READ_CONTACTS 的条件,根本不需要修改通讯录。这些应用可能会继续请求 READ_CONTACTS。
  • 使用旧版 Android 的用户会受到什么影响?
    • 联系人 intent 也适用于旧版,由设备的通讯录应用处理。大多数应用在使用这些 intent 时,在所有设备上都能正常运行。如果您的应用受到特定一致性问题的影响,而该问题只能在最新版本中解决,您可以在清单中保留 WRITE_CONTACTS 权限,并使用适当的 maxSdkVersion 限定其范围。
继续阅读