隐私保护和用户控制仍然是 Android 体验的核心。正如照片选择器让媒体分享变得安全且易于实现一样,我们现在也将同样级别的隐私保护、简洁性和出色的用户体验带到联系人选择功能中。
联系人隐私保护的新标准
过去,需要访问特定用户联系人的应用依赖于广泛的 READ_CONTACTS 权限。虽然这种方法可以正常运行,但通常会授予应用比必要数据更多的数据。Android 17 中推出的新 Android 联系人选择器改变了这种动态,它提供了一个标准化、安全且可搜索的联系人选择界面。
借助此功能,用户可以仅向应用授予对他们选择的特定联系人的访问权限,这与 Android 对数据透明度和最小化权限占用的承诺相一致。
工作方式
开发者可以使用 Intent.ACTION_PICK_CONTACTS intent 集成联系人选择工具。此更新后的 API 提供了一些强大的功能:
- 精细的数据请求: 应用可以准确指定它们需要的字段,例如电话号码或电子邮件地址,而不是接收整个联系人记录。
- 多选支持: 该选择器支持单选和多选联系人,为开发者提供了更大的灵活性,以便实现群组邀请等功能。
- 选择限制: 开发者可以自定义用户一次可以选择的联系人数量上限。
- 临时访问: 选择后,系统会返回一个会话 URI,该 URI 提供对所请求数据的临时读取访问权限,确保访问权限不会持续超过必要的时间。
- 访问其他用户个人资料: 使用此新 intent 时,界面将允许用户从其他用户个人资料(例如工作资料、克隆的个人资料或私密空间)中选择内容。
- 优化性能: 联系人选择工具会返回一个 URI,该 URI 允许集体查询结果,无需像
ACTION_PICK所要求的那样单独查询各个联系人 URI。这种效率通过使用单个Binder事务进一步减少了系统开销。
向后兼容性和实现
对于搭载 Android 17 或更高版本的设备,系统会自动将指定联系人数据类型的旧版 ACTION_PICK intent 升级到新的、更安全的界面。不过,为了充分利用多选等高级功能,我们建议开发者更新其实现代码,并利用 ContentResolver 查询返回的会话 URI。
集成联系人选择工具如需集成联系人选择工具,开发者可以使用 ACTION_PICK_CONTACTS intent。以下代码示例演示了如何启动选择器并请求特定数据字段,例如电子邮件地址和电话号码。
// State to hold the list of selected contacts var contacts by remember { mutableStateOf<List>(emptyList()) } // Launcher for the Contact Picker intent val pickContact = rememberLauncherForActivityResult(StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { val resultUri = it.data?.data ?: return@rememberLauncherForActivityResult // Process the result URI in a background thread coroutine.launch { contacts = processContactPickerResultUri(resultUri, context) } } } // Define the specific contact data fields you need val requestedFields = arrayListOf( Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, ) // Set up the intent for the Contact Picker val pickContactIntent = Intent(ACTION_PICK_CONTACTS).apply { putExtra(EXTRA_PICK_CONTACTS_SELECTION_LIMIT, 5) putStringArrayListExtra( EXTRA_PICK_CONTACTS_REQUESTED_DATA_FIELDS, requestedFields ) putExtra(EXTRA_PICK_CONTACTS_MATCH_ALL_DATA_FIELDS, false) } // Launch the picker pickContact.launch(pickContactIntent)
用户做出选择后,应用会通过查询返回的会话 URI 来处理结果,以提取所请求的联系人信息。
// Data class representing a parsed Contact with selected details data class Contact(val id: String, val name: String, val email: String?, val phone: String?) // Helper function to query the content resolver with the URI returned by the Contact Picker. // Parses the cursor to extract contact details such as name, email, and phone number private suspend fun processContactPickerResultUri( sessionUri: Uri, context: Context ): List<Contact> = withContext(Dispatchers.IO) { // Define the columns we want to retrieve from the ContactPicker ContentProvider val projection = arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.Data.MIMETYPE, // Type of data (e.g., email or phone) ContactsContract.Data.DATA1, // The actual data (Phone number / Email string) ) val results = mutableListOf<Contact>() // Note: The Contact Picker Session Uri doesn't support custom selection & selectionArgs. context.contentResolver.query(sessionUri, projection, null, null, null)?.use { cursor -> // Get the column indices for our requested projection val contactIdIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID) val mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE) val nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY) val data1Idx = cursor.getColumnIndex(ContactsContract.Data.DATA1) while (cursor.moveToNext()) { val contactId = cursor.getString(contactIdIdx) val mimeType = cursor.getString(mimeTypeIdx) val name = cursor.getString(nameIdx) ?: "" val data1 = cursor.getString(data1Idx) ?: "" // Determine if the current row represents an email or a phone number val email = if (mimeType == Email.CONTENT_ITEM_TYPE) data1 else null val phone = if (mimeType == Phone.CONTENT_ITEM_TYPE) data1 else null // Add the parsed contact to our results list results.add(Contact(contactId, name, email, phone)) } } return@withContext results }
如需查看完整文档,请点击此处。
开发者最佳实践
为了提供最佳用户体验并保持高安全标准,我们建议您执行以下操作:
- 数据最小化: 仅请求应用需要的特定数据字段(例如电子邮件地址)。
- 立即持久保留: 立即持久保留所选数据,因为会话 URI 访问权限是临时的。
继续阅读
-
2026 年 1 月 27 日2026 年 1 月 27 日
产品动态
嵌入式照片选择器:一种更顺畅的方式,可在应用中私密地请求照片和视频。
Roxanna Aliabadi Walker, Yacine Rezgui • 阅读用时:8 分钟
-
产品动态
每位开发者的 AI 工作流程和需求都是独一无二的,因此能够选择 AI 如何帮助您进行开发非常重要。1 月,我们推出了选择任何本地或远程 AI 模型来为 Android Studio 中的 AI 功能提供支持的功能
Matthew Warner • 阅读用时:2 分钟
-
产品动态
Android Studio Panda 3 现在已是稳定版,可在生产环境中使用。此版本让您可以更好地控制和自定义 AI 支持的工作流程,从而比以往更轻松地构建高质量的 Android 应用。
Matt Dyor • 阅读用时:3 分钟
随时了解最新动态
每周通过电子邮件接收最新的 Android 开发洞见 每周。