客户端实现
本文介绍如何使用声网灵隼纯呼叫版本的客户端示例项目在 Android 客户端实现呼叫和通话等功能。
纯呼叫版本的客户端示例项目提供呼叫、实时音视频等功能。你需要自行实现账号管理和设备管理相关逻辑。
如果你已有自研的设备管理等模块,声网建议你使用纯呼叫版本示例项目。
前提条件
开始前,请确保你的开发环境满足以下条件:
- Android Studio 4.1.2 或以上版本。
- Android API level 16 或以上。
- 运行 Android 4.1 或以上版本的移动设备。
- 参考开通并配置声网灵隼物联网服务开通灵隼服务。
业务流程
在纯呼叫模式下,你需要自行实现账号系统与设备管理功能。基本流程如下:
跑通示例项目
运行步骤
-
从 GitHub 上下载最新版本的 Android 客户端示例项目。
-
使用 Android Studio 加载工程后,在 app>>res>>values>>config.xml 文件中,将以下配置项修改为你在灵隼控制台的应用配置>>开发者选项页面中获取的相关参数。详见开通声网灵隼服务。
XML<!-- 声网灵隼控制台的应用配置>>开发者选项中的 App ID-->
<string name="AGORA_APPID">4b31f**********************3037</string>
<!-- 声网灵隼控制台的应用配置>>开发者选项中的 Project ID -->
<string name="PROJECT_ID">a******_C</string>
<!-- 声网灵隼控制台的应用配置>>开发者选项中的 Easemob App Key -->
<string name="EASEMOB_APPKEY">52****88#3****2</string>
<!-- 声网灵隼控制台的应用配置>>开发者选项中的 Master Server URL -->
<string name="MASTER_SERVER_URL">https://app.agoralink-iot-cn.sd-rtn.com</string>
<!-- 声网灵隼控制台的应用配置>>开发者选项中的 Slave Server URL -->
<string name="SALVE_SERVER_URL">https://api.sd-rtn.com/agoralink/cn/api</string>
<!-- 配置环信SDK的AppId -->
<string name="EASEMOB_APPKEY">81315488#963933</string>
<!-- 配置 Firebase 离线推送参数 -->
<bool name="FCM_ENBALE"></bool>
<string name="FCM_SENDERID"></string>
<!-- 配置小米手机离线推送参数 -->
<string name="MI_PUSH_APPID"></string>
<string name="MI_PUSH_APPKEY"></string>
<!-- 配置魅族手机离线推送参数 -->
<string name="MEIZU_PUSH_APPID"></string>
<string name="MEIZU_PUSH_APPKEY"></string>
<!-- 配置 OPPO 手机离线推送参数 -->
<string name="OPPO_PUSH_APPKEY"></string>
<string name="OPPO_PUSH_APPSECRET"></string>
<!-- 配置 VIVIO 手机离线推送参数 -->
<integer name="VIVO_PUSH_APPID"></integer>
<string name="VIVO_PUSH_APPKEY"></string>
<!-- 配置华为手机离线推送参数,注意‘appid=’不要删除 -->
<string name="HUAWEI_PUSH_APPKID">appid=</string> -
连接上 Android 设备后,用 Android Studio 编译并运行示例项目。
-
直接输入要登录的账号名称。
-
登录成功后,会显示当前账号信息。
- 当前账号显示的是实际账号名称。
- 账号 ID 是账号的唯一标识。
- 对端账号 ID 可以输出要主动呼叫的对端账号 ID。
如果要呼叫的对端是一个设备,你可以根据设备的 (Product Key + Device ID) 计算出设备的账号 ID。
(可选)开通离线呼叫
当 Android 端应用不再运行时(包括不在后台运行时或应用进程不存在时),离线呼叫可以实现在有设备端呼叫客户端时,客户端能收到后台服务,并启动应用来接听设备端呼叫。你可以参考以下步骤开通离线呼叫:
-
在
app\src\main\res\values\config.xml
文件中配置离线呼叫的相应参数。XML<!-- 声网灵隼控制台的应用配置>>开发者选项中的 Easemob App Key -->
<string name="EASEMOB_APPKEY">your_easemob_appkey</string>
<!-- 配置 FCM 离线推送参数 -->
<bool name="FCM_ENBALE">false</bool>
<string name="FCM_SENDERID"></string>
<!-- 配置小米手机离线推送参数 -->
<string name="MI_PUSH_APPID"></string>
<string name="MI_PUSH_APPKEY"></string>
<!-- 配置 OPPO 手机离线推送参数 -->
<string name="OPPO_PUSH_APPKEY"></string>
<string name="OPPO_PUSH_APPSECRET"></string>
<!-- 配置 VIVIO 手机离线推送参数 -->
<integer name="VIVO_PUSH_APPID">0</integer>
<string name="VIVO_PUSH_APPKEY"></string>
<!-- 配置华为手机离线推送参数,注意‘appid=’不要删除 -->
<string name="HUAWEI_PUSH_APPKID">appid=106736987</string> -
恢复
\app\src\main\AndroidManifest.xml
文件中关于离线推送注释的代码。主要包括离线推送权限配置、离线推送账号配置和离线推送服务配置。XML<!-- <!– ==================== 离线推送权限 BEGIN ==================== –>-->
<!-- <!– Mi推送权限配置 start –>-->
<!-- <permission-->
<!-- android:name="${applicationId}.permission.MIPUSH_RECEIVE"-->
<!-- android:protectionLevel="signature" />-->
<!-- <uses-permission android:name="${applicationId}.permission.MIPUSH_RECEIVE" />-->
<!-- <uses-permission android:name="android.permission.GET_TASKS"/>-->
<!-- <!– Mi推送权限配置 end –>-->
<!-- <!– HUAWEI 推送权限配置 start –>-->
<!-- <uses-permission android:name="android.permission.WAKE_LOCK" />-->
<!-- <!– HUAWEI 推送权限配置 end –>-->
<!-- <!– OPPO推送权限配置 start –>-->
<!-- <uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE"/>-->
<!-- <uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE"/>-->
<!-- <!– OPPO推送权限配置 end –>-->
<!-- <!– ==================== 离线推送权限 END ==================== –>-->
<!-- <!– ==================== 离线推送账号配置 BEGIN ==================== –>-->
<!-- <!– 环信账号配置 –>-->
<!-- <meta-data-->
<!-- android:name="EASEMOB_APPKEY"-->
<!-- android:value="@string/EASEMOB_APPKEY" />-->
<!-- <!– Mi账号配置 –>-->
<!-- <meta-data-->
<!-- android:name="com.mi.push.api_key"-->
<!-- android:value="@string/MI_PUSH_APPKEY" />-->
<!-- <meta-data-->
<!-- android:name="com.mi.push.app_id"-->
<!-- android:value="@string/MI_PUSH_APPID" />-->
<!-- <!–华为账号配置 –>-->
<!-- <meta-data-->
<!-- android:name="com.huawei.hms.client.appid"-->
<!-- android:value="@string/HUAWEI_PUSH_APPKID" />-->
<!-- <meta-data-->
<!-- android:name="push_kit_auto_init_enabled"-->
<!-- android:value="true"/>-->
<!-- <!– Oppo账号配置 –>-->
<!-- <meta-data-->
<!-- android:name="com.oppo.push.api_key"-->
<!-- android:value="@string/OPPO_PUSH_APPKEY" />-->
<!-- <meta-data-->
<!-- android:name="com.oppo.push.app_secret"-->
<!-- android:value="@string/OPPO_PUSH_APPSECRET" />-->
<!-- <!– VIVO账号配置 –>-->
<!-- <meta-data-->
<!-- android:name="com.vivo.push.api_key"-->
<!-- android:value="@string/VIVO_PUSH_APPKEY" />-->
<!-- <meta-data-->
<!-- android:name="com.vivo.push.app_id"-->
<!-- android:value="@integer/VIVO_PUSH_APPID" />-->
<!-- <!– ==================== 离线推送账号配置 END ==================== –>-->
<!-- <!– ==================== 离线推送服务 BEGIN ==================== –>-->
<!-- <!– Mi推送服务配置 start –>-->
<!-- <service-->
<!-- android:name="com.xiaomi.push.service.XMPushService"-->
<!-- android:enabled="true"-->
<!-- android:process=":pushservice" />-->
<!-- <service-->
<!-- android:name="com.xiaomi.push.service.XMJobService"-->
<!-- android:enabled="true"-->
<!-- android:exported="false"-->
<!-- android:permission="android.permission.BIND_JOB_SERVICE"-->
<!-- android:process=":pushservice" /> <!–注:此service必须在3.0.1版本以后(包括3.0.1版本)加入–>-->
<!-- <service-->
<!-- android:name="com.xiaomi.mipush.sdk.PushMessageHandler"-->
<!-- android:enabled="true"-->
<!-- android:exported="true" />-->
<!-- <service-->
<!-- android:name="com.xiaomi.mipush.sdk.MessageHandleService"-->
<!-- android:enabled="true" /> <!–注:此service必须在2.2.5版本以后(包括2.2.5版本)加入–>-->
<!-- <receiver-->
<!-- android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"-->
<!-- android:exported="true">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<!-- <receiver-->
<!-- android:name="com.xiaomi.push.service.receivers.PingReceiver"-->
<!-- android:exported="false"-->
<!-- android:process=":pushservice">-->
<!-- <intent-filter>-->
<!-- <action android:name="com.xiaomi.push.PING_TIMER" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<!-- <receiver android:name="com.hyphenate.push.platform.mi.EMMiMsgReceiver">-->
<!-- <intent-filter>-->
<!-- <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />-->
<!-- </intent-filter>-->
<!-- <intent-filter>-->
<!-- <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />-->
<!-- </intent-filter>-->
<!-- <intent-filter>-->
<!-- <action android:name="com.xiaomi.mipush.ERROR" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<!-- <!– Mi推送服务配置 end–>-->
<!-- <!– HuaWei 推送服务配置 start –>-->
<!-- <service android:name="io.agora.iotlinkdemo.huanxin.HMSPushService"-->
<!-- android:exported="false">-->
<!-- <intent-filter>-->
<!-- <action android:name="com.huawei.push.action.MESSAGING_EVENT" />-->
<!-- </intent-filter>-->
<!-- </service>-->
<!-- <!– HuaWei 推送服务配置 end –>-->
<!-- <!– Oppo推送服务配置 start –>-->
<!-- <service-->
<!-- android:name="com.heytap.msp.push.service.CompatibleDataMessageCallbackService"-->
<!-- android:permission="com.coloros.mcs.permission.SEND_MCS_MESSAGE"-->
<!-- android:exported="false" >-->
<!-- <intent-filter>-->
<!-- <action android:name="com.coloros.mcs.action.RECEIVE_MCS_MESSAGE"/>-->
<!-- </intent-filter>-->
<!-- </service> <!–兼容Q以下版本–>-->
<!-- <service-->
<!-- android:name="com.heytap.msp.push.service.DataMessageCallbackService"-->
<!-- android:permission="com.heytap.mcs.permission.SEND_PUSH_MESSAGE"-->
<!-- android:exported="false">-->
<!-- <intent-filter>-->
<!-- <action android:name="com.heytap.mcs.action.RECEIVE_MCS_MESSAGE"/>-->
<!-- <action android:name="com.heytap.msp.push.RECEIVE_MCS_MESSAGE"/>-->
<!-- </intent-filter>-->
<!-- </service> <!–兼容Q版本–>-->
<!-- <!– Oppo推送服务配置 end –>-->
<!-- <!– VIVO推送服务配置 start –>-->
<!-- <service-->
<!-- android:name="com.vivo.push.sdk.service.CommandClientService"-->
<!-- android:exported="true" />-->
<!-- <activity-->
<!-- android:name="com.vivo.push.sdk.LinkProxyClientActivity"-->
<!-- android:exported="false"-->
<!-- android:screenOrientation="portrait"-->
<!-- android:theme="@android:style/Theme.Translucent.NoTitleBar" />-->
<!-- <receiver-->
<!-- android:name="com.hyphenate.push.platform.vivo.EMVivoMsgReceiver"-->
<!-- android:exported="false" >-->
<!-- <intent-filter>-->
<!-- <!– 接收 push 消息 –>-->
<!-- <action android:name="com.vivo.pushclient.action.RECEIVE" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<!-- <!– VIVO推送服务配置 end –>-->
<!-- <!– ==================== 离线推送服务 END ==================== –>--> -
恢复
app\build.gradle
中标注为各手机厂商离线推送 SDK
的代码 ,以集成各家手机厂商的离线推送 SDK。gradle// // 各手机厂商离线推送SDK
// //implementation(name: 'oppo_push_v2.1.0', ext: 'aar')
// implementation 'com.huawei.hms:push:4.0.2.300'
// implementation files('libs/mi_push_v3.6.12.jar')
// implementation files('libs/vivo_push_v2.3.1.jar')
// implementation 'io.hyphenate:hyphenate-push:0.1.0' // 环信
//
// // Google firebase 离线推送
// implementation 'com.google.android.gms:play-services-base:18.1.0'
// implementation 'com.google.firebase:firebase-messaging:23.0.6' -
\app\src\main\io\agora\iotlinkdemo\base\PushApplication.java
文件的PushApplication.onCreate()
方法中,恢复 import 语句和标注为初始化环信的离线推送
的代码。Java//import io.agora.iotlinkdemo.huanxin.EmAgent;
//
// 初始化环信的离线推送
//
// if (mMetaData != null)
// {
// EmAgent.EmPushParam pushParam = new EmAgent.EmPushParam();
// pushParam.mFcmSenderId = mMetaData.getString("com.fcm.push.senderid", "");
// pushParam.mMiAppId = mMetaData.getString("com.mi.push.app_id", "");
// pushParam.mMiAppKey = mMetaData.getString("com.mi.push.api_key", "");
// pushParam.mOppoAppKey = mMetaData.getString("com.oppo.push.api_key", "");
// pushParam.mOppoAppSecret = mMetaData.getString("com.oppo.push.app_secret", "");;
// pushParam.mVivoAppId = String.valueOf(mMetaData.getInt("com.vivo.push.app_id", 0));
// pushParam.mVivoAppKey = mMetaData.getString("com.vivo.push.api_key", "");
// pushParam.mHuaweiAppId = mMetaData.getString("com.huawei.hms.client.appid", "");
// EmAgent.getInstance().initialize(this, pushParam);
// }
} -
在
\app\src\main\io\agora\iotlinkdemo\base\PushApplication.java
文件的PushApplication.initializeEngine()
方法中,恢复以下代码:JavainitParam.mPusherId = EmAgent.getInstance().getEid();
-
在
\app\src\main\io\agora\iotlinkdemo\huanxin\EmAgent.java
文件,恢复全部代码。
对于华为推送,你还要进行以下额外步骤:
-
恢复以下文件中的全部代码:
\app\src\main\io\agora\iotlinkdemo\huanxin\HMSPushHelper.java
\app\src\main\io\agora\iotlinkdemo\huanxin\HMSPushService.java
-
从AppGallery Connect下载
agconnect-services.json
文件,覆盖\app\agconnect-services.json
文件。 -
将证书文件
iot.jks
的 SHA256 指纹,填写到AppGallery Connect中。
客户端实现
Android 客户端的核心逻辑如下。
配置离线推送账号信息
在你的 Android 项目中,修改 AndroidManifest.xml
文件配置离线推送账号信息。参考开通声网灵隼服务获取账号信息。
<!-- 离线推送的账号配置 start -->
<!-- 离线消息通道通知 -->
<meta-data
android:name="EASE_NOTIFIER_NOTIFY_ID"
android:value="@integer/EASE_NOTIFIER_MSG_ID" />
<meta-data
android:name="EASE_NOTIFIER_CHANNEL_ID"
android:value="@string/EASE_NOTIFIER_CHANNEL_ID" />
<!-- 环信账号配置 -->
<meta-data
android:name="EASEMOB_APPKEY"
android:value="@string/EASEMOB_APPKEY" />
<!-- Firebase账号配置 -->
<meta-data
android:name="com.fcm.push.enable"
android:value="@bool/FCM_ENBALE"/>
<meta-data
android:name="com.fcm.push.senderid"
android:value="@string/FCM_SENDERID"/>
<!-- Mi账号配置 -->
<meta-data
android:name="com.mi.push.api_key"
android:value="@string/MI_PUSH_APPKEY" />
<meta-data
android:name="com.mi.push.app_id"
android:value="@string/MI_PUSH_APPID" />
<!-- 魅族账号配置 -->
<meta-data
android:name="com.meizu.push.api_key"
android:value="@string/MEIZU_PUSH_APPKEY" />
<meta-data
android:name="com.meizu.push.app_id"
android:value="@string/MEIZU_PUSH_APPID" />
<!-- Oppo账号配置 -->
<meta-data
android:name="com.oppo.push.api_key"
android:value="@string/OPPO_PUSH_APPKEY" />
<meta-data
android:name="com.oppo.push.app_secret"
android:value="@string/OPPO_PUSH_APPSECRET" />
<!-- VIVO账号配置 -->
<meta-data
android:name="com.vivo.push.api_key"
android:value="@string/VIVO_PUSH_APPKEY" />
<meta-data
android:name="com.vivo.push.app_id"
android:value="@integer/VIVO_PUSH_APPID" />
<meta-data
android:name="com.agora.demo.productkey"
android:value="@string/product_key" />
<!--华为账号配置 -->
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="@string/HUAWEI_PUSH_APPKID" />
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="true"/>
<!-- 离线推送的账号配置 end -->
初始化
参考以下步骤调用 initialize()
方法来初始化。参考开通声网灵隼服务获取相关参数。
//
// 初始化 SDK
//
// 调用 SDK 初始化接口,返回错误码为 0,表示初始化成功
IAgoraCallkitSdk.InitParam initParam = new IAgoraCallkitSdk.InitParam();
initParam.mContext = this;
initParam.mRtcAppId = appId; // 声网 App ID
// 开发者选项>>呼叫服务>>Project ID 获取
initParam.mProjectId = projectId;
// 开发者选项>>呼叫服务>>Master Server URL 获取
initParam.mMasterServerUrl = masterUrl;
// 开发者选项>>呼叫服务>>Slave Server URL 获取
initParam.mSlaveServerUrl = slaveUrl;
initParam.mPublishVideo = false; // 客户端默认不推送视频流
initParam.mPublishAudio = true; // 客户端默认不推送音频流
initParam.mSubscribeAudio = true; // 客户端默认订阅视频流
initParam.mSubscribeVideo = true; // 客户端默认订阅视频流
// 设置日志文件路径,也可以不配置
String storageRootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
initParam.mLogFilePath = storageRootPath + "/callkit.log"; // 设置日志路径
// 调用 SDK 初始化接口,返回错误码为 0,表示初始化成功
int ret = ACallkitSdkFactory.getInstance().initialize(initParam);
登录用户账号
你不需要进行账号注册,直接输入用户账号名字进行无密码登录。
// 使用账号进行登录
int ret = AIotAppSdkFactory.getInstance().getAccountMgr().login(accountName, password);
// 登录成功/失败后,触发 IAccoungMgr.ICallback.onLoginDone() 回调
@Override
public void onLoginDone(int errCode, String account) { }
登录成功后,本机可以开始进行呼叫、接听和挂断操作。
String localAccountId = ACallkitSdkFactory.getInstance().getAccountMgr().getAccountId();
客户端呼叫设备端
-
客户端发出呼叫请求。
Java// 通过对端账号 ID 来呼叫对端,可以同时附带一些呼叫消息
int ret = ACallkitSdkFactory.getInstance().getCallkitMgr().callDial(peerAccountId, attachMsg);
// 呼叫完成后,触发 ICallkitMgr.ICallback.onDialDone()回调事件
// 可以根据 errCode 错误码判断呼叫是否成功,或者对端是否忙
@Override
public void onDialDone(int errCode, final String peerAccountId) { } -
客户端发出呼叫请求成功后,设备端会有三种状态回应:
- 设备端正常接听,客户端接收到
ICallkitMgr.ICallback.onPeerAnswer(IotDevice iotDev)
回调事件,进入双方通话状态。 - 设备端拒绝接听,客户端接收到
ICallkitMgr.ICallback.onPeerHangup(IotDevice iotDev)
回调事件,结束本次通话。 - 设备端超时不回应,客户端接收到
ICallkitMgr.ICallback.onPeerTimer(IotDevice iotDev)
回调事件,结束本次通话。
如果呼叫的对端是一个设备,并且知道设备的 productKey 和 deviceId,可以按照固定规则来生成对端设备的 accountId。
具体规则如下:
Java// 根据设备的 (productKey + deviceId) 生成相应的 accountId
String peerName = productKey + "-" + deviceId;
String base32Name = Base32.encodeAsString(peerName.getBytes());
String devAccountId = base32Name.replace("=", ""); - 设备端正常接听,客户端接收到
-
正常接通时,客户端会接收到
ICallkitMgr.ICallback.onPeerFirstVideo(IotDevice iotDevice, int videoWidth, int videoHeight)
事件,表示接收到设备端首帧视频帧,应用层可以根据自己实际的显示业务逻辑进行处理。正常通话过程中,客户端可以发送音视频,对端也可以发送音视频。客户端可以调用AIotAppSdkFactory.getInstance().getCallkitMgr().setAudioEffect();
来设置各种变声效果。客户端也可以设置设备端是否推送音视频流等达到静音等效果,详见ICallkitMgr
接口提供的方法。 -
如果客户端想要结束通话,调用
AIotAppSdkFactory.getInstance().getCallkitMgr().callHangup();
方法主动挂断电话。在通话过程中接收到ICallkitMgr.ICallback.onPeerHangup(IotDevice iotDev)
事件,说明对端设备主动挂断,结束通话。
设备端呼叫客户端
-
客户端处于空闲状态时,如果有设备端呼叫,会接收到来电呼叫事件
ICallkitMgr.ICallback.onPeerIncoming()
,你可以根据自己的业务逻辑显示来电界面。Java// 来自于设备端的呼叫,附带消息
@Override
public void onPeerIncoming(IotDevice iotDevice, String attachMsg) -
客户端根据实际的业务逻辑进行处理。
- 如果客户端正常接听,则调用
ICallkitMgr.getCallkitMgr. callAnswer(IotDevice iotDev)
方法,进入双方通话状态。 - 如果客户端拒绝接听,则调用
ICallkitMgr.getCallkitMgr.callHangup(IotDevice iotDev)
方法挂断 ,结束本次通话。 - 如果客户端不回应,设备端会超时主动挂断,客户端接收到
ICallkitMgr.ICallback. onPeerHangup(String peerAccount)
回调事件,结束本次通话。
- 如果客户端正常接听,则调用
-
正常接通时,客户端会接收到
ICallkitMgr.ICallback.onPeerFirstVideo(IotDevice iotDevice, int videoWidth, int videoHeight)
事件,表示接收到设备端首帧视频帧,应用层可以根据自己实际的显示业务逻辑进行处理。正常通话过程中,客户端可以发送音视频,对端也可以发送音视频。客户端可以调用ACallkitSdkFactory.getInstance().getCallkitMgr().setAudioEffect();
来设置各种变声效果。客户端也可以设置设备端是否推送音视频流等达到静音等效果,详见ICallkitMgr
接口提供的方法。 -
如果客户端想要结束通话,调用
ACallkitSdkFactory.getInstance().getCallkitMgr().callHangup();
方法主动挂断电话。在通话过程中接收到ICallkitMgr.ICallback. onPeerHangup(String peerAccount)
事件,说明对端设备主动挂断,结束通话。
参考信息
你可以通过客户端 Android API 参考(纯呼叫)了解 API 的详细信息。