本文档将帮助您使用 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(context).init(GenerateTestUserSig.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。 |
实现接听通话
接听通话前,请确保已完成登录,这是服务可用的必要前提。接下来,我们将通过 6 个步骤带您实现‘接听一通电话’的功能。
步骤1:创建通话页面
您需要创建一个通话页面,当收到来电时唤起通话页面,实现方式如下:
1. 创建通话页面:您可以新建一个 Activity 作为通话宿主页面,用于响应来电时的跳转逻辑。
2. 通话页面绑定 CallCoreView : 通话视图核心组件,自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等UI定制化能力。
import io.trtc.tuikit.atomicxcore.api.view.CallCoreViewclass 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 打造,无版权风险,可放心使用。
以下是添加"接听"和"拒接"按钮的实现方式:
import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass CallActivity : AppCompatActivity() {private var buttonContainer: LinearLayout? = nullprivate var buttonAccept: Button? = nullprivate var buttonReject: Button? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 1.创建底部按钮栏容器createButtonContainer()// 2.添加"接听"与"拒接"按钮addRejectAndAcceptButtons()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)}}}// 添加"接听"与"拒接"按钮private fun addRejectAndAcceptButtons() {createAcceptButton()createRejectButton()buttonContainer?.addView(buttonAccept)buttonContainer?.addView(buttonReject)}// 创建接听按钮private fun createAcceptButton() {buttonAccept = Button(this).apply {text = "接听"layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {marginEnd = dpToPx(8)}setOnClickListener {// 3.接听按钮点击事件绑定 acceptCallStore.shared.accept(null)}}}// 创建拒接按钮private fun createRejectButton() {buttonReject = Button(this).apply {text = "拒接"layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {marginStart = dpToPx(8)}setOnClickListener {// 3.拒接按钮点击事件绑定 rejectCallStore.shared.reject(null)}}}}
1.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 。 |
步骤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) {Log.d("MainActivity", "权限申请成功")} else {Log.w("MainActivity", "部分权限被拒绝")}}}
步骤4: 来电播放提示
您可以监听当前用户的通话状态,在收到来电时播放铃声或振动,接听、挂断后停止播放来电提示,实现方法如下:
1. 数据层订阅:订阅
CallStore.observerState.selfInfo , 建立当前登录用户信息的响应式监听。2. 播放/停止来电提示:若当前用户的通话状态 (
SelfInfo.Status) 为等待接听状态(CallParticipantStatus.Waiting)播放铃声或振动,若当前用户的通话状态 (SelfInfo.Status) 为已接听状态(CallParticipantStatus.Accept)停止铃声或振动。import kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchclass MainActivity : AppCompatActivity() {private var stateJob: Job? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 1.监听当前用户的通话状态observeSelfStatus()}// 监听当前用户的通话状态private fun observeSelfStatus() {stateJob = CoroutineScope(Dispatchers.Main).launch {CallStore.shared.observerState.selfInfo.collect { selfInfo ->// 2.播放/停止来电提示if (selfInfo.status == CallParticipantStatus.Waiting) {// 等待接听时,开始播放来电提示}if (selfInfo.status == CallParticipantStatus.Accept) {// 接听后,停止播放来电提示}}}}}
步骤5:来电打开媒体设备
1. 监听来电事件:订阅
onCallReceived 事件。2. 根据来电媒体类型打开设备:若为语音通话仅开启麦克风,若为视频通话开启麦克风和摄像头。
import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.*class MainActivity : AppCompatActivity() {private var callListener: CallListener? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// ... 其他初始化代码// 1.监听来电事件addListener()}private fun addListener() {callListener = object : CallListener() {override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {super.onCallReceived(callId, mediaType, userData)// 2.根据来电媒体类型打开设备openDeviceForMediaType(mediaType)}}callListener?.let { CallStore.shared.addListener(it) }}private fun openDeviceForMediaType(mediaType: CallMediaType?) {mediaType?.let {DeviceStore.shared().openLocalMicrophone(null)if (mediaType == CallMediaType.Video) {val isFrontCamera = trueDeviceStore.shared().openLocalCamera(isFrontCamera, null)}}}}
onCallReceived 事件详细说明:
参数 | 类型 | 说明 |
callId | String | 此次通话的唯一标识。 |
mediaType | 通话媒体类型,用于指定发起音频通话还是视频通话。 CallMediaType.Video : 视频通话。CallMediaType.Audio : 语音通话。 |
openLocalCamera 接口参数详细说明:
参数名 | 类型 | 必填 | 说明 |
isFront | Boolean | 是 | 是否开启前置摄像头。 true : 开启前置摄像头。false :开启后置摄像头。 |
completion | CompletionHandler | 否 | 操作完成回调,用于返回开启摄像头的结果。若开启失败则会返回错误码和错误信息。 |
openLocalMicrophone 接口参数详细说明:
参数名 | 类型 | 必填 | 说明 |
completion | CompletionHandler | 否 | 操作完成回调,用于返回开启麦克风的结果。若开启失败则会返回错误码和错误信息。 |
步骤6:来电唤起通话界面
private fun addListener() {callListener = object : CallListener() {override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {super.onCallReceived(callId, mediaType, userData)// 唤起通话页面val intent = Intent(this@MainActivity, CallActivity::class.java)startActivity(intent)}}callListener?.let { CallStore.shared.addListener(it) }}
运行效果
当您完成以上 6 步后,"接听一通电话"运行效果如下:

接入离线推送
Notification | FCM Data Message |
![]() | ![]() |
说明:
定制页面
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 接口设置用户头像。建议您监听 CallStore 中的 allParticipants(所有参与通话的成员)状态:当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
setParticipantAvatars 示例代码:
private fun setParticipantAvatars() {val avatars: MutableMap<String, ParticipantAvatarInfo> = 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.atomicx.Rimport 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()}override fun onDetachedFromWindow() {super.onDetachedFromWindow()subscribeStateJob?.cancel()}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())}}
说明:
更多功能
设置头像和昵称
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()val template = CallLayoutTemplate.Grid// 设置布局模式callCoreView.setLayoutTemplate(template)setContentView(callCoreView)}
setLayoutTemplate 接口参数详细说明:
参数 | 类型 | 是否必填 | 说明 |
template | 是 | CallCoreView 的布局模式。 CallLayoutTemplate.Float :布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。 交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。 CallLayoutTemplate.Grid :布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。 交互特性:支持点击特定成员画面放大查看。 CallLayoutTemplate.Pip : 布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。 交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
实现画中画功能
开始进入画中画:调用系统方法
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 回调中,若检测到进入 PiP 模式,请将 CallCoreView 的布局设置为 CallLayoutTemplate.Pip 模式:val callCoreView = CallCoreView(context)override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {super.onPictureInPictureModeChanged(isInPictureInPictureMode)if (isInPictureInPictureMode) {callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip)}}
从画中画返回到通话界面:当从画中画返回应用(恢复全屏)时,会触发
onResume 回调。您可以在该回调中,根据当前通话的人数重新设置布局。若当前为 1v1 通话:设置为 CallLayoutTemplate.Float,多人通话:设置为 CallLayoutTemplate.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)}}
开启后台采集音频/视频
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条群消息,如果存在通话邀请,则触发来电邀请事件。

















