拨打第一通电话

最近更新时间:2026-02-04 15:40:01

我的收藏
本文档将帮助您使用 AtomicXCore SDKDeviceStoreCallStore 以及核心组件 CallCoreView,快速完成拨打电话功能。


核心功能

AtomicXCore 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
模块
功能描述
通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。
CallStore
通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。

准备工作

步骤1:开通服务

请参见 开通服务,获取体验版或付费版 SDK。

步骤2:集成 SDK

安装组件:请在您的 build.gradle 文件中添加 api "io.trtc.uikit:atomicx-core:latest.release"api "com.tencent.imsdk:imsdk-plus:8.7.7201" 依赖,然后执行 Gradle Sync
dependencies {
api "io.trtc.uikit:atomicx-core:latest.release"
api "com.tencent.imsdk:imsdk-plus:8.7.7201"
// 其他依赖...
}

步骤3:初始化与登录流程

启动通话服务需依次完成 CallStore 初始化与用户登录。CallStore 通过监听登录成功事件自动同步用户信息,从而进入就绪状态。流程图与示例代码如下:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// CallStore 初始化
CallStore.shared
val sdkAppId = 1400000001 // 替换为您的 SDKAppID
val userId = "test_001" // 替换为您的 UserID
val userSig = "xxxxxxxxxxx" // 替换为您的 UserSig
LoginStore.shared.login(
this,
sdkAppId,
userId,
userSig,
object : CompletionHandler {
override fun onSuccess() {
// 完成 TUICallEngine 的初始化
TUICallEngine.createInstance(this@MainActivity).init(sdkAppId, userId, userSig, null)
// 登录成功处理
Log.d("Login", "login success");
}


override fun onFailure(code: Int, desc: String) {
// 登录失败处理
Log.e("Login", "login failed, code: $code, error: $desc");
}
}
)
}
}
参数
类型
说明
userId
String
当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1、123 等简单 ID。
sdkAppId
int
控制台 获取,通常是以 140 或 160 开头的 10 位整数。
userSig
String
用于腾讯云鉴权的票据。请注意:
开发环境:您可以采用本地 GenerateTestUserSig.genTestUserSig 函数生成 userSig 或者 通过 UserSig 辅助工具 生成临时的 UserSig。
生产环境:为了防止密钥泄露,请务必采用服务端生成 UserSig 的方式。详细信息请参考 服务端生成 UserSig
更多信息请参见 如何计算及使用 UserSig

实现步骤

发起通话前,请确保完成登录,这是服务可用的必要前提。以下将分 5 步为您讲解如何"拨打一通电话"。

步骤1:创建通话界面

您需要创建一个通话页面,当发起通话唤起通话页面,实现方式如下:
1. 创建通话页面:您可以新建一个 Activity 作为通话宿主页面,用于响应来电时的跳转逻辑。
2. 通话页面绑定 CallCoreView : 通话视图核心组件,自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等UI定制化能力。
class CallActivity : AppCompatActivity() {
private var callCoreView: CallCoreView? = null
// 1.创建通话页面容器
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 2.通话页面绑定 CallCoreView
callCoreView = CallCoreView(this)
setContentView(callCoreView)
}
}
CallCoreView 视图组件功能说明:
功能
说明
参考文档
设置布局模式
支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。
设置头像
支持通过传入头像资源路径,为特定用户自定义头像。
设置音量提示图标
支持根据不同音量等级,配置个性化的音量指示图标。
设置网络提示图标
支持根据实时网络质量,配置对应的网络状态提示图标。
设置等待接听用户的动画
在多人通话场景下,支持传入 GIF 图像路径,为待接听状态的用户展示动画。

步骤2:添加通话控制按钮

您可以参考 DeviceStoreCallStore 提供的 API ,自定义添加您的按钮。
DeviceStore 功能说明:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
CallStore 功能说明:接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
图标资源下载:按钮图标可以直接从 Github 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
图标:






















