适用版本:

  • HarmonyOS 5
  • HarmonyOS NEXT
  • DevEco Studio 6+
  • ArkTS

一、前言

通讯录(Contacts)几乎是所有社交类、办公类、企业IM、CRM、OA、企业通讯录等应用都会使用到的一项能力。

例如:

  • 微信邀请好友
  • QQ添加通讯录好友
  • 钉钉企业联系人
  • 飞书组织架构
  • 企业OA通讯录
  • CRM客户导入
  • 手机联系人同步

很多刚开始学习HarmonyOS的开发者都会发现:

为什么网上几乎没有鸿蒙通讯录API?

原因很简单:

HarmonyOS NEXT 出于隐私保护考虑,已经不建议应用直接读取整个通讯录,而是推荐开发者通过 Contacts Picker(联系人选择器) 获取用户主动选择的联系人。批量读取通讯录属于受限开放能力,仅适用于备份、同步等特定场景,需要申请相应权限。

因此,目前开发主要有两种方式:


方式一(推荐)
Contacts Picker
用户主动选择联系人

★★★★★ 推荐
普通应用均可使用

-----------------------

方式二(受限)
Contacts Kit

读取整个通讯录
新增联系人
修改联系人
删除联系人

★★★★★
需要申请受限权限

本文将全部讲解。


二、Contacts Kit介绍

HarmonyOS提供了Contacts Kit。

主要负责:


联系人查询

联系人新增

联系人修改

联系人删除

联系人选择器

联系人头像

联系人电话

联系人邮箱

联系人地址

联系人公司

联系人备注

联系人分组

官方能力如下:


Contacts Kit

↓

联系人管理

↓

联系人Picker

↓

联系人数据库

需要注意的是:

HarmonyOS NEXT已经把通讯录能力进行了严格权限划分。

普通应用:

✅ 推荐使用Picker

特殊应用:

才能申请READ_CONTACTS权限。


三、通讯录权限介绍

很多人第一步就写:


ohos.permission.READ_CONTACTS

然后发现:


权限申请失败

为什么?

因为:

HarmonyOS NEXT中:


READ_CONTACTS

属于受限开放权限

官方说明:


READ_CONTACTS

仅支持:

联系人同步

联系人备份

设备克隆

等特殊业务

普通应用一般不能申请。

因此:

官方推荐:


Contacts Picker

由用户主动选择联系人。


四、Contacts Picker介绍

这是HarmonyOS官方推荐方式。

流程如下:


App

↓

打开联系人Picker

↓

系统联系人界面

↓

用户选择联系人

↓

返回联系人信息

↓

App获取结果

整个过程:


无需读取整个通讯录

无需同步全部联系人

更加安全

更加符合隐私规范

也是目前HarmonyOS官方推荐方案。


五、Contacts Picker使用流程

整个流程只有四步:


第一步

导入模块

↓

第二步

创建Picker

↓

第三步

打开联系人列表

↓

第四步

获取联系人结果

整体流程如下:


Button

↓

点击

↓

Contacts Picker

↓

用户选择联系人

↓

返回联系人对象

六、项目结构


entry
│
├──pages
│     Index.ets
│
├──model
│     ContactModel.ets
│
├──utils
│     ContactHelper.ets
│
└──resources

后面我们会把联系人模块封装成一个工具类。


七、导入联系人模块

首先导入联系人能力:


import contact from '@ohos.contact';

随后即可调用联系人相关接口。


八、创建联系人选择器

例如:


import contact from '@ohos.contact';

let picker = new contact.ContactPicker();

然后可以配置:


let options = {
  multiple: false
}

如果:


multiple=true

表示:


可以选择多个联系人

否则:


只能选择一个联系人

九、打开联系人选择器

例如:


picker.select(options)
.then((result)=>{
    console.info(JSON.stringify(result))
})
.catch((err)=>{
    console.error(JSON.stringify(err))
})

运行效果:


点击按钮

↓

系统联系人界面

↓

选择联系人

↓

返回App

↓

获得联系人数据

整个过程无需申请读取全部通讯录权限,更符合 HarmonyOS 的隐私设计。


十、返回的数据结构

返回的数据通常包含:


{
    id:"1001",

    name:"张三",

    phoneNumbers:[
        "13800138000"
    ],

    emails:[
        "abc@qq.com"
    ],

    organization:"Huawei",

    portraitUri:"",

    addresses:[

    ]
}

一般情况下:

开发者最常用的是:


姓名

手机号

邮箱

头像

十一、Contacts Picker 完整实战(推荐方式)

上一章节我们已经能够打开系统联系人选择器,本章节我们来完成一个完整的联系人选择案例,包括:

  • 点击按钮打开联系人
  • 单选联系人
  • 多选联系人
  • 获取联系人信息
  • 页面数据显示
  • 异常处理

这是 HarmonyOS 官方推荐的联系人获取方式,无需读取整个通讯录,更符合隐私保护要求。


1、实现效果

最终效果如下:


┌────────────────────────────┐
│        通讯录Demo           │
├────────────────────────────┤
│                            │
│   请选择联系人             │
│                            │
│   张三                     │
│   13800138000             │
│                            │
│ [选择联系人]               │
│                            │
└────────────────────────────┘

点击按钮后:


Button

↓

打开系统联系人

↓

用户选择联系人

↓

返回联系人对象

↓

刷新UI

整个流程完全由系统负责,开发者无需自行实现联系人列表页面。


2、定义联系人数据模型

为了方便项目维护,建议先定义一个联系人实体类。

ContactModel.ets


export class ContactModel {
  id: string = ''

  name: string = ''

  phone: string = ''

  email: string = ''

  organization: string = ''

  portraitUri: string = ''
}

以后无论是网络接口还是数据库,都可以统一使用该实体。


3、页面状态定义

ArkUI 推荐使用 @State 管理页面数据。


@State contact: ContactModel = new ContactModel()

默认显示:


姓名:

手机号:

邮箱:

选择联系人后自动刷新。


4、页面UI设计

例如:


Column({ space: 20 }) {

  Text(this.contact.name)

  Text(this.contact.phone)

  Text(this.contact.email)

  Button("选择联系人")
    .onClick(()=>{
      this.selectContact()
    })

}
.width("100%")
.padding(20)

运行后:


姓名

手机号

邮箱

按钮

布局十分简单。


5、封装选择联系人方法

推荐不要把所有代码写在 Button 中。

例如:


private async selectContact() {

}

以后:


Button

↓

selectContact()

↓

Contacts Picker

代码更加清晰。


6、创建 Picker

例如:


let picker = new contact.ContactPicker()

然后配置:


let options = {
    multiple:false
}

参数说明:

参数 类型 说明
multiple boolean 是否允许多选

例如:


false

↓

只能选择一个联系人

如果:


true

↓

可以选择多个联系人

7、打开联系人页面

例如:


let result = await picker.select(options)

整个调用流程:


App

↓

Contacts Picker

↓

用户点击联系人

↓

返回结果

如果用户取消:


throw Exception

所以建议:


try{

}
catch(){

}

8、解析联系人数据

例如:


this.contact.name =
    result.name

this.contact.phone =
    result.phoneNumbers[0]

this.contact.email =
    result.emails[0]

如果联系人没有邮箱:


emails

↓

[]

↓

不能直接取下标

推荐:


this.contact.email =
result.emails.length>0 ?
result.emails[0] :
""

更加安全。


9、完整代码示例

下面给出完整示例:


import contact from '@ohos.contact'

@Entry
@Component
struct Index {

  @State
  contactName:string=""

  @State
  phone:string=""

  build(){

    Column(){

      Text(this.contactName)

      Text(this.phone)

      Button("选择联系人")
      .onClick(async ()=>{

          let picker =
          new contact.ContactPicker()

          try{

              let result =
              await picker.select({
                  multiple:false
              })

              this.contactName =
              result.name

              if(result.phoneNumbers.length>0){
                  this.phone =
                  result.phoneNumbers[0]
              }

          }catch(err){

              console.error(JSON.stringify(err))

          }

      })

    }

  }

}

10、运行流程分析

整个运行过程如下:


App启动

↓

点击按钮

↓

创建Picker

↓

跳转系统通讯录

↓

用户选择联系人

↓

返回联系人对象

↓

刷新State

↓

UI自动刷新

ArkUI 响应式更新:


State变化

↓

组件重新构建

↓

Text重新显示

无需手动调用:


notifyDataSetChanged()

setState()

refresh()

11、多选联系人

如果:


multiple:true

那么:


张三

李四

王五

都可以选择。

返回的数据变成:


[
   Contact,

   Contact,

   Contact
]

遍历即可:


for(let item of result){

    console.info(item.name)

}

企业通讯录导入通常都会采用这种方式。


12、联系人头像

如果联系人存在头像:

返回:


portraitUri

例如:


console.info(result.portraitUri)

返回:


file://....

或者

media://....

然后即可:


Image(result.portraitUri)

显示联系人头像。


13、联系人电话号码

很多联系人:


手机

家庭

办公

公司

传真

都会存在多个号码。

例如:


phoneNumbers:[
   "138****0000",
   "0755-88888888"
]

不要默认只有一个。

推荐:


for(let phone of result.phoneNumbers){

}

全部展示。


14、联系人邮箱

同样:


emails

也是数组。

例如:


emails:[

"abc@qq.com",

"123@gmail.com"

]

建议全部读取。


15、联系人公司信息

例如:


organization

返回:


Huawei

Tencent

Alibaba

企业OA中经常使用。


16、联系人地址

返回:


addresses

例如:


广东省深圳市

北京市海淀区

一般用于:

  • 快递
  • CRM
  • 客户管理

17、联系人备注

联系人还可能包含:


Note

Birthday

Nickname

Website

IM

CustomLabel

不同系统版本返回字段可能存在差异,因此建议在使用前做好空值判断,并根据实际返回的数据结构进行适配。


18、异常处理

最常见异常:

用户取消选择


打开联系人

↓

点击返回

↓

Promise Reject

处理:


try{

}catch(err){

}

不要弹出错误提示。


联系人为空

例如:


没有手机号

