本文档将帮助您使用 AtomicXCore SDK 的 DeviceStore、CallStore 以及核心组件 CallCoreView,快速完成拨打电话功能。

核心功能
AtomicXCore 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
模块 | 功能描述 |
通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。 | |
通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 | |
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
准备工作
步骤1:开通服务
步骤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.sharedval sdkAppId = 1400000001 // 替换为您的 SDKAppIDval userId = "test_001" // 替换为您的 UserIDval userSig = "xxxxxxxxxxx" // 替换为您的 UserSigLoginStore.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 | |
userSig | String | 用于腾讯云鉴权的票据。请注意: 开发环境:您可以采用本地 GenerateTestUserSig.genTestUserSig 函数生成 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.通话页面绑定 CallCoreViewcallCoreView = CallCoreView(this)setContentView(callCoreView)}}
CallCoreView 视图组件功能说明:
功能 | 说明 | 参考文档 |
设置布局模式 | 支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。 | |
设置头像 | 支持通过传入头像资源路径,为特定用户自定义头像。 | |
设置音量提示图标 | 支持根据不同音量等级,配置个性化的音量指示图标。 | |
设置网络提示图标 | 支持根据实时网络质量,配置对应的网络状态提示图标。 | |
设置等待接听用户的动画 | 在多人通话场景下,支持传入 GIF 图像路径,为待接听状态的用户展示动画。 |
步骤2:添加通话控制按钮
DeviceStore 功能说明:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
CallStore 功能说明:接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
图标资源下载:按钮图标可以直接从 Github 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
以添加挂断、麦克风、摄像头按钮为例,实现方式如下:
1.1 创建底部栏容器:在通话页面底部创建一个容器,用于添加挂断、麦克风、摄像头按钮。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: Button? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 其他初始化代码// 创建底部栏容器createButtonContainer()setContentView(buttonContainer)}private fun createButtonContainer() {buttonContainer = LinearLayout(this).apply {orientation = LinearLayout.HORIZONTALgravity = Gravity.CENTERlayoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.WRAP_CONTENT).apply {gravity = Gravity.BOTTOMbottomMargin = dpToPx(80)}}}}
1.2 添加挂断按钮:在底部工具栏容器中添加挂断按钮,在点击事件中调用 hangup 接口并销毁页面。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: Button? = nullprivate var buttonHangup: Button? = nulloverride 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 添加麦克风开关按钮:在底部工具栏容器中添加麦克风开关按钮,并在点击事件中调用 openLocalMicrophone 或 closeLocalMicrophone 接口。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: Button? = nullprivate var buttonMicrophone: Button? = nulloverride 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.ONif (isMicrophoneOpen) {DeviceStore.shared().closeLocalMicrophone()} else {DeviceStore.shared().openLocalMicrophone(null)}}}val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ONbuttonMicrophone?.text = if (isMicrophoneOpen) "关麦克风" else "开麦克风"buttonContainer?.addView(buttonMicrophone)}}
1.4 添加摄像头开关按钮:在底部工具栏容器中添加摄像头开关按钮,并在点击事件中调用 openLocalCamera 或 closeLocalCamera 接口。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: Button? = nullprivate var buttonCamera: Button? = nulloverride 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.ONif (isCameraOpen) {DeviceStore.shared().closeLocalCamera()} else {val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.valueDeviceStore.shared().openLocalCamera(isFrontCamera, null)}}}val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ONbuttonCamera?.text = if (isCameraOpen) "关摄像头" else "开摄像头"buttonContainer?.addView(buttonCamera)}}
1.5 实时更新媒体设备按钮文本:监听麦克风和摄像头的状态,实时更新按钮文本。
import io.trtc.tuikit.atomicxcore.api.call.*import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport kotlinx.coroutines.*class CallActivity : AppCompatActivity() {private var buttonCamera: Button? = nullprivate var buttonMicrophone: Button? = nulloverride 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><!-- ... --><activityandroid: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 = truefor (result in grantResults) {if (result != PackageManager.PERMISSION_GRANTED) {allGranted = falsebreak}}if (allGranted) {// 权限申请成功} else {// 部分权限被拒绝}}}
步骤4:发起通话
1. 发起通话:调用
calls 发起通话。2. 开启媒体设备:发起通话成功后开启麦克风,如果是视频通话同时开启摄像头。
3. 唤起通话页面:发起通话成功,唤起通话页面。
import io.trtc.tuikit.atomicxcore.api.CompletionHandlerimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass 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.valueDeviceStore.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: 结束通话
1. 监听通话结束事件:监听
onCallEnded 事件。2. 销毁通话页面:
onCallEnded 触发后,销毁通话页面。import io.trtc.tuikit.atomicxcore.api.call.CallEndReasonimport io.trtc.tuikit.atomicxcore.api.call.CallListenerimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass CallActivity : AppCompatActivity() {private var callListener: CallListener? = nulloverride 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 打造,无版权风险,可放心使用。
自定义音量提示的图标

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

setNetworkQualityIcons 示例代码:
private fun setNetworkQualityIcons() {val volumeLevelIcons = mapOf(NetworkQuality.BAD to "对应图标的路径")val callCoreView = CallCoreView(context)callCoreView.setNetworkQualityIcons(volumeLevelIcons)}
setNetworkQualityIcons 接口参数详细说明:
参数 | 类型 | 是否必填 | 说明 |
icons | 是 | 网络质量与图标资源的映射表。 Key ( NetworkQuality ) : 表示网络质量等级。 NetworkQuality.UNKNOWN :未知网络状态。NetworkQuality.EXCELLENT:网络状态极佳。NetworkQuality.GOOD : 网络状态较好。NetworkQuality.POOR : 网络状态较差。NetworkQuality.BAD : 网络状态差。NetworkQuality.VERY_BAD :网络状态极差。NetworkQuality.DOWN :网络断开。Value ( String ) : 对应网络状态的图标资源路径。 |
网络较差的提示图标:
图标 | 说明 | 下载地址 |
![]() | 【图标含义】网络较差的提示图标。 【推荐用法】您可以将该图标等级设置为 NetworkQuality.BAD、NetworkQuality.VERY_BAD 或 NetworkQuality.DOWN ,当网络较差时显示该图标。 |
自定义默认头像
您可以调用 CallCoreView 的 setParticipantAvatars 接口设置用户头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
setParticipantAvatars 示例代码:
private fun setParticipantAvatars() {val avatars: MutableMap<String, String> = mutableMapOf()val userId = "" // 用户 IDval avatarPath = "" // 用户默认头像资源的路径avatars[userId] = avatarPathval callCoreView = CallCoreView(context)callCoreView.setParticipantAvatars(avatars)}
setParticipantAvatars 接口参数详细说明:
参数 | 类型 | 是否必填 | 说明 |
icons | Map<String, String> | 是 | 用户头像映射表。 Key : 用户的 userID 。 Value : 该用户的头像资源绝对路径。 |
默认头像资源:
图标 | 说明 | 下载地址 |
![]() | 【图标含义】默认头像 【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。 |
自定义 loading 动画

setWaitingAnimation 示例代码:
private fun setWaitingAnimation() {val waitingAnimationPath = "" // 等待动画 GIF 图像资源的路径val callCoreView = CallCoreView(context)callCoreView.setWaitingAnimation(waitingAnimationPath)}
setWaitingAnimation 接口参数详细说明:
参数 | 类型 | 是否必填 | 说明 |
path | String | 是 | GIF 格式图像资源的绝对路径。 |
等待接听的动画:
图标 | 说明 | 下载地址 |
![]() | 【图标含义】用户等待接听动画。 【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。 |
添加通话计时提示
1. 数据层订阅:订阅
CallStore.observerState.activeCall , 建立当前活跃通话的响应式监听。2. 绑定通话计时数据:将
activeCall.duration 字段绑定至 UI 控件。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。import android.content.Contextimport androidx.appcompat.widget.AppCompatTextViewimport androidx.core.content.ContextCompatimport com.tencent.qcloud.tuicore.util.DateTimeUtilimport io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatusimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchclass TimerView(context: Context) : AppCompatTextView(context) {private var subscribeStateJob: Job? = nulloverride fun onAttachedToWindow() {super.onAttachedToWindow()// 1.数据层订阅 activeCallregisterActiveCallObserver()}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.durationtext = DateTimeUtil.formatSecondsTo00(currentDuration.toInt())}override fun onDetachedFromWindow() {super.onDetachedFromWindow()subscribeStateJob?.cancel()}}
说明:
更多功能
设置头像和昵称
setSelfInfo 示例代码:
val user = UserProfile()user.userID = "" // 您的 userIduser.avatarURL = "" // 头像的 urluser.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) :用户的昵称。 | |
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) 策略,自动识别并全屏展示正在说话的用户。 交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
设置通话的默认超时时间
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.valueif (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><serviceandroid:name=".CallForegroundService"android:enabled="true"android:exported="false"android:foregroundServiceType="camera|microphone" /></application></manifest>
2. 创建前台服务类(CallForegroundService):
import android.app.Notificationimport android.app.NotificationChannelimport android.app.NotificationManagerimport android.app.Serviceimport android.content.Contextimport android.content.Intentimport android.os.Buildimport android.os.IBinderimport androidx.core.app.NotificationCompatclass CallForegroundService : Service() {companion object {private const val NOTIFICATION_ID = 1001private 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? = nullprivate 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 为一个独立的任务栈。<activityandroid:name=".view.CallActivity" <!-- 您的通话界面 -->android:launchMode="singleTask"android:taskAffinity="${applicationId}.call" />
在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?
单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的20条群消息,如果存在通话邀请,则触发来电邀请事件。















