厂商 VoIP 集成指南

最近更新:2026-08-27
展开全部
厂商 VoIP 集成指南

本文介绍如何集成极光厂商 VoIP Service Kit,并分别完成去电、来电和通话状态同步。开始集成前,请先确认设备、厂商推送服务和 VoIP 权益均满足要求。

前置条件

设备与系统版本

品牌 系统版本要求
vivo OriginOS 6.0 及以上
小米 HyperOS 3.1 及以上
OPPO ColorOS 16.0 及以上
荣耀 MagicOS 10.0 及以上

说明: 当前仅支持在中国大陆销售的手机设备,不支持平板等其他设备类型。

厂商系统推送服务

VoIP 消息依赖厂商系统推送通道下发。请先开通并集成小米、荣耀、vivo 和 OPPO 的系统推送服务,具体参考 厂商通道参数申请指南厂商通道 SDK 集成指南

厂商 VoIP 权益

VoIP 权益由各厂商管控,需 App 开发者自行申请。未获批的应用即使满足系统版本要求,也无法拉起系统来电界面。各厂商的权益说明如下:

注意: 以上三个条件任一不满足时,on() 将返回 ERROR_FEATURE_UNSUPPORTED,模块不会展示系统来电界面。此时来电业务的后续处理和兜底展示需由 App 自行实现。


集成依赖

在工程的 build.gradle 中添加:

implementation 'cn.jiguang.sdk.plugin:voip-service-kit:6.2.1'
          implementation 'cn.jiguang.sdk.plugin:voip-service-kit:6.2.1'

        
此代码块在浮窗中显示

添加这一项依赖即可,无需额外配置仓库或进行适配。

说明: VoIP Service Kit SDK 会随极光 voip-service-kit 依赖自动下载,无需手动处理。如需离线集成或核对版本,可下载 VoIP Service Kit SDK

如需使用指定版本的 VoIP Service Kit SDK,可以使用如下方式,不再自动拉取匹配的 SDK:

implementation ('cn.jiguang.sdk.plugin:voip-service-kit:6.2.1') { // 不再自动拉取匹配的 VoIP Service Kit SDK exclude group: 'cn.jiguang.sdk.plugin', module: 'voip_service_kit_th' } // 使用指定版本的 VoIP Service Kit SDK implementation files('libs/callService-1.0.0.2.aar')
          implementation ('cn.jiguang.sdk.plugin:voip-service-kit:6.2.1') {
    // 不再自动拉取匹配的 VoIP Service Kit SDK
    exclude group: 'cn.jiguang.sdk.plugin', module: 'voip_service_kit_th'
}
// 使用指定版本的 VoIP Service Kit SDK
implementation files('libs/callService-1.0.0.2.aar')

        
此代码块在浮窗中显示

剔除后必须自行提供 VoIP Service Kit SDK,否则运行时会因缺少类而崩溃。


功能使用指南

去电场景

接口说明

接口名 描述
on(Application context) 判断当前设备是否支持系统来电界面
register(JCallUiListener listener) 注册 VoIP 通话回调事件
off() 取消注册 VoIP 通话回调事件
reportOutgoingCall(JCallUiInfo call) 上报去电消息
reportCallAudioRouteEvent(String callId, int currentRoute, int supportRoute) 上报通话中的音频事件
updateCallState(String callId, int callState) 上报应用内通话状态变化
updateCallType(String callId, int callType) 上报应用内通话类型变化
reportCallError(String callId, int errorCode, String errorMessage) 上报通话失败

详细接口说明也可参考:VoIP 通话 API

开发步骤

步骤 1:注册 VoIP 通话回调事件
// 检查设备是否支持 int workable = JCallUiManager.getInstance().on(application); // 注册回调 JCallUiListener mCallUiListener = new JCallUiListener() { @Override public void answerCall(String callId) { Log.i(TAG, "answerCall callId " + callId); } @Override public void answerCallWithType(String callId, int callType) { Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType); } @Override public void rejectCall(String callId) { Log.i(TAG, "rejectCall callId " + callId); } @Override public void changeCallType(String callId, int callType) { Log.i(TAG, "changeCallType callId " + callId + ", type " + callType); } @Override public void disconnectCall(String callId) { Log.i(TAG, "disconnectCall callId " + callId); } @Override public void mute(String callId, boolean mute) { Log.i(TAG, "mute callId " + callId + ", mute " + mute); } @Override public void changeAudioRoute(String callId, int route) { Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route); } @Override public void onCallKitEvent(int event, Bundle extras) { Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras); } }; if (workable == JCallUiConstants.ERROR_NONE) { JCallUiManager.getInstance().register(mCallUiListener); }
          // 检查设备是否支持
int workable = JCallUiManager.getInstance().on(application);

// 注册回调
JCallUiListener mCallUiListener = new JCallUiListener() {
    @Override
    public void answerCall(String callId) {
        Log.i(TAG, "answerCall callId " + callId);
    }

    @Override
    public void answerCallWithType(String callId, int callType) {
        Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType);
    }

    @Override
    public void rejectCall(String callId) {
        Log.i(TAG, "rejectCall callId " + callId);
    }

    @Override
    public void changeCallType(String callId, int callType) {
        Log.i(TAG, "changeCallType callId " + callId + ", type " + callType);
    }

    @Override
    public void disconnectCall(String callId) {
        Log.i(TAG, "disconnectCall callId " + callId);
    }

    @Override
    public void mute(String callId, boolean mute) {
        Log.i(TAG, "mute callId " + callId + ", mute " + mute);
    }

    @Override
    public void changeAudioRoute(String callId, int route) {
        Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route);
    }

    @Override
    public void onCallKitEvent(int event, Bundle extras) {
        Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras);
    }
};