判断:


if(result.phoneNumbers.length==0){

}

邮箱为空

例如:


if(result.emails.length==0){

}

头像不存在

例如:


portraitUri==""

使用默认头像即可。


19、最佳实践

建议将联系人能力统一封装为一个工具类:


ContactHelper

↓

selectSingle()

↓

selectMultiple()

↓

getPhones()

↓

getEmails()

↓

parseContact()

页面只需要:


let contact =
await ContactHelper.selectSingle()

即可获得统一格式的联系人对象,后续维护成本更低,也更符合企业级项目的分层设计。

十三、HarmonyOS 通讯录权限管理详解(Contacts Permission)

上一章节,我们介绍了 Contacts Picker 的使用,它是 HarmonyOS 官方推荐的联系人获取方式。

很多开发者在实际开发时都会产生疑问:

为什么 Android 可以直接读取整个通讯录,而 HarmonyOS NEXT 却提示没有权限?

本章节就来深入解析 HarmonyOS 的通讯录权限模型、受限权限机制以及企业级开发中的最佳实践。


一、HarmonyOS 权限模型

HarmonyOS NEXT 对用户隐私进行了更加严格的保护。

通讯录属于用户的高敏感数据,因此默认情况下应用不能直接访问。

整体权限模型如下:


应用

        │

        ▼

申请权限

        │

        ▼

系统权限管理

        │

        ▼

用户授权

        │

        ▼

允许访问

但是对于通讯录而言,仅有用户授权还不够。


二、通讯录权限分类

HarmonyOS 中通讯录相关能力主要涉及以下权限:


READ_CONTACTS

读取联系人

------------------------

WRITE_CONTACTS

修改联系人

------------------------

READ_CALL_LOG

读取通话记录

------------------------

WRITE_CALL_LOG

修改通话记录

其中真正开发最常用的是:


READ_CONTACTS

三、为什么申请 READ_CONTACTS 会失败?

很多开发者第一次都会这样写:


{
  "requestPermissions": [
    {
      "name": "ohos.permission.READ_CONTACTS"
    }
  ]
}

然后运行:


权限申请失败

或者:


Authorization failed

甚至:


BusinessError

为什么?

答案就是:


READ_CONTACTS

属于 Restricted Permission(受限权限)

也就是说:

不是所有应用都可以申请。


四、什么是受限权限(Restricted Permission)

HarmonyOS 将权限分成多个等级:


普通权限

↓

危险权限

↓

受限权限

↓

系统权限

其中:

通讯录、

短信、

通话记录、

设备标识、

安装应用列表

都属于高隐私数据。

因此:

HarmonyOS 不允许普通应用自由读取。


五、为什么这样设计?

假设:

某个小游戏安装后:


读取通讯录

↓

上传服务器

↓

分析联系人关系

↓

广告推送

用户根本不知道发生了什么。

因此 HarmonyOS NEXT 提出了:

最小权限原则(Least Privilege)

即:

应用只获取完成当前任务所必须的数据。

例如:

用户只是想邀请一位好友。

那么:

应用根本没有必要读取:


1000

2000

5000

全部联系人

所以官方提供了:


Contacts Picker

用户主动选择联系人。

应用只能获取:


张三

而不是:


整个通讯录

这样更加安全。


六、Contacts Picker 为什么不需要 READ_CONTACTS?

很多人觉得奇怪:

既然通讯录受限,

为什么 Picker 可以?

原因就在于:

联系人数据并不是应用自己读取的。

整个过程如下:


App

↓

调用 Picker

↓

系统联系人应用

↓

用户主动选择

↓

系统返回结果

↓

App 获取数据

注意:

真正读取通讯录的是:


系统联系人

不是你的 App。

因此:


无需 READ_CONTACTS

这也是目前 HarmonyOS 官方推荐方案。


七、哪些应用可以申请 READ_CONTACTS?

官方允许以下类型应用申请:


联系人同步

联系人备份

手机克隆

企业设备管理

系统工具

例如:


华为云同步

手机克隆

企业通讯录

设备迁移

普通:


商城

新闻

外卖

视频

小游戏

通常无法获得该权限。


八、企业应用怎么办?

很多企业 OA 都需要:


读取全部联系人

例如:


员工通讯录

CRM

客户同步

组织架构

这类应用通常需要:


申请受限开放能力

↓

平台审核

↓

权限放开

↓

发布

因此:

不要认为:


READ_CONTACTS

申请不了

就是 API 坏了

实际上:

这是 HarmonyOS 的安全策略。


九、什么时候应该使用 Picker?

官方推荐:

绝大多数 App:

都应该:


Contacts Picker

例如:


邀请好友

↓

选择收货联系人

↓

分享联系人

↓

添加紧急联系人

↓

企业联系人选择

这些场景:

都不需要读取整个通讯录。


十、什么时候必须读取整个通讯录?

例如:


微信好友同步

需要:


全部联系人

例如:


企业CRM

↓

同步客户

例如:


通讯录备份

这些属于:


后台同步

批量读取

持续更新

只能:


READ_CONTACTS

十一、运行时权限申请流程

对于普通危险权限,HarmonyOS 的流程如下:


声明权限

↓

检查权限状态

↓

申请权限

↓

用户授权

↓

执行功能

但对于:


READ_CONTACTS

即使代码流程正确,也可能因为权限类型受限而无法获得授权。

因此,开发时要先确认应用是否具备申请该权限的资格,而不是一味排查代码问题。


十二、如何优雅处理权限失败?

推荐不要这样:


读取失败!

程序异常!

未知错误!

用户完全不知道原因。

建议:


无法读取通讯录

请选择联系人

或者:


当前版本

仅支持联系人选择

体验更好。


十三、企业级权限封装

建议项目中统一封装权限管理类:


PermissionManager

│

├──checkPermission()

├──requestPermission()

├──gotoSetting()

├──showPermissionDialog()

└──openContactPicker()

业务页面:


Page

↓

PermissionManager

↓

Contacts Picker

这样:

以后修改权限逻辑:

所有页面:

无需修改。


十四、权限异常处理

企业项目最常见:

用户拒绝


第一次

↓

拒绝

处理:


弹出说明

↓

再次申请

永久拒绝


勾选

↓

不再询问

处理:


引导进入

设置页面

企业权限未开通

表现:


BusinessError

Permission denied

不要反复重试。

应该:


联系管理员

申请开放权限

模拟器无法测试

部分通讯录能力:


Simulator

×

真机

√

建议:

始终使用真机。


十五、企业项目最佳实践

建议采用如下架构:


UI

↓

ViewModel

↓

ContactService

↓

PermissionManager

↓

Contacts Kit

其中:

PermissionManager

负责:

  • 检查权限
  • 申请权限
  • 权限失败处理

ContactService

负责:

  • 打开 Picker
  • 查询联系人
  • 数据转换
  • 返回统一模型

这样可以做到:


业务页面

↓

一行代码

↓

获得联系人

例如:


const contact = await ContactService.selectSingle();

而页面无需关心:

  • 权限判断
  • 异常捕获
  • 数据解析
  • 兼容不同系统版本

十六、Contacts Kit 与 Contacts Picker 对比

对比项 Contacts Picker Contacts Kit
是否官方推荐 ⭐⭐⭐⭐⭐ ⭐⭐⭐
是否需要受限权限 ❌ 否 ✅ 是
用户体验 用户主动选择 应用直接读取
是否支持普通应用 ✅ 支持 ❌ 大多数不支持
是否可批量读取
是否可后台同步
适用场景 选择联系人、邀请好友 企业同步、备份、迁移

十七、面试高频问题

1. HarmonyOS 为什么推荐 Contacts Picker?

答: 因为 Contacts Picker 由系统联系人应用完成数据读取,用户主动选择联系人,应用仅获得用户授权分享的联系人信息,无需访问整个通讯录,更符合最小权限原则和隐私保护要求。


2. 普通应用可以申请 READ_CONTACTS 吗?

答: 一般不可以。READ_CONTACTS 属于受限开放权限(Restricted Permission),通常仅开放给通讯录同步、数据迁移、企业管理等符合条件的应用。


3. 企业级项目如何设计通讯录模块?

答: 建议按 UI → ViewModel → ContactService → PermissionManager → Contacts API 分层设计,将权限申请、联系人选择、数据解析统一封装,业务层只关注联系人结果,提高可维护性和可测试性。

十四、HarmonyOS Contacts Kit 数据模型深度解析(联系人字段详解)

前面我们已经了解了:

  • Contacts Picker 的使用
  • 通讯录权限体系
  • 为什么普通应用推荐使用 Picker

很多开发者接下来会有新的疑问:

联系人对象到底有哪些字段?

手机号为什么是数组?

一个联系人可以有几个邮箱?

为什么不同手机返回的数据不一样?

本章就深入解析 HarmonyOS Contacts Kit 的数据模型,理解联系人对象的组织方式,为后续联系人查询、同步、搜索、导入导出打下基础。


一、联系人(Contact)对象结构

在 HarmonyOS 中,一个联系人(Contact)并不是简单的一条数据,而是由多个属性组合而成。

一个完整的联系人可以表示为:


Contact
│
├── id
├── name
├── phoneNumbers
├── emails
├── organization
├── addresses
├── portraitUri
├── websites
├── nickName
├── note
├── birthday
└── groups

也就是说:

联系人并不是:


姓名

手机号

而是:


一个对象

↓

多个属性

↓

多个集合

二、为什么手机号是数组?

很多新人都会写:


contact.phone

结果发现:


不存在

真正的数据结构更类似于:


phoneNumbers: [
    "13800138000",
    "0755-88888888",
    "400-800-8888"
]

为什么?

因为一个联系人可能拥有:


手机

公司

家庭

备用

传真

其它号码

因此设计成数组。

例如:


张三

↓

手机

13800138000

↓

办公

0755-88888888

↓

家庭

02088888888

企业 CRM 中这种情况十分常见。


三、手机号建议如何封装?

很多业务只需要:


手机号

建议统一封装:


class ContactModel {

    name:string=""

    phone:string=""

}

解析时:


