contacts
联系人读取、编辑、选择器和 vCard 导入导出。
访问系统通讯录:权限、读取、搜索、编辑、分组、系统选择器与 vCard 导入导出。
边界:联系人属于敏感数据。默认只读当前功能需要的字段和数量;列表页用limit/offset;修改后需save()提交。token 等凭据不要用通讯录存,请用 keychain。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import contacts |
| 适合做什么 | 选人、搜索联系人、展示卡片、创建/编辑联系人 |
| 调用时机 | 读取和选择器放在按钮回调;不要首屏批量读取 |
| 推荐顺序 | is_authorized → request_access → 读取/选择 → 修改后 save() |
| 编辑事务 | add_person 等改动需 save() 落盘;失败时 revert() |
#快速开始
下面脚本申请权限,读取前 10 个联系人并搜索名字:
已复制
import contacts
if not contacts.is_authorized():
contacts.request_access()
if not contacts.is_authorized():
print("通讯录未授权")
else:
people = contacts.get_all_people(limit=10)
for person in people:
name = getattr(person, "full_name", "") or "未命名"
print(person.identifier, name)
matches = contacts.find("张")
print("搜索到", len(matches), "个匹配")
#AppUI 示例
读取和系统选择器都放在按钮回调;列表不加载头像和 vCard。
已复制
import appui
import contacts
state = appui.State(
status="等待操作",
selected="—",
people=[],
)
def person_key(person):
return person["id"]
def person_row(person):
return appui.VStack([
appui.Text(person["name"]),
appui.Text(person["phone"]).font("caption").foreground_color("secondaryLabel"),
], spacing=2)
def load_people():
if not contacts.is_authorized():
contacts.request_access()
if not contacts.is_authorized():
state.batch_update(status="未授权", people=[], selected="—")
return
rows = []
for person in contacts.get_all_people(limit=20):
phones = getattr(person, "phone_numbers", []) or []
phone = phones[0] if phones else "—"
rows.append({
"id": person.identifier or str(person.id),
"name": (
getattr(person, "full_name", "")
or getattr(person, "organization", "")
or "未命名联系人"
),
"phone": str(phone),
})
state.batch_update(
status=f"已读取 {len(rows)} 个联系人",
people=rows,
)
def pick_one():
if not contacts.is_authorized():
contacts.request_access()
if not contacts.is_authorized():
state.status = "未授权,无法打开选择器"
return
picked = contacts.pick_contact()
if not picked:
state.status = "用户取消选择"
return
name = getattr(picked, "full_name", "") or "未命名联系人"
state.batch_update(
selected=name,
status="已通过系统选择器选中联系人",
)
def body():
return appui.NavigationStack(
appui.List([
appui.Section("操作", [
appui.Button("读取联系人", action=load_people)
.button_style("bordered_prominent"),
appui.Button("系统选择器选人", action=pick_one),
appui.LabeledContent("状态", value=state.status),
appui.LabeledContent("已选", value=state.selected),
]),
appui.Section("列表", [
appui.ForEach(state.people, row_builder=person_row, key=person_key),
], footer="默认 limit=20,不含头像与 vCard。"),
]).navigation_title("通讯录")
)
appui.run(body, state=state)
#API 参考
#速查
| API | 作用 |
|---|---|
is_authorized() / request_access() | 权限判断与申请 |
get_all_people(limit, offset) | 分页读取联系人 |
get_person(id, ...) | 读取单个联系人详情 |
find(name) | 按名字搜索 |
pick_contact() | 系统选择器选一人 |
add_person / save() / revert() | 编辑事务 |
export_vcards / import_vcards | vCard 导入导出 |
#权限
| API | 说明 |
|---|---|
authorization_status() | 查询授权状态 |
is_authorized() | 是否可访问通讯录 |
request_access() | 申请权限 |
manage_limited_access() | 调整「有限访问」联系人 |
capabilities() | 当前设备/系统能力 |
已复制
if not contacts.is_authorized():
contacts.request_access()
#读取与搜索
get_all_people(limit=100, offset=0) — 分页读取,列表页务必设 limit。
get_person(person_or_id, include_image_data=False, include_vcard=False) — 读取详情;头像和 vCard 按需开启。
已复制
people = contacts.get_all_people(limit=20, offset=0)
person = contacts.get_person(people[0], include_image_data=False)
matches = contacts.find("Ada")
by_phone = contacts.find_by_phone("138")
by_email = contacts.find_by_email("ada@example.com")
其他:get_me() 读取「我的名片」。
#编辑事务
修改不是立即落盘,需 save() 提交;失败时 revert() 回滚。
已复制
person = contacts.new_person(seed={"given_name": "Ada", "family_name": "Lovelace"})
contacts.add_person(person)
try:
contacts.save()
except Exception:
contacts.revert()
raise
| API | 说明 |
|---|---|
new_person(seed=...) | 创建新联系人对象 |
add_person(person) | 加入待提交队列 |
remove_person(person) | 标记删除 |
edit_person(person, **kwargs) | 修改字段 |
save() | 提交到系统通讯录 |
revert() | 放弃待提交修改 |
分组与容器:get_all_groups、add_group、get_people_in_group、get_all_containers 等。
#系统选择器
由用户主动选择,比无提示批量读取更稳妥:
已复制
picked = contacts.pick_contact()
if picked:
contacts.show_person(picked, allows_editing=False, allows_actions=True)
multi = contacts.pick_contacts()
prop = contacts.pick_property(kind="phone")
pick_contact() 返回 None 表示用户取消。
#vCard 与变更
已复制
data = contacts.export_vcards([person])
contacts.import_vcards(data)
history = contacts.get_change_history(token=None)
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
| 首屏读取全部联系人 | 隐私体验差、可能很慢 | 按钮触发 + limit |
修改后忘记 save() | 变更未落盘 | 成功路径调用 save() |
| 保存失败继续用待提交对象 | 状态不一致 | revert() 后重试 |
默认 include_image_data=True | 内存和隐私成本高 | 只在头像页按需加载 |
#相关文档
| 文档 | 用途 |
|---|---|
| permission | 统一权限查询(Bridge 对 contacts 支持有限) |
| keychain | 保存 token,不用通讯录 |
| 原生能力入口 | MiniApp 场景配方 |
#预期效果
运行示例后,界面应出现文档描述的目标结果;若与预期不符,先看「失败路径」并按返回值或日志排查。