if (workable == JCallUiConstants.ERROR_NONE) {
    JCallUiManager.getInstance().register(mCallUiListener);
}

        
此代码块在浮窗中显示

返回结果:

  • ERROR_NONE:支持
  • ERROR_FEATURE_UNSUPPORTED:不支持

步骤 2:上报去电事件

应用内部建立通话连接之后,需要上报去电并携带通话信息,字段详见 API 文档的 JCallUiInfo

JCallUiInfo call = new JCallUiInfo(); call.setCallId("123456789"); call.setUserName("Mark"); call.setPackageName(getPackageName()); call.setActivityName(this.getClass().getName()); call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE); call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_DIALING); call.setDirection(JCallUiConstants.DIRECTION_OUTGOING); // 上报去电事件 JCallUiManager.getInstance().reportOutgoingCall(call);
          JCallUiInfo call = new JCallUiInfo();
call.setCallId("123456789");
call.setUserName("Mark");
call.setPackageName(getPackageName());
call.setActivityName(this.getClass().getName());
call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE);
call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_DIALING);
call.setDirection(JCallUiConstants.DIRECTION_OUTGOING);

// 上报去电事件
JCallUiManager.getInstance().reportOutgoingCall(call);

        
此代码块在浮窗中显示

步骤 3:上报通话状态

当对端用户接听后,上报通话状态:

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);

        
此代码块在浮窗中显示

步骤 4:上报通话中的音频事件

在通话过程中,用户可以根据实际需要选择不同的音频通路,包括手机听筒、免提扬声器、蓝牙设备和有线耳机等。当通话的音频通路被切换,应用会接收到切换通话音频通路回调 changeAudioRoute,应用在完成通话音频通路切换后,需通过调用接口上报当前通话的音频通路。

String callId = "123456789"; int currentRoute = JCallUiConstants.ROUTE_SPEAKER; int supportRoute = JCallUiConstants.ROUTE_EARPIECE | JCallUiConstants.ROUTE_BLUETOOTH | JCallUiConstants.ROUTE_SPEAKER; JCallUiManager.getInstance().reportCallAudioRouteEvent(callId, currentRoute, supportRoute);
          String callId = "123456789";
int currentRoute = JCallUiConstants.ROUTE_SPEAKER;
int supportRoute = JCallUiConstants.ROUTE_EARPIECE | JCallUiConstants.ROUTE_BLUETOOTH | JCallUiConstants.ROUTE_SPEAKER;

JCallUiManager.getInstance().reportCallAudioRouteEvent(callId, currentRoute, supportRoute);

        
此代码块在浮窗中显示

步骤 5:上报应用内通话类型变化

通话类型分视频通话和语音通话两种,对于视频来电语音接听、通话中视频降语音或者语音升视频,需要应用调用接口上报通话类型变化。

String callId = "123456789"; JCallUiManager.getInstance().updateCallType(callId, JCallUiConstants.VOIP_CALL_TYPE_VOICE);
          String callId = "123456789";
JCallUiManager.getInstance().updateCallType(callId, JCallUiConstants.VOIP_CALL_TYPE_VOICE);

        
此代码块在浮窗中显示

步骤 6:上报通话结束

用户在通知上点击挂断按钮,应用会收到挂断电话回调 disconnectCall,在应用内处理完成挂断后,需立即通过调用接口上报通话结束状态。