if(contact.phoneNumbers.length>0){
    model.phone=contact.phoneNumbers[0]
}

如果业务需要:


多个号码

建议:


phones:string[]=[]

全部保存。


四、邮箱为什么也是数组?

邮箱的数据结构与手机号类似:


emails:[
    "test@qq.com",
    "work@company.com",
    "abc@gmail.com"
]

原因:

现代联系人通常拥有:


个人邮箱

公司邮箱

备用邮箱

所以:


Email

↓

Array

而不是:


String

五、地址(Addresses)

地址同样可以存在多个:


家庭

公司

学校

其它

例如:


addresses:[
    "广东深圳",
    "北京市朝阳区"
]

企业管理系统经常使用:


客户地址

↓

物流地址

↓

收货地址

所以建议:

统一按集合处理。


六、组织信息(Organization)

很多开发者认为:


organization

↓

公司名称

实际上:

企业联系人一般还会包含:


公司

部门

职位

工号

例如:


Huawei

研发一部

高级工程师

在 OA、企业通讯录中使用频率非常高。


七、头像(Portrait)

联系人头像通常保存为:


URI

例如:


file://...

media://...

并不是:


Bitmap

也不是:


PixelMap

因此:

UI 显示时一般需要:


Image(contact.portraitUri)

如果:


portraitUri==""

说明:

联系人没有头像。

建议:

显示默认头像。


八、备注(Note)

联系人可以保存备注:


小学同学

大学室友

客户A

供应商

例如:


note:
"十年老客户"

CRM 系统中十分常见。


九、昵称(NickName)

例如:


王总

老李

Tom

Jerry

昵称:

并不是姓名。

很多社交软件:

都会优先显示:


昵称

↓

姓名

十、生日(Birthday)

联系人支持:


生日

例如:


1996-10-01

很多 App:

例如:


日历

生日提醒

会员系统

都会读取。


十一、网站(Website)

联系人可以拥有:


官网

博客

GitHub

个人主页

例如:


https://example.com

企业联系人:

十分常见。


十二、IM 信息

很多联系人:

还有:


微信

QQ

Telegram

Skype

Teams

统一称:


Instant Message

简称:


IM

企业办公软件:

大量使用。


十三、联系人 ID

很多开发者:

喜欢这样:


姓名

↓

唯一

实际上:

错误。

真正唯一的是:


Contact ID

例如:


10001

10002

10003

因此:

数据库更新:

应该:


ID

↓

更新联系人

不要:


姓名

因为:


张三

可能有很多个

十四、联系人对象为什么那么复杂?

因为:

联系人实际上对应:

现实中的一个人。

一个人拥有:


多个电话

多个邮箱

多个地址

多个网站

多个组织

多个IM

所以:

Contacts Kit:

采用:


Contact

↓

多个子对象

↓

多个集合

而不是:


一个大字符串

十五、推荐的数据模型设计

企业项目建议重新封装自己的联系人模型,而不是直接使用系统对象:


export class ContactModel {

    id:string=""

    name:string=""

    avatar:string=""

    company:string=""

    phones:string[]=[]

    emails:string[]=[]

    addresses:string[]=[]

    note:string=""

}

优点:


统一数据格式

↓

业务解耦

↓

方便缓存

↓

方便网络同步

↓

方便数据库存储

十六、数据转换层(Mapper)

实际开发中,不建议业务代码直接操作系统返回的 Contact 对象。

推荐增加一层转换:


Contacts Kit

        │

        ▼

ContactMapper

        │

        ▼

ContactModel

        │

        ▼

UI

例如:


export class ContactMapper {

    static map(contact:any):ContactModel{

        let model=new ContactModel()

        model.id=contact.id

        model.name=contact.name

        model.avatar=contact.portraitUri

        model.company=contact.organization

        model.phones=contact.phoneNumbers

        model.emails=contact.emails

        model.addresses=contact.addresses

        return model

    }

}

这样做有几个好处:

  • 系统 API 变化时,只需修改 Mapper。
  • UI 和业务层无需依赖系统数据结构。
  • 便于单元测试和 Mock 数据。
  • 后续切换数据来源(本地数据库、网络接口)时改动更小。

十七、大型项目推荐架构

推荐采用如下分层:


UI

↓

ViewModel

↓

Repository

↓

ContactService

↓

ContactMapper

↓

Contacts Kit

各层职责:

层级 职责
UI 展示联系人、处理用户交互
ViewModel 管理页面状态,调用业务接口
Repository 统一管理联系人数据来源(本地/远程)
ContactService 封装 Contacts Kit 调用
ContactMapper 系统对象转换为业务对象
Contacts Kit 与系统通讯录交互

这种结构更适合企业级项目,也便于后期维护和扩展。


十八、常见开发误区

误区一:手机号只有一个

错误:


contact.phoneNumbers[0]

直接认为唯一号码。

正确做法:

根据业务需求遍历全部号码,或明确约定取主号码。


误区二:姓名唯一

错误:


根据姓名更新联系人

正确:


根据 Contact ID 更新

误区三:所有字段都存在

错误:


contact.emails[0]

正确:


const email = contact.emails?.length > 0
  ? contact.emails[0]
  : ""

任何可选字段都应做好空值判断。

十五、HarmonyOS Contacts Kit 查询联系人实战(Contacts Query 全解析)

说明:
从本章开始,我们进入 Contacts Kit 的核心 API 讲解。需要再次强调的是:查询整个通讯录属于受限能力,普通应用通常应使用 Contacts Picker;本章内容主要用于了解 Contacts Kit 的能力模型、企业应用开发以及面试学习。


一、Contacts 查询整体流程

很多开发者认为:


查询联系人

↓

得到 List<Contact>

实际上,企业级项目通常会经历多个步骤:


UI

↓

ContactService

↓

ContactsRepository

↓

Contacts Kit

↓

联系人数据库

↓

Cursor(游标)

↓

Contact

↓

Mapper

↓

ContactModel

↓

UI

可以看到:

真正返回给 UI 的一般不是系统对象,而是业务对象。


二、Contacts 查询方式

Contacts Kit 一般支持以下几类查询:


查询全部联系人

↓

根据ID查询

↓

根据姓名查询

↓

根据手机号查询

↓

分页查询

↓

条件过滤

↓

排序查询

不同查询方式适用于不同业务场景。


三、查询全部联系人

企业通讯录同步时:

通常需要:


所有联系人

流程如下:


Contacts Kit

↓

查询数据库

↓

Cursor

↓

读取每一条联系人

↓

转换对象

↓

返回 List<Contact>

注意:

不要一次性把所有联系人直接加载到页面。

例如:


5000 联系人

↓

一次创建5000个对象

↓

内存快速增长

↓

页面卡顿

因此:

推荐:


分页

↓

边读取

↓

边转换

↓

边显示

四、Cursor(游标)是什么?

很多开发者第一次看到:


Cursor

感觉很陌生。

实际上:

Cursor 可以理解成:


数据库结果集

例如:

数据库:


联系人表

↓

10000 条数据

不会:


一次返回10000对象

而是:


Cursor

↓

第一条

↓

第二条

↓

第三条

↓

……

像指针一样:

逐条读取。

这样:

内存占用非常低。


五、Cursor 遍历流程

例如:


Cursor

↓

moveNext()

↓

读取数据

↓

moveNext()

↓

读取数据

↓

结束

整体流程:


Cursor

↓

while()

↓

读取

↓

封装

↓

加入 List

企业项目:

基本都是这样处理。


六、为什么不能一次读取全部?

假设:

一个联系人:


姓名

手机号

头像

邮箱

地址

备注

约:


2KB

如果:


10000 联系人

则:


约20MB

如果:

再加载头像:


几十 MB

甚至

上百 MB

因此:

千万不要:


一次全部加载

七、分页查询思想

推荐:


第一页

50 条

↓

用户滑动

↓

第二页

50 条

↓

第三页

这样:

页面:

始终保持:


几十条联系人

不会出现:


OOM

卡顿

八、联系人分页架构

企业项目:

一般采用:


List

↓

Scroll

↓

滑到底部

↓

加载下一页

↓

追加数据

而不是:


一次全部显示

九、根据姓名查询

很多 App:

都会:


搜索

↓

张三

流程:


输入框

↓

关键词

↓

Contacts Kit

↓

返回联系人

例如:


可能返回:


张三

张四

张老师

张经理

因此:

一般:


模糊查询

而不是:


完全匹配

十、根据手机号查询

例如:

CRM:


客户来电

↓

13800138000

↓

查联系人

↓

显示姓名

整体流程:


手机号

↓

Contacts Kit

↓

联系人

↓

详情页

这种:

企业系统:

使用非常多。


十一、根据 ID 查询

联系人:

真正唯一的是:


ID

例如:


10001

查询:


Contact

↓

ID=10001

返回:


张三

更新联系人:

也是:


ID

↓

更新

不要:


姓名

十二、联系人排序

很多通讯录:

都会:


A

B

C

D

按照:


拼音

字母

姓名

排序。

例如:


Apple

↓

Bob

↓

Cindy

↓

David

中文:

通常:


拼音排序

例如:


李四

↓

王五

↓

张三

十三、联系人过滤

例如:

企业:

只显示:


有手机号

过滤:


Contact

↓

phone!=null

或者:


有邮箱

过滤:


email!=null

还可以:


公司

部门

标签

过滤。


十四、搜索优化

错误方式:


输入:

张

↓

查询

↓

输入:

张三

↓

再次查询

每输入一个字:

都查数据库。

推荐:


用户停止输入

300ms

↓

开始查询

即:


Debounce(防抖)

流程:


输入

↓

等待300ms

↓

没有继续输入

↓

查询

这样:

数据库压力:

降低很多。


十五、缓存联系人

企业项目:

建议:

第一次:


Contacts Kit

↓

读取联系人

之后:

缓存:


Memory

或者:


Preferences

Database

搜索:

直接:


缓存

不用:

每次:


读取系统通讯录

效率:

提升很多。


十六、Repository 模式

推荐:

不要:


UI

↓

Contacts Kit

而是:


UI

↓

ViewModel

↓

Repository

↓