下载地址:
以添加挂断、麦克风、摄像头按钮为例,实现方式如下:
1.1 创建底部栏容器:在通话页面底部创建一个容器,用于添加挂断、麦克风、摄像头按钮。
import io.trtc.tuikit.atomicxcore.api.call.CallStore
import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

class CallActivity : AppCompatActivity() {
private var buttonContainer: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 其他初始化代码
// 创建底部栏容器
createButtonContainer()
setContentView(buttonContainer)
}
private fun createButtonContainer() {
buttonContainer = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.BOTTOM
bottomMargin = dpToPx(80)
}
}
}
}
1.2 添加挂断按钮:在底部工具栏容器中添加挂断按钮,在点击事件中调用 hangup 接口并销毁页面。
import io.trtc.tuikit.atomicxcore.api.call.CallStore
import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

class CallActivity : AppCompatActivity() {
private var buttonContainer: Button? = null
private var buttonHangup: Button? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 其他初始化代码
// 1.在底部工具栏容器中添加挂断按钮
addHangupButton()
setContentView(buttonContainer)
}
private fun addHangupButton() {
buttonHangup = Button(this).apply {
text = "挂断"
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
marginEnd = dpToPx(8)
}
setOnClickListener {
// 2.在点击事件中调用 hangup 接口并销毁页面
CallStore.shared.hangup(null)
finish()
}
}
buttonContainer?.addView(buttonHangup)
}
}
1.3 添加麦克风开关按钮:在底部工具栏容器中添加麦克风开关按钮,并在点击事件中调用 openLocalMicrophonecloseLocalMicrophone 接口。
import io.trtc.tuikit.atomicxcore.api.call.CallStore
import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

class CallActivity : AppCompatActivity() {
private var buttonContainer: Button? = null
private var buttonMicrophone: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 其他初始化代码
// 1.在底部工具栏容器中添加麦克风开关
addMicrophoneButton()
setContentView(buttonContainer)
}
private fun addMicrophoneButton() {
buttonMicrophone = Button(this).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
setMargins(16, 0, 16, 0)
}
setOnClickListener {
// 2.点击事件调用开启或关闭麦克风
val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON
if (isMicrophoneOpen) {
DeviceStore.shared().closeLocalMicrophone()
} else {
DeviceStore.shared().openLocalMicrophone(null)
}
}
}
val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON
buttonMicrophone?.text = if (isMicrophoneOpen) "关麦克风" else "开麦克风"
buttonContainer?.addView(buttonMicrophone)
}
}
1.4 添加摄像头开关按钮:在底部工具栏容器中添加摄像头开关按钮,并在点击事件中调用 openLocalCameracloseLocalCamera 接口。
Android
import io.trtc.tuikit.atomicxcore.api.call.CallStore
import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

class CallActivity : AppCompatActivity() {
private var buttonContainer: Button? = null
private var buttonCamera: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 其他初始化代码
// 1.在底部工具栏容器中添加摄像头开关按钮
addCameraButton()
setContentView(buttonContainer)
}
private fun addCameraButton() {
buttonCamera = Button(this).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
setMargins(16, 0, 16, 0)
}
setOnClickListener {
// 2.点击事件调用开启或关闭摄像头
val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
if (isCameraOpen) {
DeviceStore.shared().closeLocalCamera()
} else {
val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
DeviceStore.shared().openLocalCamera(isFrontCamera, null)
}
}
}
val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
buttonCamera?.text = if (isCameraOpen) "关摄像头" else "开摄像头"
buttonContainer?.addView(buttonCamera)
}
}
1.5 实时更新媒体设备按钮文本:监听麦克风和摄像头的状态,实时更新按钮文本。
import io.trtc.tuikit.atomicxcore.api.call.*
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
import kotlinx.coroutines.*