注意: 为确保系统状态同步和资源释放,通话结束后必须调用此接口上报通话结束状态。

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_DISCONNECTED);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_DISCONNECTED);

        
此代码块在浮窗中显示

步骤 7:取消注册

通话结束后取消订阅:

JCallUiManager.getInstance().off();
          JCallUiManager.getInstance().off();

        
此代码块在浮窗中显示

注意: 取消订阅后,下次使用需重新注册回调事件。


步骤 8:上报通话异常

当通话出现异常时,应用需通过接口上报通话失败的原因:

JCallUiManager.getInstance().reportCallError(callId, errorCode, errorMessage);
          JCallUiManager.getInstance().reportCallError(callId, errorCode, errorMessage);

        
此代码块在浮窗中显示

来电场景

接口说明

接口名 描述
on(Application context) 判断当前设备是否支持系统来电界面
register(JCallUiListener listener) 注册 VoIP 通话回调事件
off() 取消注册 VoIP 通话回调事件
reportIncomingCall(JCallUiInfo call) 上报来电消息
reportCallAudioRouteEvent(String callId, int currentRoute, int supportRoute) 上报通话中的音频事件
updateCallState(String callId, int callState) 上报应用内通话状态变化
updateCallType(String callId, int callType) 上报应用内通话类型变化
reportCallError(String callId, int errorCode, String errorMessage) 上报通话失败

开发步骤

步骤 1:感知 VoIP 来电

通过 JPush 的 JPushMessageReceiver#onVoipMessage 感知 VoIP 来电:

public class MyReceiver extends JPushMessageReceiver { @Override public void onVoipMessage(Context context, VoipDataMessage message) { // message.getVoipSupportStatus() 表明本次下发时设备的 VoIP 能力: // VOIP_STATUS_SUPPORTED = 2 支持 // VOIP_STATUS_UNSUPPORTED = 1 不支持 // VOIP_STATUS_UNKNOWN = 0 未知 // message.getExtraData() 为业务自定义的 JSON payload } }
          public class MyReceiver extends JPushMessageReceiver {
    @Override
    public void onVoipMessage(Context context, VoipDataMessage message) {
        // message.getVoipSupportStatus() 表明本次下发时设备的 VoIP 能力:
        //   VOIP_STATUS_SUPPORTED   = 2  支持
        //   VOIP_STATUS_UNSUPPORTED = 1  不支持
        //   VOIP_STATUS_UNKNOWN     = 0  未知
        // message.getExtraData() 为业务自定义的 JSON payload
    }
}

        
此代码块在浮窗中显示

注意: 对于不支持系统来电界面的设备,来电如何展示由接入方自行处理。


步骤 2:注册 VoIP 通话回调事件
int workable = JCallUiManager.getInstance().on(application); JCallUiListener mCallUiListener = new JCallUiListener() { @Override public void answerCall(String callId) { Log.i(TAG, "answerCall callId " + callId); } @Override public void answerCallWithType(String callId, int callType) { Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType); } @Override public void rejectCall(String callId) { Log.i(TAG, "rejectCall callId " + callId); } @Override public void changeCallType(String callId, int callType) { Log.i(TAG, "changeCallType callId " + callId + ", type " + callType); } @Override public void disconnectCall(String callId) { Log.i(TAG, "disconnectCall callId " + callId); } @Override public void mute(String callId, boolean mute) { Log.i(TAG, "mute callId " + callId + ", mute " + mute); } @Override public void changeAudioRoute(String callId, int route) { Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route); } @Override public void onCallKitEvent(int event, Bundle extras) { Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras); } }; if (workable == JCallUiConstants.ERROR_NONE) { JCallUiManager.getInstance().register(mCallUiListener); }
          int workable = JCallUiManager.getInstance().on(application);

JCallUiListener mCallUiListener = new JCallUiListener() {
    @Override
    public void answerCall(String callId) {
        Log.i(TAG, "answerCall callId " + callId);
    }

    @Override
    public void answerCallWithType(String callId, int callType) {
        Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType);
    }

    @Override
    public void rejectCall(String callId) {
        Log.i(TAG, "rejectCall callId " + callId);
    }

    @Override
    public void changeCallType(String callId, int callType) {
        Log.i(TAG, "changeCallType callId " + callId + ", type " + callType);
    }

    @Override
    public void disconnectCall(String callId) {
        Log.i(TAG, "disconnectCall callId " + callId);
    }

    @Override
    public void mute(String callId, boolean mute) {
        Log.i(TAG, "mute callId " + callId + ", mute " + mute);
    }

    @Override
    public void changeAudioRoute(String callId, int route) {
        Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route);
    }

    @Override
    public void onCallKitEvent(int event, Bundle extras) {
        Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras);
    }
};

