Google Play 的联系人权限政策意味着,从 2027 年 1 月 27 日起,目标为 Android 17(API 级别 37)或更高版本的应用只能请求 READ_CONTACTS 前提是 Android Contact Picker 无法支持核心功能,并且必须通过 Play Console 声明说明理由。对于大多数通过读取联系人来邀请好友、分享文件或填写手机号的 Capacitor 应用,解决办法是改用 Contact Picker 并移除该权限。本指南会解释该政策,并演示如何借助 @capgo/capacitor-contacts,并说明如果确实需要完整通讯录时该怎么做。
政策内容
Google 于 2026 年 4 月 15 日发布了这项政策,作为一系列隐私更新的一部分。以下是 Play Console 帮助页面的核心要点:
- 适用范围: 针对 Android 17 (API 37) 或更高版本并请求
READ_CONTACTS. - 规则: 你只能在以下情况下请求权限:
READ_CONTACTS如果 Android Contact Picker 无法满足应用的核心功能需求。 - 声明: 必须保留该权限的应用需在 Play Console 中填写声明,说明哪些功能需要它,以及为什么 Picker 不够用。
- 时间线: Play Console 已开始提示声明该权限的应用
READ_CONTACTS2026 年 9 月生效。2027 年 1 月 27 日起强制合规。Google 在 Play Console 中提及可申请 30 天的自助延期。 - 豁免情况: 私有应用和企业设备管理应用不在范围内。
- 数据: 根据用户数据政策,联系人数据属于个人敏感信息。未经数据主体授权,您不得发布非公开的联系人数据。
可保留 READ_CONTACTS 权限的使用场景
Google 列出的核心功能包括:联系人管理应用、拨号器和短信应用、通话记录与来电筛选、CRM、辅助功能、个人助理、好友匹配、备份与恢复,以及键盘或自动补全。
应切换至选择器(Picker)的使用场景
邀请或推荐他人、分享文件、协作,或选择联系人以发送款项。如果您的应用读取联系人仅出于这些目的,声明将不会被接受。请使用选择器。
Android 17 的变化
Android 17 引入了新的系统联系人选择器。应用将获得一个临时的、只读的会话 URI,仅暴露用户选定的联系人和字段,且无需任何权限。对于目标 API 为 Android 17 的应用,系统会自动将传统的 Intent.ACTION_PICK 联系人 Intent 升级为此新选择器。应用还可以通过新的 Intent Action 请求特定数据字段(如电话、邮箱、邮政地址)并支持多选。
在旧版 Android 上,通过联系人选择器选取特定数据行(例如电话号码)时,返回的 URI 一直允许应用无需 READ_CONTACTS即可读取。这使得在所有 Android 版本上都能实现无权限流程。
为什么“选择联系人”过去需要 READ_CONTACTS 权限
许多 Capacitor 插件通过打开联系人选择器来选取整个联系人,然后查询通讯录以获取该联系人的电话和邮箱。第二次查询需要 READ_CONTACTS。因此,即使应用只想获取一个电话号码,也最终声明了该权限,而这正是该政策所针对的问题。
修复方法是让选择器直接请求所需的特定字段,这样返回的 URI 就已经携带了数据。
无需 READ_CONTACTS 即可选择电话、邮箱或地址
@capgo/capacitor-contacts 在 8.1.0 版本中添加了 property 选项。设置后,Android 会针对电话、邮箱或邮政地址表打开选择器,并从授权的 URI 中读取选中的行。无需 READ_CONTACTS 在任何 Android 版本上都需要。在 iOS 上,系统选择器始终不需要联系人权限。
bun add @capgo/capacitor-contacts
bunx cap sync
import { CapacitorContacts, ContactProperty } from '@capgo/capacitor-contacts';
export async function pickPhoneNumber(): Promise<string | undefined> {
const { contacts } = await CapacitorContacts.pickContacts({
property: ContactProperty.PhoneNumber,
});
const picked = contacts[0];
return picked?.phoneNumbers?.[0]?.value;
}
export async function pickEmail(): Promise<string | undefined> {
const { contacts } = await CapacitorContacts.pickContacts({
property: ContactProperty.EmailAddress,
});
return contacts[0]?.emailAddresses?.[0]?.value;
}
使用 property,结果包含该联系人 id, displayName 以及所选属性。多选会被忽略,属性选择始终只返回一个值。如果用户取消,请检查是否为空数组。
该插件不会在 READ_CONTACTS 或 WRITE_CONTACTS 自身的 manifest 中声明,因此如果你只使用属性选择,合并后的 manifest 会保持干净。
当你仍需要 READ_CONTACTS 时
这些插件方法会读取通讯录,因此仍然需要该权限:
pickContacts()或pickContact()无需property,当你需要完整的联系人记录时getContacts(),getContactById(),countContacts()getGroups(),getGroupById(),getAccounts()
如果您的应用是 CRM、备份工具或好友匹配功能,这属于合理的核心用途。请自行添加权限并提交声明:
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
import { CapacitorContacts } from '@capgo/capacitor-contacts';
const perm = await CapacitorContacts.requestPermissions({ permissions: ['readContacts'] });
if (perm.readContacts === 'granted' || perm.readContacts === 'limited') {
const { contacts } = await CapacitorContacts.getContacts({
fields: ['displayName', 'phoneNumbers'],
limit: 200,
offset: 0,
});
console.log(contacts.length);
}
本政策针对 READ_CONTACTS. 使用 写入联系人 WRITE_CONTACTS (针对 createContact, updateContactById, deleteContactById)不属于本政策的范围,但只有用到这些方法时才添加。
Capacitor 应用迁移方案
-
检查合并后的 Manifest。 构建 Release 版本并打开
android/app/build/intermediates/merged_manifest/(或使用 Android Studio 的 Merged Manifest 标签页)。某些插件会添加READ_CONTACTS而您却毫无察觉。如果某个插件添加了该权限且您并不需要,请使用以下命令将其移除:tools:node="remove":<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_CONTACTS" tools:node="remove" /> </manifest> -
列出所有涉及联系人的位置。 针对每一处,决定是使用带属性的选择器,还是访问完整通讯录。
-
替换邀请和分享流程 配合
pickContacts({ property }). -
仅为核心功能保留该权限,并准备好声明文案:说明具体功能、为何选择器不够用,若功能较难找到,请附上演示视频。
-
发布原生构建。 Manifest 和插件变更需要新的 Play 商店版本。如果您在 CI 中构建, Capgo 构建 可以生成已签名的 Android 构建,并且 Android keystore 生成器 如果你第一次配置签名,这会有所帮助。
-
通过空中推送(OTA)发布 UI 更改。 新的原生插件上线后,选择器周围的文案和流程调整可以通过 Capgo 实时更新.
从其他联系人插件迁移
如果你使用 @capacitor-community/contacts (撰写本文时为 8.0.0 版本),其 Android 端 pickContact 会在打开选择器前请求 contacts 权限组,然后从通讯录读取完整联系人。它没有属性模式,方法名也不同(getContact 对比 getContactById使用 projection 对象而非 fields 数组)。我们 对 community contacts 插件与 Capgo 插件进行了对比 并映射了每次调用。
如果你仍在 Capacitor 中使用 Cordova contacts 插件,现在是替换它的好时机。请参阅 从 Cordova 迁移到 Capacitor.
故障排除
选择器返回了一个没有电话号码的联系人。 您调用了 pickContacts() 无需 property 且应用没有 READ_CONTACTS. 仅 id 以及 displayName (以及 Android 17+ 中通过选择器授予的字段)会恢复。添加 property.
Play Console 仍显示应用声明了 READ_CONTACTS。 某个库添加了它。检查合并后的 manifest 并使用以下命令移除: tools:node="remove"。同时检查其他轨道中的旧版本发布,每个活跃构建产物都会计入。
声明被拒绝。 邀请、推荐和分享被列为选择器的适用场景。重构该功能,或明确说明为何完整联系人列表是核心功能(例如针对整个通讯录运行的好友匹配)。
iOS 请求联系人权限。 你调用了读取通讯录的方法。仅选择联系人无需此权限。移除 NSContactsUsageDescription 如果你只进行联系人选择。
关键日期
| 日期 | 会发生什么 |
|---|---|
| 2026年4月15日 | 政策发布 |
| 2026年9月 | Play Console 将提示声明了 READ_CONTACTS 的应用提交声明 |
| 2027年1月27日 | 面向 Android 17 (API 37) 或更高版本的应用必须合规 |
提交前请查阅 Play Console 帮助页面了解该政策,Google 可能会更新截止日期和措辞。