Contacts Kit

Repository:

负责:


查询

缓存

分页

搜索

更新

以后:

如果:

联系人:

来自:


网络

只需要:

修改:

Repository。

UI:

不用改。


十七、Service 层封装

例如:


ContactService

↓

queryAll()

↓

queryById()

↓

queryByName()

↓

queryByPhone()

↓

insert()

↓

update()

↓

delete()

页面:

调用:


const list =
await ContactService.queryByName("张");

即可。


十八、Mapper 转换

Contacts Kit:

返回:


System Contact

业务:

使用:


ContactModel

中间:

增加:


Mapper

流程:


System Contact

↓

Mapper

↓

Business Contact

后期:

接口:

变化:

只改:

Mapper。


十九、大数据优化

如果:

联系人:


50000

推荐:


分页

↓

懒加载

↓

缓存

↓

异步解析

↓

增量刷新

不要:


主线程

↓

解析50000对象

否则:

页面:

一定:

卡顿。


二十、异步读取

推荐:


读取联系人

↓

后台线程

↓

转换对象

↓

主线程刷新UI

不要:


UI线程

↓

查询数据库

↓

解析联系人

↓

刷新页面

这样会导致界面掉帧,影响用户体验。


二十一、企业级架构示意图


                   UI
                    │
                    ▼
              ContactViewModel
                    │
                    ▼
            ContactRepository
             ┌──────────────┐
             │              │
             ▼              ▼
      Contacts Kit      Local Cache
             │              │
             └──────┬───────┘
                    ▼
              ContactMapper
                    ▼
              ContactModel

这种架构具有以下优点:

  • UI 不依赖系统 API。
  • 支持缓存与离线数据。
  • 方便切换数据源(系统通讯录、企业服务器等)。
  • 易于测试和维护。

二十二、常见性能问题

问题 原因 建议
打开通讯录很慢 一次查询全部联系人 使用分页加载
搜索卡顿 每输入一个字符都查询 使用防抖(Debounce)
页面掉帧 主线程解析大量数据 放到后台线程处理
内存占用高 一次加载全部头像 按需加载头像,使用缓存
查询重复执行 每次进入页面都重新读取 建立本地缓存并按需刷新

二十三、面试高频问题

1. 为什么企业项目推荐使用 Repository?

答:

Repository 可以统一管理数据来源(系统通讯录、本地数据库、网络接口),降低 UI 与底层 API 的耦合,提高代码可维护性和可测试性。


2. 为什么不能一次读取所有联系人?

答:

大量联系人会占用较多内存,并增加对象创建和 UI 渲染开销。分页、懒加载和缓存可以有效降低内存压力,提高页面响应速度。


3. 联系人搜索为什么建议使用防抖?

答:

用户输入时如果每个字符都立即查询,会产生大量无效查询。使用约 300ms 的防抖,可以显著减少查询次数,提高性能和用户体验。

十六、HarmonyOS Contacts Kit 联系人增删改(CRUD)实战

前言

联系人管理不仅仅是"读取联系人"。

企业 OA、CRM、企业通讯录、手机助手、数据迁移等应用,还需要支持:

  • 新增联系人
  • 修改联系人
  • 删除联系人
  • 批量导入
  • 数据同步

需要注意: Contacts Kit 的写操作通常需要相应权限,普通应用一般无法直接修改系统通讯录。本章主要介绍 Contacts Kit 的数据管理思路、企业级设计方式以及面试重点。


一、联系人 CRUD 流程

整个联系人管理流程如下:


UI

↓

ViewModel

↓

Repository

↓

ContactService

↓

Contacts Kit

↓

系统通讯录数据库

所有的联系人操作:


新增

修改

删除

最终都会落到:


Contacts Kit

完成数据写入。


二、新增联系人流程

新增联系人并不是:


new Contact()

↓

保存

而是:


创建联系人对象

↓

填写联系人信息

↓

参数校验

↓

写入数据库

↓

返回 Contact ID

通常:


成功

↓

生成新的联系人ID

以后:

所有修改:

都依赖:


Contact ID

三、新增联系人数据模型

例如:


export class ContactModel {

    id:string=""

    name:string=""

    phones:string[]=[]

    emails:string[]=[]

    addresses:string[]=[]

    company:string=""

    note:string=""

}

新增时:

通常:


ID为空

保存成功:

系统:


自动生成ID

四、联系人校验

企业项目:

不要直接:


保存

建议:

增加:


Validator

例如:


Contact

↓

Validator

↓

合法

↓

保存

检查内容:


姓名不能为空

手机号是否合法

邮箱是否合法

重复联系人检查

公司名称长度

备注长度

例如:


if(model.name==""){

    return

}

避免:


空联系人

写入数据库。


五、手机号校验

推荐:

统一工具类:


PhoneUtils

例如:


PhoneUtils.isMobile(phone)

判断:


13800138000

√

12345

×

abcdef

×

避免:

错误号码。


六、邮箱校验

例如:


abc@qq.com

√

hello@gmail.com

√

123@

×

@@@@

×

企业:

建议:

统一:


Regex

校验。


七、重复联系人检测

很多 App:

都会:


导入联系人

例如:


张三

13800138000

已经存在。

此时:

不要:

再次保存。

推荐:


手机号

↓

查数据库

↓

存在

↓

更新

不存在:


新增

八、联系人修改流程

修改:

实际上:

就是:


ID

↓

找到联系人

↓

修改字段

↓

保存

而不是:


姓名

↓

查找

例如:


Contact ID

↓

1001

↓

更新手机号

九、为什么不能根据姓名更新?

例如:

通讯录:


张三

张三

张三

三个:

同名。

如果:


update("张三")

到底:

更新哪个?

所以:

必须:


Contact ID

唯一。


十、修改联系人最佳实践

例如:

错误:


重新创建联系人

↓

覆盖

推荐:


查询联系人

↓

修改字段

↓

提交修改

这样:

不会:

丢失:


头像

生日

备注

网站

IM

等其它字段。


十一、删除联系人

删除流程:


联系人ID

↓

Contacts Kit

↓

删除数据库

↓

成功

例如:


ID=10001

↓

Delete

↓

联系人消失

删除:

建议:

弹窗:


确认删除?

取消

确定

不要:

直接删除。


十二、批量删除

例如:

企业:


通讯录

↓

多选

↓

删除

流程:


选中

↓

ID列表

↓

循环删除

不要:


一个联系人

↓

刷新一次UI

推荐:


全部删除

↓

统一刷新

性能:

更好。


十三、批量导入联系人

例如:

CSV:


姓名

手机号

邮箱

导入:


CSV

↓

解析

↓

ContactModel

↓

Validator

↓

保存

建议:

不要:


解析

↓

立即保存

推荐:

先:


全部校验

避免:

导入:

一半:

失败。


十四、联系人同步

企业:

经常:


服务器

↓

联系人

↓

手机

流程:


Server

↓

JSON

↓

Mapper

↓

ContactModel

↓

Contacts Kit

反向:


手机

↓

联系人

↓

JSON

↓

上传

形成:

双向同步。


十五、冲突处理

例如:

服务器:


手机号:

1380000

手机:


1389999

怎么办?

一般:

采用:


最后修改时间

例如:


Server

2026

↓

Phone

2025

↓

Server覆盖

或者:


提示用户:

保留哪个?

十六、联系人去重

很多:

CRM:

都会:


张三

13800138000

↓

再次导入

↓

重复

建议:

唯一键:


手机号

+

邮箱

不要:


姓名

因为:

姓名:

重复率:

太高。


十七、事务(Transaction)

批量:

新增:

1000 联系人。

错误:


保存一条

提交一次

推荐:


开启事务

↓

保存1000条

↓

统一提交

优点:


速度快

失败可回滚

数据一致

十八、Repository 实现

建议:


ContactRepository

↓

insert()

↓

update()

↓

delete()

↓

query()

页面:

永远:

不要:

直接:


Contacts Kit

例如:


await repository.insert(contact)

即可。


十九、Service 封装

例如:


ContactService

↓

save()

↓

update()

↓

remove()

↓

sync()

↓

import()

↓

export()

以后:

如果:

Contacts Kit:

API:

变化。

修改:


Service

即可。


二十、Undo(撤销)

很多:

系统通讯录:

删除:

并不是:

立即:


物理删除

推荐:


删除

↓

缓存30秒

↓

撤销

↓

恢复

例如:


联系人删除成功

【撤销】

用户体验:

更好。


二十一、批量操作优化

错误:


1000联系人

↓

刷新1000次UI

推荐:


1000联系人

↓

全部处理

↓

刷新一次

ArkUI:

性能:

提升:

非常明显。


二十二、企业项目架构

推荐:


UI

↓

ViewModel

↓

Repository

↓

Service

↓

Validator

↓

Mapper

↓

Contacts Kit

新增:

修改:

删除:

统一:

走:

Repository。


二十三、异常处理

例如:

保存失败:


Permission denied

处理:


提示:

当前应用

没有联系人写入权限

例如:

联系人不存在:


Contact Not Found

处理:


重新同步

或者

刷新联系人

不要:

直接:

崩溃。


二十四、性能优化

联系人:


10000条

新增:

不要:


循环:

insert()

推荐:


批量提交

事务

后台线程

删除:

同样:


批量删除

不要:

每删:

一条:

刷新一次页面。


二十五、面试高频问题

1、为什么修改联系人必须使用 Contact ID?

答:

因为姓名、手机号等字段都可能重复或发生变化,而 Contact ID 是系统生成的唯一标识,能够准确定位联系人,因此更新、删除等操作都应基于 Contact ID,而不是姓名。


2、为什么企业项目要增加 Validator 层?

答:

Validator 用于统一校验联系人数据,例如姓名、手机号、邮箱格式、重复联系人等,避免无效数据进入数据库,提高代码复用性,也便于后期维护。


3、为什么推荐 Repository + Service 双层设计?

答:

Repository 负责数据访问和数据源切换,Service 负责业务逻辑封装。这样可以降低 UI 与 Contacts Kit 的耦合,方便后续扩展到网络同步、本地缓存等场景,也更符合企业级架构设计。


