厂商 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 开发者自行申请。未获批的应用即使满足系统版本要求,也无法拉起系统来电界面。各厂商的权益说明如下:
- 小米: 小米 HyperOS VoIP 文档
- OPPO: “VoIP 音视频通话”类别当前未开放申请。如有特殊诉求,请联系 OPPO 在线客服,详情参见 OPPO 官方文档。
- vivo: vivo VoIP 文档
- 荣耀: 荣耀 VoIP FAQ
注意: 以上三个条件任一不满足时,
on()将返回ERROR_FEATURE_UNSUPPORTED,模块不会展示系统来电界面。此时来电业务的后续处理和兜底展示需由 App 自行实现。
集成依赖
在工程的 build.gradle 中添加:
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')
剔除后必须自行提供 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);
}
返回结果:
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);
步骤 3:上报通话状态
当对端用户接听后,上报通话状态:
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);
步骤 5:上报应用内通话类型变化
通话类型分视频通话和语音通话两种,对于视频来电语音接听、通话中视频降语音或者语音升视频,需要应用调用接口上报通话类型变化。
String callId = "123456789";
JCallUiManager.getInstance().updateCallType(callId, JCallUiConstants.VOIP_CALL_TYPE_VOICE);
步骤 6:上报通话结束
用户在通知上点击挂断按钮,应用会收到挂断电话回调 disconnectCall,在应用内处理完成挂断后,需立即通过调用接口上报通话结束状态。
注意: 为确保系统状态同步和资源释放,通话结束后必须调用此接口上报通话结束状态。
JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_DISCONNECTED);
步骤 7:取消注册
通话结束后取消订阅:
JCallUiManager.getInstance().off();
注意: 取消订阅后,下次使用需重新注册回调事件。
步骤 8:上报通话异常
当通话出现异常时,应用需通过接口上报通话失败的原因:
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
}
}
注意: 对于不支持系统来电界面的设备,来电如何展示由接入方自行处理。
步骤 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);
}
步骤 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);
若此时应用处于后台,系统将展示来电横幅通知。
步骤 4:上报通话状态
接听:
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()仍会被拒绝。