class CallActivity : AppCompatActivity() {
private var buttonCamera: Button? = null
private var buttonMicrophone: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 其他初始化代码
// 1.监听麦克风和摄像头的状态
observeDeviceState()
}
private fun observeDeviceState() {
deviceStateJob = CoroutineScope(Dispatchers.Main).launch {
supervisorScope {
launch {
DeviceStore.shared().deviceState.cameraStatus.collect { status ->
// 2.更新摄像头按钮文本
buttonCamera?.text = if (status == DeviceStatus.ON) "关闭摄像头" else "开启摄像头"
}
}
launch {
DeviceStore.shared().deviceState.microphoneStatus.collect { status ->
// 2.更新麦克风按钮文本
buttonMicrophone?.text = if (status == DeviceStatus.ON) "关闭麦克风" else "开启麦克风"
}
}
}
}
}
}

步骤3:申请麦克风/摄像头权限

建议在发起通话前,先行检测音视频权限。若权限缺失,请引导用户动态申请。实现方法如下:
1. 声明权限:首先,在 AndroidManifest.xml 文件中声明应用需要的摄像头和麦克风权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 麦克风权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<application>
<!-- ... -->
<activity
android:name=".CallActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>
2. 动态申请权限:我们推荐您在发起通话时,根据通话媒体类型动态申请音视频权限。以下是权限处理的示例代码:
// 动态申请权限
private fun requestAllCallPermissions() {
val allPermissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
if (!checkPermissions(allPermissions)) {
ActivityCompat.requestPermissions(this, allPermissions, PERMISSION_REQUEST_CODE)
}
}

// 处理权限申请结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

if (requestCode == PERMISSION_REQUEST_CODE) {
// 检查是否所有权限都已授予
var allGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false
break
}
}
if (allGranted) {
// 权限申请成功
} else {
// 部分权限被拒绝
}
}
}

步骤4:发起通话

您可以在 calls 调用成功后跳转通话界面,我们建议您根据媒体类型自动开启麦克风或摄像头获得更好的通话体验,实现方式如下:
1. 发起通话:调用 calls 发起通话。
2. 开启媒体设备:发起通话成功后开启麦克风,如果是视频通话同时开启摄像头。
3. 唤起通话页面:发起通话成功,唤起通话页面。
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
import io.trtc.tuikit.atomicxcore.api.call.CallStore

class MainActivity : ComponentActivity() {
// 1.发起通话
private fun startCall(userIdList: List<String>, mediaType: CallMediaType) {
CallStore.shared.calls(userIdList, mediaType, null, object : CompletionHandler {
override fun onFailure(code: Int, desc: String) {
}
override fun onSuccess() {
// 2.开启媒体设备
openDeviceForMediaType(mediaType)
// 3.唤起通话页面
val intent = Intent(this@MainActivity, CallActivity::class.java)
startActivity(intent)
}
})
}
private fun openDeviceForMediaType(mediaType: CallMediaType?) {
if (mediaType == null) {
return
}
DeviceStore.shared().openLocalMicrophone(null)
if (mediaType == CallMediaType.Video) {
val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
DeviceStore.shared().openLocalCamera(isFrontCamera, null)
}
}
}
calls 接口参数详细说明:
参数
类型
是否必填
说明
userIdList
List<String>
目标用户的 userId 列表。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.Video : 视频通话。
CallMediaType.Audio : 语音通话。
params
通话扩展参数,如:房间号、通话邀请超时时间等。
roomId (String) : 房间 ID,可选参数,未指定时由服务端自动分配。
timeout (Int) : 呼叫超时时间(秒)。
userData (String) : 用户自定义数据。
chatGroupId (String) : Chat 群组 ID,用于群组通话场景。
isEphemeralCall (Boolean) : 是否为加密通话(不产生通话记录)。

步骤5: 结束通话