本章总结

本章重点介绍了 Contacts Kit 在联系人 增删改(CRUD) 场景中的设计思路,包括:

  • 联系人新增流程
  • 修改与删除策略
  • 批量导入与同步
  • 数据校验(Validator)
  • 去重策略
  • Repository + Service 分层设计
  • 大批量联系人操作性能优化

这些内容不仅适用于通讯录开发,也是企业级 HarmonyOS 项目中常见的架构设计方式。

十七、HarmonyOS 通讯录企业级实战(完整通讯录 Demo 架构设计)

本章开始进入实战阶段。

前面的章节我们学习了:

  • Contacts Picker
  • 通讯录权限体系
  • Contact 数据模型
  • 查询联系人
  • 联系人 CRUD
  • Service / Repository 分层

本章将这些知识组合起来,设计一个完整的企业级通讯录模块。

最终实现:


通讯录 App

├── 联系人列表
├── 搜索联系人
├── A-Z 索引导航
├── 联系人详情
├── 编辑联系人
├── 新增联系人
├── 删除联系人
├── 联系人缓存
└── 数据同步

一、企业通讯录整体架构

一个成熟 HarmonyOS 应用,不推荐:


Page

↓

Contacts Kit

这种写法。

原因:

页面直接依赖系统 API:

导致:

  • 难测试
  • 难维护
  • 难扩展
  • API变化影响大

推荐架构:


                 UI层
                  │
                  ▼
            ContactPage
                  │
                  ▼
            ViewModel层
                  │
                  ▼
        ContactRepository层
                  │
        ┌─────────┴─────────┐
        ▼                   ▼
 ContactService        LocalCache
        │                   │
        ▼                   ▼
 Contacts Kit        Preferences/Database
        │
        ▼
 System Contacts

二、项目目录设计

推荐结构:


entry
│
├── pages
│   │
│   ├── ContactPage.ets
│   ├── ContactDetailPage.ets
│   └── ContactEditPage.ets
│
├── viewmodel
│   │
│   └── ContactViewModel.ets
│
├── repository
│   │
│   └── ContactRepository.ets
│
├── service
│   │
│   └── ContactService.ets
│
├── model
│   │
│   └── ContactModel.ets
│
├── mapper
│   │
│   └── ContactMapper.ets
│
├── utils
│   │
│   ├── PhoneUtils.ets
│   └── SearchUtils.ets
│
└── cache
    │
    └── ContactCache.ets

三、联系人业务模型设计

不要直接使用系统 Contact。

定义自己的模型:


export class ContactModel {


  id:string = ""


  name:string = ""


  avatar:string = ""


  phones:string[] = []


  emails:string[] = []


  company:string = ""


  department:string = ""


  note:string = ""


}

为什么?

因为:

系统对象:


Contact

属于:


系统层

而:


ContactModel

属于:


业务层

二者不要混合。


四、ContactMapper设计

负责:

系统对象转换。

结构:


Contacts Kit

↓

Contact

↓

Mapper

↓

ContactModel

示例:


export class ContactMapper {


 static convert(data:any):ContactModel{


    let model =
    new ContactModel()


    model.id =
    data.id


    model.name =
    data.name


    model.avatar =
    data.portraitUri


    model.phones =
    data.phoneNumbers


    model.emails =
    data.emails


    return model

 }

}

五、ContactService设计

Service负责:

直接操作 Contacts Kit。

例如:


export class ContactService {


 async queryAll(){


 }


 async queryById(id:string){


 }


 async insert(contact:ContactModel){


 }


 async update(contact:ContactModel){


 }


 async delete(id:string){


 }


}

业务页面:

不知道:

Contacts Kit。

只知道:


ContactService

六、Repository设计

Repository:

负责:

数据管理。

例如:


export class ContactRepository {


 private service =
 new ContactService()



 async getContacts(){


    return await
    this.service.queryAll()

 }



}

以后:

加入缓存:

只改这里。

例如:


Repository

↓

先查缓存

↓

没有

↓

查系统通讯录

↓

更新缓存

七、ViewModel设计

ViewModel负责:

页面状态。

例如:


export class ContactViewModel {


 contacts:ContactModel[]=[]



 async load(){


    this.contacts =
    await repository.getContacts()


 }


}

页面:

只负责显示。


八、通讯录列表页面设计

效果:


--------------------------------

🔍 搜索联系人


A

Alice

Andy


B

Bob


C

Charlie


--------------------------------

数据:


@State
contacts:ContactModel[]=[]

List:


List(){

 ForEach(
 this.contacts,
 item=>{

   ListItem(){

      Text(item.name)

   }

 })

}

九、联系人头像加载

不要:

直接:


Image(url)

大量联系人:

会导致:


卡顿

推荐:

缓存:


头像URI

↓

ImageCache

↓

Image

例如:


Image(
 ContactCache.getAvatar(item.id)
)

十、A-Z 字母索引实现

企业通讯录常见:

右侧:


A

B

C

D

...

Z

实现思路:

联系人:

增加:


firstLetter

例如:


张三

↓

Z

模型:

增加:


firstLetter:string

排序:


A

B

C

...

Z

十一、拼音首字母转换

中文:

需要:


中文

↓

拼音

↓

首字母

例如:


张三

↓

ZHANG SAN

↓

Z

工具类:


class PinYinUtils{


 static getFirstLetter(
 name:string
 ){

 }


}

十二、联系人搜索

搜索:

不要直接:

每次输入:

查询。

错误:


输入:

张

查询一次

输入:

张三

再次查询

推荐:

防抖:


输入

↓

等待300ms

↓

没有继续输入

↓

执行搜索

十三、SearchUtils

封装:


class SearchUtils{


 static match(
 keyword:string,
 name:string
 ){


 }


}

支持:


张

↓

张三


zs

↓

张三

十四、联系人详情页

点击:

列表:

进入:


ContactDetailPage

显示:


头像

姓名

公司

手机号

邮箱

地址

备注

页面参数:

不要传:

整个对象。

推荐:

传:


id

例如:


/detail?id=10001

然后:

重新查询。


为什么?

因为:

联系人可能变化。


十五、联系人编辑页面

结构:


姓名

手机号

邮箱

公司

备注


保存

保存流程:


点击保存

↓

ViewModel

↓

Validator

↓

Repository

↓

Service

↓

Contacts Kit

↓

刷新列表

十六、状态管理设计

推荐:

页面:

不要自己维护:

多个变量。

例如:

错误:


name

phone

email

list

散落。


推荐:

统一:


ContactState

例如:


class ContactState{


 loading:boolean=false


 list:ContactModel[]=[]


 keyword:string=""


}

十七、加载状态

联系人读取:

可能耗时。

因此:

增加:


Loading

状态。

流程:


进入页面

↓

loading=true

↓

读取联系人

↓

loading=false

↓

显示列表

UI:


加载中...

或者

空状态

十八、错误状态

例如:

权限失败:

显示:


无法读取联系人

请检查权限

网络同步失败:

显示:


同步失败

点击重试

不要:

直接:

Toast错误。


十九、空数据处理

联系人为空:

不要:

白屏。

显示:


暂无联系人

点击添加

二十、万级联系人性能方案

如果:

10000联系人。

必须:

使用:

1. 分页


50条

↓

下一页

2. 懒加载


滑动到底

↓

加载更多

3. 图片缓存

头像:

按需加载。


4. 数据缓存

第一次:

读取:

保存:


Database

之后:

优先:

本地。


二十一、完整数据流

最终:

一次打开通讯录:

流程:


ContactPage

↓

ContactViewModel.load()

↓

Repository.getContacts()

↓

Cache查询

↓

ContactService

↓

Contacts Kit

↓

ContactMapper

↓

ContactModel

↓

State更新

↓

ArkUI刷新

二十二、企业级最佳实践总结

推荐:

✅ 页面不直接调用 Contacts Kit

✅ 使用 Service 封装系统能力

✅ 使用 Repository 管理数据

✅ 使用 Mapper 隔离系统模型

✅ 使用 ViewModel 管理状态

✅ 使用缓存提升性能

✅ 使用分页处理大量联系人

✅ 使用防抖优化搜索


二十三、本章面试重点

Q1:为什么不要在页面直接调用 Contacts Kit?

因为页面直接依赖系统 API,会导致代码耦合严重,不利于维护和测试。应该通过 Service 和 Repository 隔离底层实现。


Q2:为什么详情页推荐传 ID 而不是整个对象?

因为联系人数据可能发生变化,传 ID 后进入详情页重新查询,可以保证数据最新,同时减少页面参数传递。


Q3:万级联系人如何优化?

主要通过:

  • 分页加载
  • 懒加载
  • 头像缓存
  • 数据缓存
  • 异步读取
  • 防抖搜索

降低内存和渲染压力。

十八、HarmonyOS 通讯录 A-Z 索引 + 搜索性能优化实战

本章进入通讯录项目中最常见、也是最容易出现性能问题的部分:

  • 联系人列表
  • 中文拼音排序
  • A-Z 快速定位
  • 搜索过滤
  • 万级联系人性能优化

微信、企业微信、钉钉、飞书等通讯录类应用,都大量使用类似方案。


一、为什么通讯录需要 A-Z 索引?

普通 List:


张三

李四

王五

赵六

当联系人:


10000+

用户查找:

非常困难。

因此通讯录通常设计:


左侧:

联系人列表


右侧:

A

B

C

D

...

Z

用户点击:


M

立即跳转:


M

↓

马某

梅某

孟某

二、通讯录数据结构设计

原始联系人:


{
 name:"张三"
}

不足。

需要增加:


首字母

例如:


{
 name:"张三",

 firstLetter:"Z"
}

完整模型:


export class ContactModel {


 id:string=""


 name:string=""


 firstLetter:string=""


 phones:string[]=[]


 avatar:string=""


}

三、中文姓名转拼音

例如:

输入:


张三

转换:


ZHANG SAN

然后:

取:


Z

作为索引。


流程:


中文姓名

↓

拼音转换

↓

大写

↓

首字符

↓

索引

例如:


王五

↓

WANG WU

