uni-app App端无uni.getContact和uni.chooseContact可用API,必须自研原生插件:Android需动态申请READ_CONTACTS并用ContentResolver查询,iOS需配置NSContactsUsageDescription且无法直达通讯录开关;权限拒绝后须先getAuthSetting再openSetting,并onShow同步状态。

uni-app 无法直接读取通讯录联系人,必须通过原生插件 + 显式权限申请实现,且 iOS/Android 行为差异极大,跳转设置页后仍需手动同步状态。
为什么 uni.getContact 不行、uni.chooseContact 也不行
App 端(iOS/Android)根本不存在 uni.getContact 这个 API,它只在微信小程序中可用;uni.chooseContact 在 App 平台调用后会静默失败或直接返回空数组——这不是 bug,是 uni-app 官方明确不支持该能力的平台限制。系统级通讯录访问必须走原生层,JS 层无任何兜底通道。
- Android:需声明
android.permission.READ_CONTACTS,且从 Android 6.0 起必须动态申请 - iOS:需在
Info.plist中配置NSContactsUsageDescription描述字段,否则首次调用直接 crash - 所有平台下,用户点“拒绝”后,
uni.authorize({scope: 'scope.contact'})再次调用不会弹窗,必须跳转系统设置页
如何用原生插件安全读取联系人(Android 示例)
不能靠 H5+ 的 plus.contacts(已废弃且不兼容新 Android 版本),必须自研原生插件。核心逻辑是:Java 层用 ContentResolver 查询 ContactsContract.Contacts,再逐条关联号码,最后通过 JSCallback.invoke() 返回结构化数组。
- 插件入口方法必须为
public void main(Context context, Map,Android 容器只认这个签名options, JSCallback success, JSCallback fail) - 查询前务必检查
Context.checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED,否则抛异常 - 返回数据建议只传
displayName和phoneNumbers(数组),避免传输完整 raw-contact 数据引发内存溢出 - 不要在主线程做游标遍历,尤其联系人超 500 条时,务必切到
AsyncTask或Executors.newSingleThreadExecutor()
权限拒绝后怎么正确跳转设置页并同步 UI
uni.openSetting 是唯一可行入口,但它有严格触发条件:必须在 uni.getAuthSetting 确认 scope.contact 为 false 后才调用,否则 iOS 静默失败、Android 可能闪退。
- 错误写法:
uni.authorize失败后立刻uni.openSetting—— 此时系统尚未记录“已拒绝”,跳转无效 - 正确流程:失败回调里先调
uni.getAuthSetting,检查authSetting['scope.contact'] === false,再调uni.openSetting - 跳转回来后,必须在页面
onShow中重新执行uni.getAuthSetting并更新按钮状态(如“去授权”→“已授权”),否则 UI 脱离真实权限状态 - iOS 用户跳转后只能看到 App 主设置页,无法直达「通讯录」开关,需提示:“请在设置中找到「通讯录」并开启”
容易被忽略的兼容性硬伤
即使代码全对,以下三点仍会导致功能在部分设备上完全失效:
- 华为 EMUI / HarmonyOS 4+、小米 HyperOS 2.0+ 默认关闭「后台联系人访问」,插件在
onHide后再次onShow时可能查不到数据,必须在每次读取前重新校验权限并重置查询逻辑 - Android 13(API 33)起,
READ_CONTACTS被拆分为READ_CONTACTS(仅已有联系人)和READ_MEDIA_IMAGES(含头像),但 uni-app 目前不支持声明后者,头像字段将为空 - 返回的联系人手机号是原始字符串(含空格、括号、分机号),前端做匹配前必须用正则清洗,例如
phone.replace(/[\s\-\(\)\+]/g, '')