无论您调用 hangup 挂断还是对方主动结束通话,均会触发 onCallEnded 事件。 建议监听该事件,当该事件被触发(即通话结束)时,执行关闭当前界面的操作。实现方式如下:
1. 监听通话结束事件:监听 onCallEnded 事件。
2. 销毁通话页面onCallEnded 触发后,销毁通话页面。
import io.trtc.tuikit.atomicxcore.api.call.CallEndReason
import io.trtc.tuikit.atomicxcore.api.call.CallListener
import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
import io.trtc.tuikit.atomicxcore.api.call.CallStore

class CallActivity : AppCompatActivity() {
private var callListener: CallListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ... 其他初始化代码
// 1.监听通话结束事件
addListener()
}
private fun addListener() {
callListener = object : CallListener() {
override fun onCallEnded(callId: String, mediaType: CallMediaType, reason: CallEndReason, userId: String) {
// 2.销毁通话页面
finish()
}
}
callListener?.let { CallStore.shared.addListener(it) }
}
}
onCallEnded 事件参数详细说明
参数
类型
说明
callId
String
此次通话的唯一标识。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.Video : 视频通话。
CallMediaType.Audio : 语音通话。
reason
通话结束的原因。
Unknown : 未知原因,无法确定结束原因。
Hangup : 正常挂断,用户主动挂断通话。
Reject : 拒绝接听,被叫方拒绝来电。
NoResponse : 无响应,被叫方未在超时时间内接听。
Offline : 对方离线,被叫方不在线。
LineBusy : 对方忙线,被叫方正在通话中。
Canceled : 通话取消,主叫方在对方接听前取消。
OtherDeviceAccepted : 其他设备已接听,通话已在另一登录设备上接听。
OtherDeviceReject : 其他设备已拒绝,通话已在另一登录设备上拒绝。
EndByServer : 服务器结束,通话被服务器终止。
userId
String
触发结束的用户 ID 。

运行效果

当您完成以上 5 步后,"拨打一通电话"运行效果如下:


定制页面

CallCoreView 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。为助力快速集成,您可以直接从 Github 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。

自定义音量提示的图标

您可以调用 CallCoreView 组件的 setVolumeLevelIcons 设置音量大小等级不同的提示图标。

setVolumeLevelIcons 示例代码:
private fun setIconResourcePath() {
val volumeLevelIcons = mapOf(VolumeLevel.Mute to "对应图标资源的路径")
val callCoreView = CallCoreView(context)
callCoreView.setVolumeLevelIcons(volumeLevelIcons)
}
setVolumeLevelIcons 接口参数详细说明:
参数
类型
是否必填
说明
icons
Map<VolumeLevel, String>
音量等级与图标资源的映射表。
key ( VolumeLevel ) 表示音量等级:
VolumeLevel.Mute :表示麦克风关闭,静音状态。
VolumeLevel.Low :表示音量范围 (0-25]
VolumeLevel.Medium : 表示音量范围 (25-50]
VolumeLevel.High : 表示音量范围在 (50-75]
VolumeLevel.Peak : 表示音量范围在 (75-100]。
Value ( String ) 表示对应音量等级的图标资源路径。
音量提示图标:
图标
说明
下载地址

【图标含义】音量提示图标。
【推荐用法】您可以将该图标等级设置为 VolumeLevel.LowVolumeLevel.Medium ,当用户音量大于对应等级时显示。

【图标含义】静音图标。
【推荐用法】您可以将该图标等级设置为 VolumeLevel.Mute ,当该用户静音时显示。

自定义网络提示的图标

您可以调用 CallCoreView 组件的 setNetworkQualityIcons 设置不同网络状态的提示图标。