↓

W

四、为什么首字母需要缓存?

很多开发者会这样:


List显示

↓

每次计算拼音

例如:

10000联系人:

每次刷新:


10000次拼音转换

性能非常差。


正确:

首次加载:


联系人

↓

计算firstLetter

↓

保存

以后:

直接:


contact.firstLetter

五、联系人分组模型

为了快速显示:

推荐:

转换成:


Map

结构:


A

 ├ Alice
 └ Andy


B

 ├ Bob


C

 ├ Charlie

代码:


Map<string,ContactModel[]>

例如:


{
 A:[
   Alice,
   Andy
 ],

 B:[
   Bob
 ]
}

六、生成分组数据

工具方法:


function groupContacts(
 list:ContactModel[]
){

 let map =
 new Map<string,ContactModel[]>()

 list.forEach(item=>{


   let key =
   item.firstLetter


   if(!map.has(key)){

      map.set(
       key,
       []
      )

   }


   map.get(key)?.push(item)


 })

 return map

}

结果:


A

B

C

D

七、List嵌套Section设计

通讯录页面:

通常:


List

↓

Section

↓

ListItem

结构:


List

 A

  Alice

  Andy


 B

  Bob

ArkUI:

类似:


List(){

 ForEach(
 sections,
 section=>{


   Text(section.title)


   ForEach(
    section.items,
    item=>{


    })

 })

}

八、右侧索引栏设计

UI:


A

B

C

D

E

...

Z

通常:


Column()

实现。


例如:


Column(){

 ForEach(
 letters,
 letter=>{


 Text(letter)

 })

}

九、点击字母跳转

核心:


点击字母

↓

找到对应Section

↓

List滚动

需要:

保存:


letter

↓

index

映射。

例如:


{
 A:0,

 B:20,

 C:35
}

十、List滚动控制

使用:


ListScroller

保存:


let scroller =
new Scroller()

点击:


B

执行:


scroller.scrollToIndex(20)

效果:


B

↓

立即定位

十一、搜索功能设计

通讯录搜索:

一般支持:

中文搜索

输入:


匹配:


张三
张伟
张强

拼音搜索

输入:


zs

匹配:


张三

手机号搜索

输入:


138

匹配:


13800138000

十二、搜索数据结构优化

不要:

每次:

遍历:


10000联系人

推荐建立:

搜索字段:


class SearchContact{


 name:string

 pinyin:string

 phone:string

}

例如:

张三:


name:

张三


pinyin:

zhangsan


phone:

13800138000

搜索:

直接:


三个字段匹配

十三、防抖搜索实现

错误:


输入:

张

查询

张三

查询

张三丰

查询

产生:

大量无效查询。


正确:

等待:


300ms

流程:


输入

↓

启动Timer

↓

300ms

↓

执行搜索

伪代码:


let timer:number=0


function search(keyword:string){


 clearTimeout(timer)


 timer=setTimeout(()=>{


   doSearch(keyword)


 },300)


}

十四、Trie 前缀搜索优化

如果:

联系人:


10万

普通:


for循环

已经慢。


可以使用:

Trie树。

结构:


张

 |

三

 |

丰

搜索:

复杂度:


O(length)

例如:

输入:


zh

快速找到:


张
赵
周

十五、搜索索引建立

初始化:


联系人

↓

建立Index

↓

保存

例如:


Map<string,List<Contact>>

索引:

包括:


姓名

拼音

手机号

例如:


138

↓

张三


zs

↓

张三

十六、大数据 List 优化

万级联系人:

不要:


ForEach

全部渲染。


推荐:


LazyForEach

区别:

ForEach:


全部创建

LazyForEach:


显示多少

创建多少

例如:

10000联系人:

屏幕:

20条。

Lazy:

只创建:

20~40条。


十七、LazyForEach 示例

数据:


class ContactDataSource
implements IDataSource
{

}

页面:


LazyForEach(
 dataSource,
 item=>{


 ListItem(){

 Text(item.name)

 }


})

优势:

明显降低:

  • 内存
  • 创建时间
  • UI压力

十八、头像优化

通讯录最大性能问题:

不是文字。

而是:

头像。


错误:


10000联系人

↓

加载10000头像

正确:


当前可见区域

↓

加载头像

策略:


懒加载

↓

缓存

↓

淘汰

十九、缓存策略

推荐:

LRU缓存。

结构:


最近使用

↓

保留

↓

不常用删除

例如:

缓存:


100个头像

超过:

删除最旧。


二十、联系人列表刷新策略

不要:

新增联系人:


重新读取全部

正确:

局部更新:


新增

↓

插入List

↓

刷新一个Item

删除:


删除Item

修改:


更新Item

二十一、搜索状态管理

推荐:

状态:


class ContactState{


 list:ContactModel[]=[]


 searchText:string=""


 loading:boolean=false


 empty:boolean=false


}

页面:

根据状态显示:


loading

↓

list

↓

empty

二十二、完整通讯录性能方案

最终方案:


联系人数据

↓

一次解析

↓

生成拼音

↓

建立索引

↓

缓存

↓

LazyForEach显示

↓

防抖搜索

↓

头像懒加载

二十三、性能指标参考

优秀通讯录:

应该达到:

操作 目标
打开列表 <500ms
搜索响应 <100ms
A-Z跳转 即时
滑动 60FPS
头像加载 无明显卡顿

二十四、常见面试题

1、为什么通讯录需要保存 firstLetter?

因为中文联系人无法直接按字母排序,通过拼音转换得到首字母,可以快速实现 A-Z 索引定位,提高大量联系人查找效率。


2、为什么使用 LazyForEach?

因为联系人数量可能达到几万条,LazyForEach 可以根据可视区域创建组件,避免一次性创建大量 UI 节点,提高性能。


3、搜索为什么需要建立索引?

如果每次搜索都遍历全部联系人,数据量大时性能下降。提前建立姓名、拼音、手机号索引,可以快速定位匹配联系人。


本章总结

本章完成了企业通讯录最核心的交互能力:

  • 中文拼音排序
  • A-Z索引
  • Section列表
  • 快速定位
  • 多字段搜索
  • 防抖优化
  • Trie搜索思想
  • LazyForEach性能优化
  • 头像缓存策略

这些技术不仅用于通讯录,也适用于:

  • 商品列表
  • 好友列表
  • 城市选择器
  • 企业组织架构
  • IM好友列表

十九、HarmonyOS 通讯录完整项目实战(联系人详情 + 编辑 + 新增页面)

本章开始实现真正的通讯录业务页面。

前面章节已经完成:

  • 联系人数据模型
  • Contacts Service
  • Repository架构
  • 列表展示
  • A-Z索引
  • 搜索优化

现在继续实现:

  • 联系人详情页
  • 联系人编辑页
  • 新增联系人
  • 数据回传
  • 页面刷新

一、页面结构设计

完整通讯录通常包含:


通讯录主页

        |
        |
        ↓

联系人详情页

        |
        |
        ↓

联系人编辑页

页面关系:


ContactPage

    |
    | 点击联系人
    |
    ↓

ContactDetailPage

    |
    | 点击编辑
    |
    ↓

ContactEditPage

二、Navigation 路由设计

HarmonyOS NEXT 推荐:


Navigation

+

RouterMap

+

页面级路由

不要:

大量使用:


router.push()

传统跳转。


推荐结构:


Navigation

├── ContactPage

├── ContactDetailPage

└── ContactEditPage

三、联系人主页跳详情

列表:


张三

李四

王五

点击:


张三

携带:


contactId

进入:


DetailPage

为什么传ID?

因为:

联系人数据可能变化。

错误:


params.contact = contact

问题:

进入详情:

数据可能已经过期。


正确:


params.id="10001"

详情页:

重新查询。


四、详情页面结构

效果:


--------------------

       头像

       张三


手机号

13800138000


邮箱

xxx@qq.com


公司

Huawei


备注

重要客户


        编辑

--------------------

五、ContactDetailPage 状态设计


@Component
export struct ContactDetailPage {


 @State
 contact:
 ContactModel =
 new ContactModel()


}

进入页面:

执行:


加载联系人

流程:


页面创建

↓

获取id

↓

Repository查询

↓

更新State

↓

UI刷新

六、详情页加载联系人

示例:


async loadData(){


 let result =
 await repository
 .queryById(this.id)


 this.contact=result


}

注意:

不要:

页面里面:

直接:


Contacts Kit

保持分层。


七、详情页UI实现

例如:


Column({space:20}){


 Image(
 this.contact.avatar
 )


 Text(
 this.contact.name
 )


 Text(
 this.contact.phones[0]
 )


 Button("编辑")


}

ArkUI特点:

修改:


this.contact

自动刷新。


八、编辑页面设计

编辑页面:

类似表单:


姓名

[ 张三 ]

手机号

[13800138000]


邮箱

[xxx@qq.com]


公司

[Huawei]


保存

数据:


@State
name:string=""

@State
phone:string=""

@State
email:string=""

九、为什么编辑页不用直接修改对象?

错误:


contact.name="李四"

问题:

用户点击返回:

数据已经改变。


推荐:

编辑页:

维护:


临时数据

保存:

再提交。

流程:


ContactModel

↓

EditState

↓

用户修改

↓

点击保存

↓

提交Repository

十、表单初始化

进入编辑页:


aboutToAppear(){


 this.name =
 this.contact.name


 this.phone =
 this.contact.phones[0]


}

效果:

打开:

显示原数据。


十一、输入框设计

例如:

姓名:


TextInput({
 placeholder:"请输入姓名"
})
.onChange(value=>{


 this.name=value


})

手机号:


TextInput({
 placeholder:"手机号"
})

邮箱:


TextInput({
 placeholder:"邮箱"
})

十二、表单校验

保存之前:

必须:


校验

流程:


点击保存

↓

Validator

↓

通过

↓

保存

十三、ContactValidator设计

例如:


export class ContactValidator{


static validate(
model:ContactModel
):boolean{


 if(model.name==""){

   return false

 }


 if(model.phones.length==0){

   return false

 }


 return true


}


}

十四、手机号校验

例如:


