概述
CallSDK 是一款 Android 通话 SDK,提供通话初始化、拨号、通话控制(挂点/ 静音 / 扬声器 / DTMF)、外显号码配置等核心功能。
核心特性
- 支持音频通话(SIP 协议)
- 外显号码组 / 号码配置
- 通话状态监听( 接听 / 挂断 )
- 通话控制(静音、扬声器、DTMF 发送)
环境准备
依赖配置
权限声明
在 AndroidManifest.xml 中添加必要权限:
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 音频/通话权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />注意,RECORD_AUDIO 需要动态申请,没有权限拨打会失败
依赖引入
本地 Jar/AAR 引入
- 将 callsdk-xxx.aar 拷贝至 app/libs 目录
- 在模块 build.gradle 中配置:
android {
repositories {
flatDir {
dirs 'libs'
}
}
}
dependencies {
implementation(name: 'callsdk-xxx', ext: 'aar')
//如果项目中没有引入 okhttp,那么需要加入 okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
}快速集成
初始化 SDK
// 1. 配置 SDK 参数
val config = SDKConfig(
username = account,
password = password, // 与 passwordPk 二选一
passwordPk = passwordPk, // 与 password 二选一
baseUrl = Environment.DEBUG,
// 日志配置
logConfig = LogConfig(
enableLog = true, //是否开启日志
logLevel = Log.INFO, //日志级别
logFilePath = null, // 文件日志路径,null 使用默认路径:filesDir/logs
fileLoggingEnabled = true, // 启用文件日志
consoleLogEnabled = true //启用控制台日志打印
)
)
// 2. 初始化 SDK
CallSDK.init(
context = applicationContext,
config = sdkConfig,
object : InitListener {
override fun onInitSuccess() {
// 初始化成功(含登录成功)
}
override fun onInitFailed(errorCode: Int, errorMsg: String?) {
// 初始化失败,错误码详见 4.1 初始化错误
}
override fun onKicked() {
// 账号被踢下线
}
}
)拨打电话
CallSDK.makeCall(
context = this,
phoneNumber = "", // 被叫号码
object : CallStateListener {
override fun onDisconnected() {
// 通话断开
}
override fun onCallFailed(callId: String?) {
// 通话失败
}
override fun onCallAnswered(callId: String?) {
// 通话接听
}
override fun onCallAlerting(callId: String?) {
// 通话振铃
}
override fun onCallProceeding(callId: String?) {
// 通话中
}
override fun onCallReleased(callId: String?) {
// 通话挂断
}
override fun onDtmfReceived(callId: String?, dtmf: Char) {
// 收到 DTMF 信号
}
}
)拨打电话携带自定义参数
自定义参数需要 JSONObject 类型,大小限制为 <256 字节,如超出,会拨打失败
CallSDK.makeCall(
phoneNumber = "", // 被叫号码
userData: JSONObject?,
object : CallStateListener {
override fun onDisconnected() {
// 通话断开
}
override fun onCallFailed(callId: String?, errorCode: Int, errorMsg: String?) {
// 通话失败,错误码详见 4.2 呼叫错误
}
override fun onCallAnswered(callId: String?) {
// 通话接听
}
override fun onCallAlerting(callId: String?) {
// 响铃中
}
override fun onCallProceeding(callId: String?) {
// 通话中
}
override fun onCallReleased(callId: String?) {
// 通话挂断
}
override fun onDtmfReceived(callId: String?, dtmf: Char) {
// 收到 DTMF 信号
}
}
)
挂断电话
CallSDK.releaseCall()发送 DTMF
// 发送单个 DTMF 信号(如:*、#、0-9)
CallSDK.sendDTMF("1")扬声器控制
// 打开/关闭扬声器
CallSDK.openLoudSpeaker(this, true) // true:打开,false:关闭
// 获取扬声器状态
val isSpeakerOn = CallSDK.isLoudSpeakerOn(this)静音控制
// 开启/关闭静音
CallSDK.openMute(true) // true:静音,false:取消静音
// 获取静音状态
val isMute = CallSDK.isMuteOn()查询外显号码组列表
CallSDK.queryNumberGroupList(
page = 1,
pageSize = 20,
listener = object : NumberGroupListListener {
override fun onSuccess(response: NumberGroupListResponse) {
// 查询成功,response 包含号码组列表
println("外显号码组列表:${response.groups}")
}
override fun onFailed(errorMsg: String?) {
println("查询外显号码组失败:$errorMsg")
}
}
)查询外显号码列表
CallSDK.queryDisplayNumberList(
listener = object : DisplayNumberListListener {
override fun onSuccess(numbers: List<DisplayNumber>) {
// 查询成功,numbers 为外显号码列表
println("外显号码列表:$numbers")
}
override fun onFailed(errorMsg: String?) {
println("查询外显号码失败:$errorMsg")
}
}
)设置座席外显号码组(通过号码组 id)
与设置座席外显号码二选一使用
val agentId = "agent_10086" // 座席 ID
val numberGroupId = "group_001" // 号码组 ID
CallSDK.updateAgentNumberGroup(
agentId = agentId,
numberGroupId = numberGroupId,
listener = object : AgentConfigListener {
override fun onSuccess(config: AgentConfig) {
// 设置成功,config 为更新后的座席配置
println("外显号码组设置成功:$config")
}
override fun onFailed(errorMsg: String?) {
println("外显号码组设置失败:$errorMsg")
}
}
)设置座席外显号码组(通过号码组名称)
val numberGroupName = "group_name" // 号码组 名称
CallSDK.updateAgentNumberGroupByName(
numberGroupName = numberGroupName,
listener = object : AgentConfigListener {
override fun onSuccess(config: AgentConfig) {
// 设置成功,config 为更新后的座席配置
println("外显号码组设置成功:$config")
}
override fun onFailed(errorMsg: String?) {
println("外显号码组设置失败:$errorMsg")
}
}
)设置座席自定义外显号码
与设置座席外显号码组二选一使用
CallSDK.updateAgentSelectNumber(
agentId = agentId,
selectNumber = selectNumber,
listener = object : AgentConfigListener {
override fun onSuccess(config: AgentConfig) {
println("自定义外显号码设置成功:$config")
}
override fun onFailed(errorMsg: String?) {
println("自定义外显号码设置失败:$errorMsg")
}
}
)查询座席配置
CallSDK.getFullAgent(
agentId = agentId,
listener = object : AgentConfigListener {
override fun onSuccess(config: AgentConfig) {
println("座席完整配置:$config")
}
override fun onFailed(errorMsg: String?) {
println("查询座席配置失败:$errorMsg")
}
}
)注销登录
CallSDK.logout()
错误码
初始化错误
| 错误码 | 原因 |
| -202 | 登录失败,账号或密码错误 |
| -210 | 由于多次登录失败,您的账号已被冻结,请联系管理员解冻 |
| -211 | 坐席已停用 |
| -212 | 账户未找到 |
| -213 | 坐席未找到 |
/**
* 登录失败,账号或密码错误
*/
const val LOGIN_FAILED_WRONG_CREDENTIALS = -202
/**
* 由于多次登录失败,您的账号已被冻结,请联系管理员解冻
*/
const val LOGIN_FAILED_ACCOUNT_FROZEN = -210
/**
* 坐席已停用
*/
const val AGENT_DISABLED = -211
/**
* 账户未找到
*/
const val ACCOUNT_NOT_FOUND = -212
/**
* 坐席未找到
*/
const val AGENT_NOT_FOUND = -213呼叫错误
| 错误码 | 原因 |
| -403 | 被叫黑名单受限 |
| -404 | 号码不存在 |
| -407 | userData 超过 255 字节限制 |
| -408 | 超时 |
| -477 | 掉注册 |
| -480 | 运营商拦截 |
| -486 | 用户忙线 |
| -487 | 对方拒接或者手机软件拦截 |
| -500 | 外显号错误 |
/**
* 被叫黑名单受限
*/
const val CALL_FAILED_REJECTED = -403
/**
* 号码不存在
*/
const val CALL_FAILED_NUMBER_NOT_FOUND = -404
/**
* userData 超过 255 字节限制
*/
const val CALL_FAILED_USERDATA_TOO_LARGE = -407
/**
* 超时
*/
const val CALL_FAILED_TIMEOUT = -408
/**
* 掉注册
*/
const val CALL_FAILED_REGISTRATION_DROPPED = -477
/**
* 运营商拦截
*/
const val CALL_FAILED_SIP_UNAVAILABLE = -480
/**
* 用户忙线
*/
const val CALL_FAILED_USER_BUSY = -486
/**
* 对方拒接或者手机软件拦截
*/
const val CALL_FAILED_CANCELLED = -487
/**
* 外显号错误
*/
const val CALL_FAILED_DISPLAY_NUMBER_ERROR = -500耳机
耳机的具体实现可参考Demo
需要增加蓝牙权限
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ 需要 BLUETOOTH_CONNECT 权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />HeadsetPlugReceiver
具体实现可参考 Demo 中的 HeadsetPlugReceiver类,实现了监听耳机插拔的逻辑
混淆配置
-keep class com.yuntongxun.ecsdk.** { *; }
-keep interface com.yuntongxun.ecsdk.** { *; }
-keep class com.yuntongxun.ecsdk.core.voip.** { *; }
-keep class com.yuntongxun.ecsdk.ECVoIPCall$* { *; }
-keep class com.useasy.callsdk.** { *; }
-keep interface com.useasy.callsdk.** { *; }常见问题
初始化失败
- 检查用户名 / 密码是否正确
- 检查网络连接(需联网状态)
- 检查环境地址是否可访问
通话无声音
- 检查录音 / 扬声器权限是否授予
- 检查设备音量是否正常
- 确认 openLoudSpeaker(true) 已调用
- 检查 SIP 服务器配置是否正确
注意事项
- 权限处理:Android 6.0+ 需动态申请录音权限,建议在拨号前检查并申请。
- 日志控制:生产环境需关闭日志(enableLog(false)),避免敏感信息泄露。
版本更新日志
| 版本号 | 更新时间 | 核心更新 |
| 1.2.10 | 2026-1-8 | 优化通话回铃音杂音问题 |
| 1.2.9 | 2026-1-7 | 修复通话震铃音问题设置外显号码组支持通过号码组名称设置 |
| 1.2.8 | 2026-1-6 | onCallReleased回调增加 hangupType 参数,0 代表主叫挂断,1 代表被叫挂断修复 onCallFailed 异常回调的问题updateAgentNumberGroup 等接口去除 agent_id 参数增加 |
| 1.2.7 | 2025-12-31 | 修复crash |
| 1.2.6 | 2025-12-28 | Sdk init 方法 SDKConfig 中增加 passwordPk 参数Demo 优化,动态申请蓝牙权限 |
| 1.2.5 | 2025-12-24 | Sdk 兼容okhttp 3.x 版本LogConfig 支持 consoleLogEnabled 配置,控制控制台log开关 |
| 1.2.4 | 2025-12-21 | 外呼支持传递自定义参数初始化失败和外呼失败回调增加错误码和错误信息修复拨号还是上一个拨号信息的 Bug修复通话声音小,有回音的问题优化日志 |
| 1.2.3 | 2025-12-19 | 增加响铃中回调 onCallAlerting增加日志配置 LogConfig保留 init 参数中的 Context,去除其他方法的 Context参数 去除 setMute 方法 优化 SDK,增加状态类型 |
| 1.2.2 | 2025-12-18 | 支持蓝牙耳机/耳机通话优化sdk接口sdk回调统一回调在主线程 |
| 1.2.1 | 2025-12-16 | 资源精简修复Demo计时基准错误问题 |
| 1.2.0 | 2025-12-15 | 修复对端挂断电话无法收到回掉的问题通话增加前台通知,启动前台 Service |
| 1.0.0 | 2025-11-30 | 支持音频通话、外显号码配置、通话控制 |
Demo
Demo 集成了通话 SDK,同时还有自定义的拨号盘页面以及通话页面,UI 开发可参考 Demo