setNetworkQualityIcons 示例代码:
private fun setNetworkQualityIcons() {
val volumeLevelIcons = mapOf(NetworkQuality.BAD to "对应图标的路径")
val callCoreView = CallCoreView(context)
callCoreView.setNetworkQualityIcons(volumeLevelIcons)
}
setNetworkQualityIcons 接口参数详细说明:
参数
类型
是否必填
说明
icons
Map<NetworkQuality, String>
网络质量与图标资源的映射表。
Key ( NetworkQuality ) : 表示网络质量等级。
NetworkQuality.UNKNOWN :未知网络状态。
NetworkQuality.EXCELLENT:网络状态极佳。
NetworkQuality.GOOD : 网络状态较好。
NetworkQuality.POOR : 网络状态较差。
NetworkQuality.BAD : 网络状态差。
NetworkQuality.VERY_BAD :网络状态极差。
NetworkQuality.DOWN :网络断开。
Value ( String ) : 对应网络状态的图标资源路径。
网络较差的提示图标:
图标
说明
下载地址

【图标含义】网络较差的提示图标。
【推荐用法】您可以将该图标等级设置为 NetworkQuality.BADNetworkQuality.VERY_BADNetworkQuality.DOWN ,当网络较差时显示该图标。

自定义默认头像

您可以调用 CallCoreViewsetParticipantAvatars 接口设置用户头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
setParticipantAvatars 示例代码:
private fun setParticipantAvatars() {
val avatars: MutableMap<String, String> = mutableMapOf()
val userId = "" // 用户 ID
val avatarPath = "" // 用户默认头像资源的路径
avatars[userId] = avatarPath
val callCoreView = CallCoreView(context)
callCoreView.setParticipantAvatars(avatars)
}
setParticipantAvatars 接口参数详细说明:
参数
类型
是否必填
说明
icons
Map<String, String>
用户头像映射表。
Key : 用户的 userID 。
Value : 该用户的头像资源绝对路径。
默认头像资源:
图标
说明
下载地址

【图标含义】默认头像
【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。

自定义 loading 动画

您可以调用 CallCoreViewsetWaitingAnimation 接口,为等待中用户设置等待动画获得更好的体验。

setWaitingAnimation 示例代码:
private fun setWaitingAnimation() {
val waitingAnimationPath = "" // 等待动画 GIF 图像资源的路径
val callCoreView = CallCoreView(context)
callCoreView.setWaitingAnimation(waitingAnimationPath)
}
setWaitingAnimation 接口参数详细说明:
参数
类型
是否必填
说明
path
String
GIF 格式图像资源的绝对路径。
等待接听的动画:
图标
说明
下载地址

【图标含义】用户等待接听动画。
【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。

添加通话计时提示

通话计时可通过响应式数据 activeCallduration 字段实时获得,实时显示通话计时的实现方式如下:
1. 数据层订阅:订阅 CallStore.observerState.activeCall , 建立当前活跃通话的响应式监听。
2. 绑定通话计时数据:将 activeCall.duration 字段绑定至 UI 控件。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。
import android.content.Context
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import com.tencent.qcloud.tuicore.util.DateTimeUtil
import io.trtc.tuikit.atomicxcore.api.call.CallStore
import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class TimerView(context: Context) : AppCompatTextView(context) {
private var subscribeStateJob: Job? = null

override fun onAttachedToWindow() {
super.onAttachedToWindow()
// 1.数据层订阅 activeCall
registerActiveCallObserver()
}

private fun registerActiveCallObserver() {
subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
CallStore.shared.observerState.activeCall.collect { activeCall ->
// 2.绑定通话计时数据,更新通话计时
updateDurationView(activeCall)
}
}
}

private fun updateDurationView(activeCall: CallInfo) {
val currentDuration = activeCall.duration
text = DateTimeUtil.formatSecondsTo00(currentDuration.toInt())
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
subscribeStateJob?.cancel()
}
}
说明:
若您想了解更多通话状态响应式数据,详细可参考:CallState

更多功能

设置头像和昵称