static validatePhone(
phone:string
){


 let reg =
 /^1[3-9]\d{9}$/


 return reg.test(phone)


}

检查:


13800138000

√


123456

×

十五、邮箱校验

例如:


static validateEmail(
email:string
){


 let reg =
 /^[A-Za-z0-9]+@[A-Za-z0-9]+\.[A-Za-z]+$/


 return reg.test(email)


}

十六、保存联系人

点击:


保存

流程:


EditPage

↓

ViewModel

↓

Validator

↓

Repository

↓

ContactService

↓

Contacts Kit

代码:


async save(){


 let model =
 new ContactModel()


 model.name=this.name


 model.phones=[
   this.phone
 ]


 await repository.update(model)


}

十七、保存成功后的页面返回

保存:

成功:

需要:


返回详情页

同时:

通知:

列表刷新。


方案:

方案一

重新查询

简单:


返回页面

↓

aboutAppear

↓

重新加载

适合:

普通项目。


方案二

EventHub通知

企业项目:

常用。

流程:


编辑页

↓

发送事件

↓

详情页监听

↓

刷新

十八、EventHub刷新机制

例如:

事件:


contact_changed

编辑:


eventHub.emit(
"contact_changed"
)

列表:

监听:


eventHub.on(
"contact_changed",
()=>{

 reload()

}
)

优点:

页面解耦。


十九、新增联系人页面

新增:

与编辑:

区别:


编辑

↓

已有ID


新增

↓

没有ID

模型:

新增:


id=""

保存:

调用:


insert()

二十、新增流程


点击新增

↓

打开AddPage

↓

填写资料

↓

校验

↓

insert

↓

返回列表

↓

刷新

二十一、联系人删除

详情页:

按钮:


删除联系人

流程:


点击删除

↓

确认弹窗

↓

Repository.delete()

↓

返回列表

不要:

直接删除。


二十二、删除确认弹窗

推荐:


确定删除张三?

取消

删除

原因:

联系人数据:

属于重要个人信息。

误删成本高。


二十三、页面间数据通信总结

HarmonyOS 常用方式:

方式 适用
Navigation参数 页面传递数据
EventHub 页面通知
状态管理 共享状态
Repository 统一数据源

推荐:

详情:


传ID

刷新:


EventHub

数据:


Repository

二十四、完整业务链路

最终:

新增:


AddPage

↓

Validator

↓

Repository

↓

Service

↓

Contacts Kit

↓

EventHub

↓

List刷新

编辑:


Detail

↓

Edit

↓

Update

↓

EventHub

↓

Refresh

删除:


Detail

↓

Confirm

↓

Delete

↓

Back

↓

Refresh

二十五、企业级最佳实践

1. 不直接修改对象

使用:


Draft Model

保存时提交。


2. 所有写操作走Repository

不要:

页面:

调用:


insert()

3. 保存前统一校验

不要:

每个页面:

自己判断。


4. 页面只负责UI

业务:

放:


ViewModel

Service

Repository

二十六、本章面试问题

1、为什么详情页传ID而不是对象?

因为对象可能已经过期,传ID可以保证进入详情页后重新获取最新数据,同时减少参数传递。


2、为什么编辑页面需要临时状态?

避免用户编辑过程中直接修改原始数据。如果用户取消编辑,可以直接丢弃临时状态。


3、EventHub适合什么场景?

适合跨页面、跨组件的事件通知,例如联系人新增、修改、删除后通知其它页面刷新。


本章总结

本章完成了通讯录核心页面设计:

  • Navigation页面跳转
  • 联系人详情
  • 联系人编辑
  • 新增联系人
  • 删除联系人
  • 表单校验
  • EventHub刷新机制
  • MVVM业务流程

到这里,一个完整通讯录App的核心功能已经基本形成。

第二十章 HarmonyOS 通讯录数据同步与云端架构设计(企业级实战)

前面几章,我们已经完成了通讯录本地 CRUD 和页面开发。

但是在企业项目中,仅仅操作本地通讯录远远不够。

例如:

  • 企业OA
  • 企业微信
  • 飞书
  • CRM
  • ERP
  • 客户管理系统

都需要将本地通讯录云端通讯录进行同步。

本章重点讲解企业级通讯录同步架构,这是 HarmonyOS 企业开发和面试中的高频知识点。


一、为什么需要数据同步?

很多初学者认为:


通讯录

↓

读取

↓

显示

就结束了。

实际上企业项目的数据流通常如下:


                企业后台

                    │
                    ▼
            Contact API Server

          ↗               ↘

HarmonyOS App      Web管理后台

          ↘               ↗

              企业数据库

例如:

员工A修改手机号:


13800138000

↓

13812345678

那么:

  • 手机端要更新
  • Web后台要更新
  • 其它设备也要更新

否则:

数据就不一致。


二、通讯录同步模型

推荐采用:


Server

↓

JSON

↓

Repository

↓

Mapper

↓

ContactModel

↓

Contacts Kit

反向:


Contacts Kit

↓

ContactModel

↓

JSON

↓

Server

这样:

系统模型和服务器模型完全隔离。


三、为什么不要直接同步系统 Contact?

错误:


System Contact

↓

JSON

↓

上传

原因:

系统 Contact:

可能包含:

  • 系统字段
  • URI
  • 本地ID
  • 无意义字段

服务器:

根本不需要。

推荐:


Contact

↓

Mapper

↓

DTO

↓

JSON

四、DTO(Data Transfer Object)

企业项目:

一般都会增加:


ContactDTO

例如:


export class ContactDTO{

    id:string=""

    name:string=""

    phone:string=""

    email:string=""

}

为什么?

因为:

DTO:

只保存:

服务器需要的数据。


五、本地ID和服务器ID

很多新人:

容易混淆:


Contact ID

实际上:

企业项目:

通常:

两个ID。

例如:


Local ID

10001

↓

Server ID

A8F123456

不要:

混用。

推荐:


class ContactModel{

 localId:string=""

 serverId:string=""

}

六、同步流程设计

推荐:

如下流程:


本地修改联系人

↓

Repository

↓

加入同步队列

↓

后台同步

↓

服务器返回成功

↓

更新同步状态

而不是:


修改

↓

立即请求服务器

为什么?

网络:

可能:

断开。


七、同步状态设计

联系人:

增加:


syncStatus:number

例如:


0

已同步

-----------------

1

待同步

-----------------

2

同步失败

-----------------

3

同步中

以后:

UI:

即可显示:


☁

同步中...

八、离线模式

企业项目:

一定支持:


无网络

例如:

地铁:


没有信号

用户:

修改联系人:

仍然:

成功。

流程:


修改联系人

↓

本地数据库

↓

syncStatus=待同步

↓

恢复网络

↓

自动同步

用户:

几乎:

感觉不到:

网络问题。


九、同步队列(Sync Queue)

推荐:

建立:


SyncQueue

例如:


新增联系人

↓

Queue

↓

修改联系人

↓

Queue

↓

删除联系人

↓

Queue

后台:

依次:

处理。


模型:


class SyncTask{

 type:string

 contactId:string

}

十、为什么需要同步队列?

例如:

用户:

一分钟:

连续:

修改:


手机号

↓

姓名

↓

邮箱

如果:

立即:

请求:

服务器。

就会:

产生:


三个HTTP请求

推荐:

队列:


等待

↓

合并

↓

一次上传

性能:

提升:

很多。


十一、增量同步

错误:


同步

↓

10000联系人

每次:

都上传。


推荐:


最后修改时间

↓

变化联系人

↓

上传

例如:


昨天:

10000联系人

今天:

修改2个

↓

上传2个

效率:

极高。


十二、版本号设计

推荐:

联系人:

增加:


version:number

例如:


Version

1

↓

修改

↓

2

↓

修改

↓

3

服务器:

根据:

Version:

判断:

是否冲突。


十三、更新时间设计

模型:

增加:


updateTime:number

例如:


2026-07-10

15:20:18

同步:

依据:

更新时间。


十四、冲突解决

例如:

手机:

修改:


手机号

1380000

Web:

同时:

修改:


1389999

怎么办?


方案一:

最后修改:

优先。


updateTime

大的

覆盖

方案二:

服务器:

提示:


发现冲突

请选择

适合:

CRM。


十五、软删除

不要:

立即:


Delete

推荐:


deleted=true

例如:


class ContactModel{

 deleted:boolean=false

}

同步:

服务器:

之后:

真正删除。


十六、数据加密

联系人:

属于:

隐私数据。

推荐:

上传:


HTTPS

敏感字段(如手机号、邮箱)可以根据业务要求进行脱敏展示或在服务端进行加密存储。客户端是否需要额外加密,应结合实际安全方案设计。


十七、本地缓存

推荐:

保存:


Database

不要:

每次:


Contacts Kit

↓

重新查询

流程:


启动App

↓

本地数据库

↓

后台同步

↓

更新数据

用户:

打开:

更快。


十八、同步架构

企业推荐:


               UI

               │

               ▼

         ContactRepository

          │          │

          ▼          ▼

   Local Database   SyncManager

          │          │

          ▼          ▼

     Contacts Kit   REST API

          │          │

          └────┬─────┘

               ▼

             Server

十九、SyncManager设计

例如:


class SyncManager{


 sync()


 upload()


 download()


 resolveConflict()


}

以后:

所有:

同步:

统一:

管理。


二十、自动同步时机

推荐:

App:

以下情况:

同步:


启动

↓

恢复网络

↓

用户登录

↓

下拉刷新

↓

后台定时

不要:

每次:

打开页面:

同步。


二十一、大型企业架构

推荐:


UI

↓

ViewModel

↓

Repository

↓

Local DB

↓

SyncManager

↓

REST API

↓

Server

Contacts Kit:

只是:

其中:

一个:

数据源。


二十二、企业项目最佳实践

✅ 使用 Repository 管理数据

✅ 使用 SyncManager 管理同步

✅ 使用 DTO 与服务器通信

✅ 使用 Mapper 隔离模型

✅ 增量同步,不全量同步

✅ 使用同步队列减少请求

✅ 本地数据库优先

✅ 后台自动同步


