更新记录
1.0.0(2025-12-21)
新增功能
- 电话状态监听功能(来电中、通话中、空闲状态)
- 自动接听电话功能
- 挂断电话功能
- 电话号码回调功能
- 权限检查和请求功能
- Android 12+ 兼容性支持
技术实现
- 使用 TelephonyManager 监听电话状态
- 支持 Android 8.0+ 的 TelecomManager API
- 支持 Android 12+ 的 TelephonyCallback API
- 完整的错误处理和权限管理
平台兼容性
uni-app(4.61)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | 4.4 | 12 | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - |
hl-call-control-uts
电话状态监听与通话控制功能 UTS 插件
功能说明
本插件提供 Android / iOS 侧电话状态监听与通话控制能力,包括:
- 电话状态监听:实时监听来电、通话中、空闲状态
- 自动接听:支持自动接听来电(需权限支持,仅 Android 支持)
- 挂断电话:支持挂断当前通话(需权限支持,仅 Android 支持)
- 号码回调:回调来电号码和通话号码(iOS 无法获取来电号码)
- 权限管理:提供权限检查和请求功能
使用说明
导入插件
// #ifdef APP-PLUS
import { getHlCallControlUtsClient } from '@/uni_modules/hl-call-control-uts'
const client = getHlCallControlUtsClient()
// #endif
初始化
// 初始化客户端
client.initClient()
// 设置状态变化回调
client.onStateChanged((data) => {
console.log('电话状态变化:', data)
// data: { event: "CALL_STATE_CHANGED", state: "RINGING", phoneNumber: "+8613800138000", timestamp: 1732600000000 }
})
// 设置结果回调
client.onResult((result) => {
console.log('操作结果:', result)
// result: { code: 0, message: "success", data: {...} }
})
权限管理
// 检查权限
client.checkPermissions()
// 请求权限(会弹出系统权限对话框)
client.requestPermissions()
注意:在 Android 上,requestPermissions() 会弹出系统权限对话框。在 iOS 上,CoreTelephony 框架不需要显式权限请求,但无法获取来电号码或控制通话。
开始监听(后台服务)
// 开始监听电话状态(使用后台服务,应用关闭后仍可监听)
client.startListening()
注意:startListening() 会启动一个前台服务在后台持续监听电话状态,即使应用关闭也能继续工作。服务会在通知栏显示一个常驻通知。iOS 版本不需要前台服务,但监听功能在应用进入后台后可能受系统限制。
接听电话
// 自动接听来电
client.answerCall()
注意:仅 Android 支持此功能。iOS 10+ 后,无法通过公开 API 接听电话,需要使用 CallKit 框架(需要特殊配置和权限)。
挂断电话
// 挂断当前通话
client.endCall()
注意:仅 Android 支持此功能。iOS 10+ 后,无法通过公开 API 挂断电话,需要使用 CallKit 框架(需要特殊配置和权限)。
获取当前状态
// 获取当前电话状态
client.getCurrentState()
停止监听
// 停止监听电话状态
client.stopListening()
释放资源
// 在页面销毁时调用
client.onDestroy()
完整示例
<template>
<view class="container">
<view class="status">当前状态: {{ currentState }}</view>
<view class="phone-number">电话号码: {{ phoneNumber }}</view>
<button @click="startListen">开始监听</button>
<button @click="stopListen">停止监听</button>
<button @click="answer">接听</button>
<button @click="endCall">挂断</button>
<button @click="checkPerms">检查权限</button>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// #ifdef APP-ANDROID
import { getHlCallControlUtsClient } from '@/uni_modules/hl-call-control-uts/utssdk/app-android/index.uts'
// #endif
const client = getHlCallControlUtsClient()
const currentState = ref('IDLE')
const phoneNumber = ref('')
onMounted(() => {
// 初始化
client.initClient()
// 设置回调
client.onStateChanged((data) => {
currentState.value = data.state
phoneNumber.value = data.phoneNumber || ''
console.log('状态变化:', data)
})
client.onResult((result) => {
console.log('操作结果:', result)
if (result.code !== 0) {
uni.showToast({
title: result.message || '操作失败',
icon: 'none'
})
}
})
})
onUnmounted(() => {
client.onDestroy()
})
const startListen = () => {
client.startListening()
}
const stopListen = () => {
client.stopListening()
}
const answer = () => {
client.answerCall()
}
const endCall = () => {
client.endCall()
}
const checkPerms = () => {
client.checkPermissions()
}
</script>
状态说明
CallState 枚举
IDLE: 空闲状态(未在通话)RINGING: 来电中OFF_HOOK: 通话中(已接听)
回调数据格式
状态变化回调:
{
"event": "CALL_STATE_CHANGED",
"state": "RINGING",
"phoneNumber": "+8613800138000",
"timestamp": 1732600000000
}
结果回调:
{
"code": 0,
"message": "success",
"data": {
"action": "answerCall"
}
}
权限要求
Android 权限
READ_PHONE_STATE: 读取电话状态(必需,用于监听电话状态)CALL_PHONE: 拨打电话(用于挂断电话)ANSWER_PHONE_CALLS: 接听电话(Android 8.0+,用于自动接听)READ_CALL_LOG: 读取通话记录(用于获取电话号码)MODIFY_AUDIO_SETTINGS: 修改音频设置(用于接听和挂断电话时的音频控制)PROCESS_OUTGOING_CALLS: 处理拨出电话(Android 10-11,Android 12+ 已废弃)FOREGROUND_SERVICE: 前台服务(Android 9.0+,用于后台监听服务)
iOS 权限
- iOS 上的
CoreTelephony框架不需要显式权限声明。 - 重要:iOS 无法通过
CoreTelephony获取来电号码。 - 如果需要接听/挂断电话,需要集成
CallKit框架,这需要额外的配置和用户授权。
权限配置
权限已在 AndroidManifest.xml 中声明,但需要在运行时动态请求:
// 在 manifest.json 中配置
{
"app-plus": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.ANSWER_PHONE_CALLS\"/>",
"<uses-permission android:name=\"android.permission.READ_CALL_LOG\"/>",
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>"
]
}
}
}
}
运行时权限请求
// 使用 uni-app 的权限 API
uni.authorize({
scope: 'scope.record',
success() {
console.log('权限已授予')
client.startListening()
},
fail() {
console.log('权限被拒绝')
// 引导用户到设置页面
uni.showModal({
title: '需要权限',
content: '需要电话权限才能使用此功能',
confirmText: '去设置',
success(res) {
if (res.confirm) {
// 打开应用设置页面
}
}
})
}
})
注意事项
-
后台监听服务:
startListening()会启动一个前台服务,在通知栏显示常驻通知- 服务使用
START_STICKY模式,被系统杀死后会自动重启 - 应用关闭后服务仍会继续运行,确保电话状态监听不中断
- 需要停止监听时调用
stopListening()停止服务
-
自动接听限制:
- Android 8.0 之后系统对自动接听权限收紧
- 部分厂商(如小米、华为)可能限制自动接听功能
- 需要用户手动授予
ANSWER_PHONE_CALLS权限 - 某些设备可能需要 Root 权限才能实现自动接听
- 接听前会检查当前状态是否为
RINGING,避免误操作
-
系统版本兼容:
- Android 12+ 使用新的
TelephonyCallbackAPI - Android 12 以下使用
PhoneStateListenerAPI - 插件已自动处理版本兼容
- 后台服务会根据系统版本自动选择正确的监听方式
- Android 12+ 使用新的
-
电话号码获取:
- Android:
- Android 12+ 由于隐私限制,可能无法直接获取来电号码
- 需要通过
READ_CALL_LOG权限或CallScreeningService获取 - 建议在 Android 12+ 上使用系统提供的来电拦截服务
- 当前实现中,Android 12+ 的电话号码可能为
null
- iOS:
- iOS 系统限制:无法通过公开 API 获取真实来电号码
CTCall.callID返回的是通话的唯一标识符(UUID),不是电话号码- 这是 iOS 系统的隐私保护机制,无法绕过
- 因此 iOS 版本的回调中
phoneNumber字段始终为null或空字符串
- Android:
-
权限获取:
- 部分权限需要在系统设置中手动授予
- 建议在应用启动时检查并引导用户授予权限
ANSWER_PHONE_CALLS权限需要在系统设置中手动授予- 前台服务需要
FOREGROUND_SERVICE权限(Android 9.0+)
-
错误处理:
- 所有操作都会通过
onResult回调返回结果 code: 0表示成功,非 0 表示失败- 失败时会返回具体的错误信息
- 接听/挂断前会检查当前状态,避免无效操作
- 所有操作都会通过
-
资源释放:
- 在页面销毁时务必调用
onDestroy()释放资源 - 停止监听时会自动停止后台服务
- 广播接收器会在
onDestroy()时自动注销
- 在页面销毁时务必调用
-
厂商定制限制:
- 部分厂商(小米、华为、OPPO、vivo 等)对电话控制功能有额外限制
- 可能需要用户手动在系统设置中授予相关权限
- 某些功能可能需要在系统设置中开启"后台运行"或"自启动"权限
- 部分厂商可能限制后台服务运行,需要在系统设置中设置白名单
-
iOS 系统限制:
- 电话号码获取:iOS 系统出于隐私保护,不允许第三方应用获取真实来电号码。
CTCall.callID返回的是 UUID,不是电话号码。 - 接听/挂断电话:
- CallKit 只能控制 VoIP 通话(如 FaceTime、WhatsApp、Skype 等),不能控制普通电话(PSTN)
- 对于普通电话,iOS 系统不允许第三方应用接听或挂断
- 这是 iOS 系统的安全机制,无法绕过
- 如果尝试控制普通电话,会返回错误:
iOS system restriction: CallKit can only control VoIP calls, not regular phone calls (PSTN)
- 后台监听:iOS 应用进入后台后,
CTCallCenter的监听可能会被系统限制或暂停
- 电话号码获取:iOS 系统出于隐私保护,不允许第三方应用获取真实来电号码。
-
生产环境建议:
- 建议在应用启动时检查所有必要权限
- 引导用户授予权限,特别是
ANSWER_PHONE_CALLS(Android) - 监控服务运行状态,处理服务异常情况
- 记录关键操作日志,便于问题排查
- 测试不同厂商设备的兼容性
- iOS 用户:明确告知用户 iOS 系统的限制,电话号码无法获取,接听/挂断仅支持 VoIP 通话
错误码说明
0: 操作成功-1: 操作失败(具体原因见 message 字段)
常见错误:
no context: 上下文为空permission denied: 权限未授予telephonyManager is null: 电话管理器初始化失败already listening: 已在监听状态not listening: 未在监听状态
iOS VoIP 通话测试指南
什么是 VoIP 通话?
VoIP(Voice over Internet Protocol)是通过互联网进行语音通话的技术,与传统的电话网络(PSTN)不同。常见的 VoIP 应用包括:
- FaceTime(苹果自带,iOS 设备之间)
- WhatsApp(需要集成 CallKit)
- Skype(需要集成 CallKit)
- Zoom(需要集成 CallKit)
- Microsoft Teams(需要集成 CallKit)
- 其他支持 CallKit 的 VoIP 应用
重要说明
⚠️ 重要限制:
- CallKit 只能控制已经集成 CallKit 框架的 VoIP 应用的通话
- 如果 VoIP 应用没有集成 CallKit,我们的插件也无法控制
- 普通电话(PSTN)完全无法控制,这是 iOS 系统限制
测试方法
方法 1:使用 FaceTime(推荐,最简单)
FaceTime 是苹果自带的 VoIP 应用,天然支持 CallKit:
- 准备两台 iOS 设备(或一台设备 + 模拟器)
- 在两台设备上登录不同的 Apple ID(或使用同一 Apple ID)
- 在测试设备上启动应用,调用
startListening() - 从另一台设备拨打 FaceTime 视频或语音通话
- 观察测试设备:
- 应该能检测到
RINGING状态 - 尝试调用
answerCall()应该能接听 - 尝试调用
endCall()应该能挂断
- 应该能检测到
注意事项:
- FaceTime 需要两台设备都连接到互联网
- 需要确保 FaceTime 功能已启用(设置 > FaceTime)
方法 2:使用支持 CallKit 的第三方应用
- 安装支持 CallKit 的 VoIP 应用(如 WhatsApp、Skype 等)
- 确保应用已集成 CallKit 框架(不是所有 VoIP 应用都支持)
- 在测试设备上启动应用,调用
startListening() - 从另一台设备拨打 VoIP 通话
- 观察测试设备是否能检测和控制通话
注意事项:
- 不是所有 VoIP 应用都支持 CallKit
- 需要查看应用的文档确认是否支持 CallKit
- 某些应用可能需要特殊配置才能使用 CallKit
方法 3:使用 Xcode 模拟器测试(有限支持)
- 在 Xcode 中运行应用
- 使用模拟器的电话功能(但模拟器可能不支持完整的 CallKit 功能)
- 使用其他设备拨打 FaceTime 到模拟器
注意事项:
- 模拟器对 CallKit 的支持有限
- 建议使用真机测试
测试步骤
-
初始化插件:
client.initClient() client.onStateChanged((data) => { console.log('状态变化:', data) }) client.onResult((result) => { console.log('操作结果:', result) }) -
开始监听:
client.startListening() -
触发 VoIP 通话:
- 从另一台设备拨打 FaceTime 或其他 VoIP 通话
- 或从测试设备拨打 VoIP 通话
-
观察状态变化:
- 应该收到
CALL_STATE_CHANGED事件 - 状态应该是
RINGING(来电中)
- 应该收到
-
测试接听:
client.answerCall()- 如果成功,应该收到
code: 0的结果 - 如果失败,会收到详细的错误信息
- 如果成功,应该收到
-
测试挂断:
client.endCall()- 如果成功,应该收到
code: 0的结果 - 如果失败,会收到详细的错误信息
- 如果成功,应该收到
调试技巧
-
查看日志:
- 在 Xcode 控制台查看
HlCallControl:开头的日志 - 观察 CallKit 是否能检测到通话
- 在 Xcode 控制台查看
-
检查 CallKit 状态:
- 在代码中添加日志,查看
callObserver?.calls的内容 - 如果为空,说明 CallKit 没有检测到 VoIP 通话
- 在代码中添加日志,查看
-
测试不同场景:
- 测试来电接听
- 测试拨出通话挂断
- 测试通话中挂断
常见问题
Q: 为什么无法控制普通电话? A: 这是 iOS 系统的安全限制,普通电话(PSTN)无法被第三方应用控制。
Q: 为什么某些 VoIP 应用无法控制? A: 只有集成了 CallKit 框架的 VoIP 应用才能被控制。不是所有 VoIP 应用都支持 CallKit。
Q: 如何确认 VoIP 应用是否支持 CallKit? A: 查看应用的文档或联系应用开发者。通常,如果应用在锁屏界面显示通话界面,说明它可能支持 CallKit。
Q: FaceTime 一定能工作吗? A: FaceTime 是苹果自带的,天然支持 CallKit,是最可靠的测试方法。
测试检查清单
- [ ] 已初始化插件并设置回调
- [ ] 已调用
startListening()开始监听 - [ ] 使用 FaceTime 或其他支持 CallKit 的 VoIP 应用
- [ ] 能够检测到
RINGING状态 - [ ] 能够成功接听 VoIP 通话(如果支持)
- [ ] 能够成功挂断 VoIP 通话(如果支持)
- [ ] 查看日志确认 CallKit 是否检测到通话

收藏人数:
购买普通授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 271
赞赏 2
下载 12419757
赞赏 1829
赞赏
京公网安备:11010802035340号