
Android 13 引入了更严格的后台访问限制和隐私保护机制,应用在完全终止(killed)状态下无法主动读取联系人数据;但若需在前台启动或通过隐式 Intent 触发时访问联系人,必须同时声明 READ_CONTACTS 权限和 声明以满足运行时可见性要求。
android 13 引入了更严格的后台访问限制和隐私保护机制,应用在完全终止(killed)状态下**无法主动读取联系人数据**;但若需在前台启动或通过隐式 intent 触发时访问联系人,必须同时声明 `read_contacts` 权限和 `
在 Android 13(API 级别 33)及更高版本中,Google 进一步强化了“运行时权限可见性”(Runtime Visibility)模型。这意味着:即使你已在 AndroidManifest.xml 中声明了 READ_CONTACTS 权限,系统仍会拒绝应用访问联系人 Provider(如 ContactsContract.Contacts.CONTENT_URI),除非你明确通过 <queries></queries> 元素声明了对联系人内容提供者的查询意图。
⚠️ 关键前提:应用被彻底杀死(terminated)后,无法自发执行任何代码(包括 JobIntentService、WorkManager 或 BroadcastReceiver),因此不可能在此状态下“主动读取”联系人。这是 Android 系统级的设计限制,旨在防止后台滥用敏感数据。所有联系人读取操作必须发生在应用处于前台、可见或由用户显式触发(例如点击通知、从分享菜单启动)的上下文中。
✅ 正确做法如下:
YZTurboWebAndroid 高性能 Android WebView 容器 SDK 接入。用于在 Android 项目中集成 WebView 容器,实现: (1) WebView 预加载与复用,提升 H5 页面加载速度 (2) 离线包管理,拦截请求优先命中本地资源 (3) JS Bridge 双向通信,Na...
-
声明必要权限与查询意图
在AndroidManifest.xml中同时配置:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><!-- 必须声明的运行时权限 --><uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission><!-- Android 13+ 强制要求:声明对联系人 Provider 的查询能力 --><queries><intent><action android:name="android.intent.action.VIEW"></action><category android:name="android.intent.category.DEFAULT"></category><data android:mimetype="vnd.android.cursor.dir/contact"></data></intent><!-- 可选:如需读取特定字段(如电话/邮箱),也可补充以下 intent --><intent><action android:name="android.intent.action.VIEW"></action><data android:mimetype="vnd.android.cursor.dir/phone_v2"></data></intent><intent><action android:name="android.intent.action.VIEW"></action><data android:mimetype="vnd.android.cursor.dir/email_v2"></data></intent></queries><application ...>
...
</application></manifest>
-
动态申请权限(运行时)
即使清单已声明,仍需在运行时调用ActivityCompat.requestPermissions()请求READ_CONTACTS,且需针对 Android 13+ 使用ActivityResultLauncher实现安全回调:
private val contactPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
loadContacts() // 执行真正的查询逻辑
} else {
Toast.makeText(this, "未授予联系人权限", Toast.LENGTH_SHORT).show()
}
}
// 调用前检查并请求
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
contactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS)
} else {
loadContacts()
}
-
安全读取联系人示例(使用 ContentResolver)
权限获准后,通过ContentResolver查询(注意:仅限前台生命周期内):
private fun loadContacts() {
contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER
),
null, null, null
)?.use { cursor ->
val idIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID)
val nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
while (cursor.moveToNext()) {
val id = cursor.getString(idIndex)
val name = cursor.getString(nameIndex)
// 查询电话号码(需 HAS_PHONE_NUMBER == 1)
if (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val phoneCursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
"${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
arrayOf(id), null
)
phoneCursor?.use {
while (it.moveToNext()) {
val number = it.getString(0)
Log.d("Contact", "$name: $number")
}
}
}
}
}
}
? 注意事项总结:
- ❌ 应用被杀后无法自动拉起并读取联系人——这是 Android 13 的硬性限制,无绕过方案;
- ✅ 若需“冷启动时获取联系人”,应引导用户手动打开 App 并完成权限授权与首次加载;
- ✅ 推荐结合
WorkManager(带约束条件)或Notification+PendingIntent实现用户可感知的轻量交互入口; - ⚠️
<queries></queries>是 Android 13+ 的强制要求,缺失将导致SecurityException或空 Cursor; - ? 建议最小化读取范围(如仅请求
READ_CONTACTS,而非READ_CONTACTS+READ_CALL_LOG等组合权限),提升用户信任度与审核通过率。
遵循上述配置与实践,即可在 Android 13 合规前提下,安全、可靠地访问联系人信息。