二十三、面试高频问题

1、为什么企业项目需要 SyncManager?

答:

SyncManager 将同步逻辑集中管理,负责上传、下载、冲突处理、失败重试、增量同步等功能,避免同步逻辑分散在多个页面或业务模块中,提升系统可维护性。


2、为什么推荐增量同步?

答:

全量同步会带来大量网络流量和数据库操作,而增量同步只处理发生变化的数据,能够显著降低带宽消耗和同步时间,提升系统性能。


3、为什么需要本地数据库,而不是每次读取 Contacts Kit?

答:

直接访问系统通讯录可能存在性能开销,也不利于离线使用。本地数据库可以提供更快的查询速度,支持离线访问,并作为同步过程中的缓存层和数据源。


本章总结

本章完成了企业级通讯录云同步架构设计,包括:

  • 通讯录同步模型
  • DTO 设计
  • SyncManager 架构
  • 同步队列
  • 增量同步
  • 版本号管理
  • 冲突处理
  • 离线同步
  • 本地缓存
  • 企业级最佳实践

至此,我们已经覆盖了 HarmonyOS 通讯录开发的大部分核心知识,包括权限体系、Contacts Picker、Contacts Kit、数据模型、CRUD、MVVM 架构、列表优化以及企业级同步方案。

第二十一章 HarmonyOS Contacts Kit 常见异常、踩坑总结与最佳实践(完结篇)

到这里,整个 HarmonyOS 通讯录开发 已经基本完成。

本章将结合大量企业项目经验,总结 Contacts Kit 开发过程中最容易遇到的问题,以及企业级开发规范和面试高频考点。


一、Contacts Kit 开发全流程回顾

整个通讯录模块的数据流如下:


                        用户操作

                            │

                            ▼

                      ContactPage

                            │

                            ▼

                      ContactViewModel

                            │

                            ▼

                    ContactRepository

                   ┌─────────┴─────────┐
                   ▼                   ▼

             Local Database      ContactService

                                       │

                                       ▼

                                Contacts Kit

                                       │

                                       ▼

                                 System Contacts

整个架构遵循:

  • UI 与系统 API 解耦
  • Repository 统一数据入口
  • Service 封装系统能力
  • ViewModel 管理页面状态
  • Mapper 转换数据模型
  • Validator 校验业务数据

二、常见异常一:未申请权限

最常见的问题就是:


读取联系人失败

原因:

没有申请:


ohos.permission.READ_CONTACTS

或者:

用户点击:


拒绝

导致读取失败。

正确流程:


检查权限

↓

没有权限

↓

申请权限

↓

申请成功

↓

读取联系人

不要直接调用 Contacts Kit,否则很容易抛出权限异常。


三、常见异常二:用户永久拒绝权限

例如:


拒绝

↓

不再询问

此时:

再次申请:

不会弹框。

正确处理:


提示用户

↓

打开系统设置

↓

手动开启权限

不要无限循环申请权限。


四、常见异常三:联系人为空

很多开发者:

默认:

一定有联系人。

实际上:


新手机

↓

没有联系人

此时:

返回:


[]

不是:

异常。

页面:

应该:

显示:


暂无联系人

而不是:

程序崩溃。


五、常见异常四:头像为空

很多联系人:

没有头像。

例如:


portraitUri

↓

null

错误:


Image(contact.avatar)

容易导致:


图片加载失败

推荐:


if (contact.avatar == "") {

    显示默认头像

}

六、常见异常五:手机号为空

有的人:

只有:


姓名

没有:


手机号

例如:


联系人

↓

张三

↓

phone=[]

不要:

直接:


phones[0]

应该:

先判断:


phones.length > 0

七、常见异常六:邮箱为空

同理:

邮箱:

可能不存在。

推荐:

统一工具:


ContactFormatter

例如:


getFirstPhone()

getFirstEmail()

getAvatar()

统一处理空值。


八、常见异常七:联系人字段不完整

不同联系人:

字段:

完全不同。

例如:

联系人A:


姓名

手机号

联系人B:


姓名

邮箱

公司

地址

不要:

假设:

所有字段:

一定存在。


九、常见异常八:联系人重复

企业项目:

经常:

导入:

Excel:

CSV:

导致:


重复联系人

推荐:

建立:

唯一规则:


手机号

+

邮箱

不要:

只根据:

姓名。


十、常见异常九:大量联系人卡顿

例如:

联系人:


20000+

错误:


ForEach()

一次:

创建:

所有Item。

正确:


LazyForEach

配合:

分页。


十一、常见异常十:搜索卡顿

例如:

输入:


每输入:

一个字:

遍历:


10000联系人

推荐:

  • 防抖(Debounce)
  • 拼音缓存
  • Trie 前缀树(超大数据量)
  • 建立搜索索引

十二、Contacts Picker 与 Contacts Kit 如何选择?

很多开发者容易混淆:

场景 推荐
用户主动选择一个联系人 Contacts Picker
读取整个通讯录 Contacts Kit
企业通讯录同步 Contacts Kit
分享联系人 Contacts Picker
CRM 导入通讯录 Contacts Kit

简单来说:

  • Picker:用户主动选择
  • Kit:程序批量读取和管理

十三、Repository 为什么必不可少?

错误架构:


Page

↓

Contacts Kit

问题:

  • 页面无法复用
  • 无法缓存
  • 无法测试
  • API 修改影响所有页面

推荐:


Page

↓

ViewModel

↓

Repository

↓

Service

↓

Contacts Kit

Repository 统一管理:

  • 缓存
  • 网络
  • 数据库
  • Contacts Kit

真正做到:


一个入口

多个数据源

十四、Mapper 为什么重要?

系统:


Contact

服务器:


ContactDTO

页面:


ContactModel

三者:

不要混用。

推荐:


Contact

↓

Mapper

↓

Model

↓

DTO

解耦:

系统层:

业务层:

网络层。


十五、性能优化 Checklist

企业项目建议逐项检查:

优化项 是否建议
Repository 分层
ViewModel 管理状态
Mapper 转换模型
Validator 校验数据
LazyForEach
防抖搜索
拼音缓存
头像缓存
本地数据库缓存
增量同步
SyncManager
DTO 隔离网络模型

十六、安全与隐私建议

通讯录属于用户敏感信息,开发时建议遵循以下原则:

1. 最小权限原则

只申请真正需要的联系人权限。

不要因为未来可能用到而提前申请。


2. 数据最小化

例如:

服务器:

只需要:


姓名

手机号

不要上传:


生日

地址

备注

头像

除非业务明确需要。


3. 加密传输

所有联系人同步:

必须:


HTTPS

避免明文传输。


4. 不打印隐私日志

错误:


console.info(contact.phone)

发布版本:

不要输出:

手机号:

邮箱:

身份证:

等敏感信息。


5. 提供隐私说明

首次读取联系人前,应向用户说明:

  • 为什么需要通讯录权限
  • 将读取哪些数据
  • 数据如何使用
  • 是否上传服务器

提高用户信任度,也有助于满足应用市场审核要求。


十七、完整项目目录(推荐)


entry
├── pages
│   ├── ContactPage
│   ├── ContactDetailPage
│   ├── ContactEditPage
│   └── ContactAddPage
│
├── model
│   ├── ContactModel
│   ├── ContactDTO
│   └── ContactState
│
├── repository
│   └── ContactRepository
│
├── service
│   ├── ContactService
│   └── SyncManager
│
├── mapper
│   └── ContactMapper
│
├── validator
│   └── ContactValidator
│
├── cache
│   └── ContactCache
│
├── database
│   └── ContactDao
│
├── network
│   └── ContactApi
│
├── utils
│   ├── PhoneUtils
│   ├── SearchUtils
│   ├── PinYinUtils
│   └── PermissionUtils
│
└── common
    ├── Constant
    └── Logger

这种目录结构适合中大型 HarmonyOS 企业项目。


十八、30 道 HarmonyOS Contacts Kit 面试高频题(精选)

基础

  1. Contacts Kit 和 Contacts Picker 有什么区别?
  2. 如何申请通讯录权限?
  3. 如何读取联系人列表?
  4. 如何获取联系人手机号?
  5. 如何获取联系人头像?

进阶

  1. 如何设计 ContactModel?
  2. 为什么要使用 Repository?
  3. 为什么需要 Mapper?
  4. 为什么需要 Validator?
  5. 如何设计联系人缓存?

性能

  1. 为什么使用 LazyForEach?
  2. 如何优化万级联系人列表?
  3. 如何实现拼音搜索?
  4. 如何实现 A-Z 索引?
  5. 如何避免搜索卡顿?

企业

  1. 如何实现联系人同步?
  2. 什么是增量同步?
  3. 为什么需要 SyncManager?
  4. 如何处理同步冲突?
  5. 如何设计同步队列?

架构

  1. MVVM 在通讯录中的作用?
  2. Repository 为什么优于直接访问 Contacts Kit?
  3. 为什么要分层?
  4. 如何进行单元测试?
  5. 如何设计 DTO?

安全

  1. 如何保护联系人隐私?
  2. 为什么不能打印联系人日志?
  3. 如何进行权限说明?
  4. 如何设计最小权限原则?
  5. 如何满足应用市场隐私审核?

十九、全文知识体系总结

整个《HarmonyOS 获取通讯录信息》专题已经完整覆盖:

第一部分:基础能力

  • Contacts Picker 使用
  • Contacts Kit 基础
  • 权限申请
  • 权限检查
  • 权限异常处理

第二部分:联系人数据

  • Contact 数据模型
  • 联系人查询
  • 联系人详情
  • 联系人 CRUD
  • 字段解析

第三部分:企业开发

  • MVVM
  • Repository
  • Service
  • Mapper
  • Validator
  • DTO
  • 缓存

第四部分:性能优化

  • LazyForEach
  • 拼音排序
  • A-Z 索引
  • 搜索优化
  • 图片缓存
  • 防抖
  • 分页加载

第五部分:企业架构

  • 本地数据库
  • SyncManager
  • 增量同步
  • 冲突处理
  • 离线模式
  • 数据安全
  • 企业通讯录设计
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