通话开始前,您可以通过 setSelfInfo 方法,设置自己的昵称和头像。
setSelfInfo 示例代码:
val user = UserProfile()
user.userID = "" // 您的 userId
user.avatarURL = "" // 头像的 url
user.nickname = "" // 需要设置的昵称
LoginStore.shared.setSelfInfo(user, object : CompletionHandler {
override fun onSuccess() {
// 设置成功回调
}

override fun onFailure(code: Int, desc: String) {
// 设置失败回调
}
})
setSelfInfo 接口参数详细说明:
参数
类型
是否必填
说明
userProfile
用户信息结构体。
userID (String):用户的 ID 。
avatarURL (String) : 用户头像的 URL。
nickname (String) :用户的昵称。
更多字段详情可参考 UserProfile
completion
CompletionHandler
操作完成回调,用于返回接通电话的结果。

切换布局模式

您可以通过 setLayoutTemplate 接口灵活切换布局模式。若未主动配置,CallCoreView 将根据通话人数自动适配:1 V 1 场景下默认采用 Float 模式,多人通话场景下则自动切换为 Grid 模式。不同布局模式的说明如下:
Float 模式
Grid 模式
PIP 模式



布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。
交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。
布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。
交互特性:支持点击特定成员画面放大查看。
布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。
交互特性:等待时显示自己的画面,接通后还会显示通话计时。
setLayoutTemplate 示例代码:
private fun setLayoutTemplate() {
val callCoreView = CallCoreView(context)
val template = CallLayoutTemplate.Grid
// 设置布局模式
callCoreView.setLayoutTemplate(template)
setContentView(callCoreView)
}
setLayoutTemplate 接口参数详细说明:
参数
类型
是否必填
说明
template
CallCoreView 的布局模式。
CallLayoutTemplate.Float
布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。
交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。
CallLayoutTemplate.Grid
布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。
交互特性:支持点击特定成员画面放大查看。
CallLayoutTemplate.Pip :
布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。
交互特性:等待时显示自己的画面,接通后还会显示通话计时。

设置通话的默认超时时间

您可以在发起通话 calls 时,通过配置参数 CallParams 中的 timeout 字段来指定等待超时时间。示例代码如下:
val callParams = CallParams()
callParams.timeout = 30 // 设置通话超时
CallStore.shared.calls(userIdList, CallMediaType.Video, callParams, null)
calls 接口参数详细说明:
参数
类型
是否必填
说明
userIdList
List<String>
目标用户的 userId 列表。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.Video : 视频通话。
CallMediaType.Audio : 语音通话。
params
通话扩展参数,如:房间号、通话邀请超时时间等。
roomId (String) : 房间 ID,可选参数,未指定时由服务端自动分配。
timeout (Int) : 呼叫超时时间(秒)。
userData (String) : 用户自定义数据。
chatGroupId (String) : Chat 群组 ID,用于群组通话场景。
isEphemeralCall (Boolean) : 是否为加密通话(不产生通话记录)。

实现画中画功能

画中画功能需 Android 8.0 (API 26) 及以上版本支持。建议您在进入画中画模式时,将 CallCoreView 切换为 Pip 布局获得更好的体验。
开始进入画中画:调用系统方法 enterPictureInPictureMode 启动画中画模式。
private fun enterPictureInPictureModeWithBuild() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPipModePermission()) {
val pictureInPictureParams: PictureInPictureParams.Builder = PictureInPictureParams.Builder()
val floatViewWidth = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_width)
val floatViewHeight = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_height)
val aspectRatio = Rational(floatViewWidth, floatViewHeight)
pictureInPictureParams.setAspectRatio(aspectRatio).build()
this.enterPictureInPictureMode(pictureInPictureParams.build())
}
}
进入画中画成功:在 onPictureInPictureModeChanged 回调中,若检测到进入画中画模式,请将 CallCoreView 的布局设置为 Pip 模式:
val callCoreView = CallCoreView(context)

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
if (isInPictureInPictureMode) {
callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip)
}
}
从画中画返回到通话界面:当从画中画返回应用(恢复全屏)时,会触发 onResume 回调。您可以在该回调中,根据当前通话的人数重新设置布局。若当前为 1 v 1 通话:设置为 Float,多人通话:设置为 Grid
val callCoreView = CallCoreView(context)