if (workable == JCallUiConstants.ERROR_NONE) {
    JCallUiManager.getInstance().register(mCallUiListener);
}

        
此代码块在浮窗中显示

步骤 3:上报来电事件

应用内部完成通话连接建立后,需上报来电并携带通话信息,字段详见 API 文档的 JCallUiInfo

JCallUiInfo call = new JCallUiInfo(); call.setCallId("123456789"); call.setMsgId("msg78680bjbp680"); call.setUserName("Mark"); call.setPackageName(getPackageName()); call.setActivityName(this.getClass().getName()); call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE); call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_RINGING); call.setDirection(JCallUiConstants.DIRECTION_INCOMING); // 上报来电事件 JCallUiManager.getInstance().reportIncomingCall(call);
          JCallUiInfo call = new JCallUiInfo();
call.setCallId("123456789");
call.setMsgId("msg78680bjbp680");
call.setUserName("Mark");
call.setPackageName(getPackageName());
call.setActivityName(this.getClass().getName());
call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE);
call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_RINGING);
call.setDirection(JCallUiConstants.DIRECTION_INCOMING);

// 上报来电事件
JCallUiManager.getInstance().reportIncomingCall(call);

        
此代码块在浮窗中显示

若此时应用处于后台,系统将展示来电横幅通知。


步骤 4:上报通话状态

接听:

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);

        
此代码块在浮窗中显示

建议: 受网络等因素影响,从用户点击接听到通话真正接通可能存在约 1 秒的延迟。此时通知仍停留在来电状态,容易让用户误以为点击无响应。建议在接听过程中先上报一次 VOIP_CALL_STATE_ANSWERED,由系统界面及时反馈接听状态。

拒接:

拒接成功后上报 VOIP_CALL_STATE_DISCONNECTED,系统会取消通话横幅通知。


步骤 5:上报通话中的音频事件

在通话过程中,用户可以根据实际需要选择不同的音频通路。当通话的音频通路被切换,应用在完成通话音频通路切换后,需通过调用接口上报当前通话的音频通路。

详细实现与去电场景相同,请参考去电场景:步骤 4


步骤 6:上报应用内通话类型变化

对于视频来电语音接听、通话中视频降语音或者语音升视频,需要应用调用接口上报通话类型变化。

详细实现与去电场景相同,请参考去电场景:步骤 4


步骤 7:上报通话结束

用户在通知上点击挂断按钮,应用会收到挂断电话回调,在应用内处理完成挂断后,需立即通过调用接口上报通话结束状态。

注意: 为确保系统状态同步和资源释放,通话结束后必须上报结束状态。详细实现请参考去电场景:步骤 4


步骤 8:取消注册

通话结束后取消订阅。

详细实现请参考去电场景:步骤 4


步骤 9:上报通话异常

当通话出现异常时,应用需通过接口上报通话失败的原因。

详细实现请参考去电场景:步骤 4


App 侧需自行处理的逻辑

集成时请注意以下几点:

事项 说明
通话结束上报 VOIP_CALL_STATE_DISCONNECTED 漏报会导致系统来电界面残留、资源不释放,是此类接入最常见的线上问题
响铃超时处理 无人接听时,由 App 定时上报 VOIP_CALL_STATE_DISCONNECTED
off() 前收尾 仍有进行中的通话时,先上报 VOIP_CALL_STATE_DISCONNECTED,再调用 off()
onCallKitEvent 里的 ERROR_SERVICE_BINDER_DIED 系统通话服务掉线,自行收束通话
头像压缩 走 Binder 传输,过大会被拒绝甚至抛 TransactionTooLargeException,建议 ≤256px、≤200KB
推送 payload 解析 模块不解析 extraData
通话期保活 需要前台服务时自行实现并声明 foregroundServiceType 与权限

已知限制

  • 系统来电页由各家 ROM 自绘且不可定制,不保证各厂商像素一致。
  • 不支持的机型上模块不会展示系统来电界面,来电展示需由 App 自行兜底。
  • 未开通厂商推送服务或未获批 VoIP 权益时,即使机型与系统版本满足,on() 仍会被拒绝。
文档内容是否对您有帮助?

Copyright 2011-2026, jiguang.cn, All Rights Reserved. 粤ICP备12056275号-13 深圳市和讯华谷信息技术有限公司

在文档中心打开