override fun onResume() {
super.onResume()
val allParticipants = CallStore.shared.observerState.allParticipants.value
if (allParticipants.size > 2) {
callCoreView?.setLayoutTemplate(CallLayoutTemplate.Grid)
} else {
callCoreView?.setLayoutTemplate(CallLayoutTemplate.Float)
}
}

设置通话过程中屏幕常亮

通话过程中保持屏幕常亮是通话应用的基本需求。Android 提供了 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 标志来实现此功能,这是最简单且推荐的方式。
class CallActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or // 锁屏时显示
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or // 解锁屏幕
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or // 保持屏幕常亮
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // 点亮屏幕
)
setContentView(R.layout.activity_call)
}
}

播放等待接听的提示音

您可以监听自己的通话状态,在等待接听时播放铃声,在接听通话或通话结束停止播放铃声。
MainScope().launch {
CallStore.shared.observerState.selfInfo.collect { selfInfo ->
if (selfInfo.status == CallParticipantStatus.Accept || selfInfo.status == CallParticipantStatus.None) {
// 停止播放铃声
return@collect
}
if (selfInfo.status == CallParticipantStatus.Waiting) {
// 播放铃声
}
}

开启后台采集音频/视频

1. 配置权限与服务(AndroidManifest.xml): 从 Android 9.0 (API 28) 开始需要声明前台服务权限;Android 14 (API 34) 强制要求声明具体的服务类型(麦克风和摄像头)。
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

<application>
<service
android:name=".CallForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="camera|microphone" />
</application>
</manifest>
2. 创建前台服务类(CallForegroundService):
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat

class CallForegroundService : Service() {
companion object {
private const val NOTIFICATION_ID = 1001
private const val CHANNEL_ID = "call_foreground_channel"
fun start(context: Context) {
val intent = Intent(context, CallForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}

fun stop(context: Context) {
val intent = Intent(context, CallForegroundService::class.java)
context.stopService(intent)
}
}

override fun onCreate() {
super.onCreate()
createNotificationChannel()
// 启动前台通知,确保后台采集权限
startForeground(NOTIFICATION_ID, createNotification())
}

override fun onBind(intent: Intent?): IBinder? = null

private fun createNotification(): Notification {
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("正在通话中")
.setContentText("应用正在后台运行以保持通话")
.setSmallIcon(android.R.drawable.ic_menu_call) // 请替换为您的应用图标
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"通话保活服务",
NotificationManager.IMPORTANCE_HIGH
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
}

下一步

恭喜您,已经完成了"拨打一通电话",接下来,您可以实现接听第一通电话功能,可参考下表:
功能
描述
集成指引
接听第一通电话
引导您快速完成接听流程集成。涵盖通话界面唤起、接听及拒接等核心控制功能。

常见问题

进入画中画模式后,应用内跳转其他界面后画面异常?

原因:Android 的画中画模式是基于任务栈(Task Stack)运作的。默认情况下,应用内所有 Activity 共享同一个任务栈。因此,若在画中画期间启动新的 Activity,该页面会被压入当前栈中,导致其错误地显示在画中画的小窗口内,引发 UI 异常。
解决方法:AndroidManifest.xml 声明通话界面的 Activity 为一个独立的任务栈。
<activity
android:name=".view.CallActivity" <!-- 您的通话界面 -->
android:launchMode="singleTask"
android:taskAffinity="${applicationId}.call" />

在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?

单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的20条群消息,如果存在通话邀请,则触发来电邀请事件。

联系我们

如果您在使用过程中,有什么建议或者意见,可以 联系我们,感谢您的反馈。