# 客户端实现（纯呼叫版本） (/zh-CN/solutions/iot-apaas/build/implement-core-features/client-call-minimum)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
本文介绍如何使用声网灵隼**纯呼叫版本**的客户端示例项目在 Android 客户端实现呼叫和通话等功能。

:::info[信息]
**纯呼叫版本的客户端示例项目**提供呼叫、实时音视频等功能。你需要自行实现账号管理和设备管理相关逻辑。

如果你已有自研的设备管理等模块，声网建议你使用纯呼叫版本示例项目。
:::

## 前提条件 [#前提条件]

开始前，请确保你的开发环境满足以下条件：

- Android Studio 4.1.2 或以上版本。
- Android API level 16 或以上。
- 运行 Android 4.1 或以上版本的移动设备。
- 参考[开通并配置声网灵隼物联网服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)开通灵隼服务。

## 业务流程 [#业务流程]

在纯呼叫模式下，你需要自行实现账号系统与设备管理功能。基本流程如下：

![纯呼叫业务流程](https://web-cdn.agora.io/docs-files/1659003380889)

## 跑通示例项目 [#跑通示例项目]

### 运行步骤 [#运行步骤]

1. 先获取与你使用的 SDK 版本匹配的 Android 客户端示例工程。原 GitHub 下载入口暂不可用，请[联系技术支持](https://ticket.shengwang.cn/)确认获取方式，再继续以下步骤。

2. 使用 Android Studio 加载工程后，在 **app&gt;&gt;res&gt;&gt;values&gt;&gt;config.xml** 文件中，将以下配置项修改为你在灵隼控制台的**应用配置&gt;&gt;开发者选项**页面中获取的相关参数。详见[开通声网灵隼服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)。

	```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>
	```

3. 连接上 Android 设备后，用 Android Studio 编译并运行示例项目。

4. 直接输入要登录的账号名称。

<DocsImage alt="跑通效果" src="https://web-cdn.agora.io/docs-files/1658823566203" displayWidth="40%" />

5. 登录成功后，会显示当前账号信息。
    - **当前账号**显示的是实际账号名称。
    - **账号 ID** 是账号的唯一标识。
    - **对端账号 ID** 可以输出要主动呼叫的对端账号 ID。

     如果要呼叫的对端是一个设备，你可以根据设备的 (Product Key + Device ID) 计算出设备的账号 ID。

<DocsImage alt="对端账号 ID" src="https://web-cdn.agora.io/docs-files/1658823557855" displayWidth="40%" />

### （可选）开通离线呼叫 [#可选开通离线呼叫]

当 Android 端应用不再运行时（包括不在后台运行时或应用进程不存在时），离线呼叫可以实现在有设备端呼叫客户端时，客户端能收到后台服务，并启动应用来接听设备端呼叫。你可以参考以下步骤开通离线呼叫：

1. 在 `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>
    ```

2. 恢复  `\app\src\main\AndroidManifest.xml` 文件中关于离线推送注释的代码。主要包括离线推送权限配置、离线推送账号配置和离线推送服务配置。

    ```xml
    <!--    &lt;!&ndash; ==================== 离线推送权限 BEGIN ==================== &ndash;&gt;-->
    <!--        &lt;!&ndash; Mi推送权限配置 start &ndash;&gt;-->
    <!--        <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"/>-->
    <!--        &lt;!&ndash; Mi推送权限配置 end &ndash;&gt;-->

    <!--        &lt;!&ndash; HUAWEI 推送权限配置 start &ndash;&gt;-->
    <!--        <uses-permission android:name="android.permission.WAKE_LOCK" />-->
    <!--        &lt;!&ndash; HUAWEI 推送权限配置 end &ndash;&gt;-->

    <!--        &lt;!&ndash; OPPO推送权限配置 start &ndash;&gt;-->
    <!--        <uses-permission android:name="com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE"/>-->
    <!--        <uses-permission android:name="com.heytap.mcs.permission.RECIEVE_MCS_MESSAGE"/>-->
    <!--        &lt;!&ndash; OPPO推送权限配置 end &ndash;&gt;-->
    <!--    &lt;!&ndash; ==================== 离线推送权限 END ==================== &ndash;&gt;-->

    <!--        &lt;!&ndash; ==================== 离线推送账号配置 BEGIN ==================== &ndash;&gt;-->
    <!--            &lt;!&ndash; 环信账号配置 &ndash;&gt;-->
    <!--            <meta-data-->
    <!--                android:name="EASEMOB_APPKEY"-->
    <!--                android:value="@string/EASEMOB_APPKEY" />-->

    <!--            &lt;!&ndash; Mi账号配置 &ndash;&gt;-->
    <!--            <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" />-->

    <!--            &lt;!&ndash;华为账号配置 &ndash;&gt;-->
    <!--            <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"/>-->

    <!--            &lt;!&ndash; Oppo账号配置 &ndash;&gt;-->
    <!--            <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" />-->

    <!--            &lt;!&ndash; VIVO账号配置 &ndash;&gt;-->
    <!--            <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" />-->
    <!--        &lt;!&ndash; ==================== 离线推送账号配置 END ==================== &ndash;&gt;-->

    <!--        &lt;!&ndash; ==================== 离线推送服务 BEGIN ==================== &ndash;&gt;-->
    <!--            &lt;!&ndash; Mi推送服务配置 start &ndash;&gt;-->
    <!--            <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" /> &lt;!&ndash;注：此service必须在3.0.1版本以后（包括3.0.1版本）加入&ndash;&gt;-->
    <!--            <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" /> &lt;!&ndash;注：此service必须在2.2.5版本以后（包括2.2.5版本）加入&ndash;&gt;-->
    <!--            <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>-->
    <!--            &lt;!&ndash; Mi推送服务配置 end&ndash;&gt;-->

    <!--            &lt;!&ndash; HuaWei 推送服务配置 start &ndash;&gt;-->
    <!--            <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>-->
    <!--            &lt;!&ndash; HuaWei 推送服务配置 end &ndash;&gt;-->

    <!--            &lt;!&ndash; Oppo推送服务配置 start &ndash;&gt;-->
    <!--            <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> &lt;!&ndash;兼容Q以下版本&ndash;&gt;-->
    <!--            <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> &lt;!&ndash;兼容Q版本&ndash;&gt;-->
    <!--            &lt;!&ndash; Oppo推送服务配置 end &ndash;&gt;-->

    <!--            &lt;!&ndash; VIVO推送服务配置 start &ndash;&gt;-->
    <!--            <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>-->
    <!--                    &lt;!&ndash; 接收 push 消息 &ndash;&gt;-->
    <!--                    <action android:name="com.vivo.pushclient.action.RECEIVE" />-->
    <!--                </intent-filter>-->
    <!--            </receiver>-->
    <!--            &lt;!&ndash; VIVO推送服务配置 end &ndash;&gt;-->
    <!--        &lt;!&ndash; ==================== 离线推送服务 END ==================== &ndash;&gt;-->
    ```

3. 恢复 `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'
    ```

4.  `\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);
    //        }
        }
    ```

5. 在 `\app\src\main\io\agora\iotlinkdemo\base\PushApplication.java` 文件的 `PushApplication.initializeEngine()` 方法中，恢复以下代码：

    ```java
    initParam.mPusherId = EmAgent.getInstance().getEid();
    ```

6. 在  `\app\src\main\io\agora\iotlinkdemo\huanxin\EmAgent.java` 文件，恢复全部代码。

对于**华为推送**，你还要进行以下额外步骤：

1. 恢复以下文件中的全部代码：
	 - `\app\src\main\io\agora\iotlinkdemo\huanxin\HMSPushHelper.java`
	 - `\app\src\main\io\agora\iotlinkdemo\huanxin\HMSPushService.java`

2. 从[AppGallery Connect](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/android-config-agc-0000001050170137)下载 `agconnect-services.json` 文件，覆盖 `\app\agconnect-services.json` 文件。

3. 将证书文件 `iot.jks` 的 SHA256 指纹，填写到[AppGallery Connect](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/android-config-agc-0000001050170137)中。

## 客户端实现 [#客户端实现]

Android 客户端的核心逻辑如下。

### 配置离线推送账号信息 [#配置离线推送账号信息]

在你的 Android 项目中，修改 `AndroidManifest.xml` 文件配置离线推送账号信息。参考[开通声网灵隼服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)获取账号信息。

```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()` 方法来初始化。参考[开通声网灵隼服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)获取相关参数。

```java
//
// 初始化 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);
```

### 登录用户账号 [#登录用户账号]

你不需要进行账号注册，直接输入用户账号名字进行无密码登录。

```java
// 使用账号进行登录
int ret = AIotAppSdkFactory.getInstance().getAccountMgr().login(accountName, password);

// 登录成功/失败后，触发 IAccoungMgr.ICallback.onLoginDone() 回调
@Override
public void onLoginDone(int errCode, String account) { }
```

登录成功后，本机可以开始进行呼叫、接听和挂断操作。

```java
String localAccountId = ACallkitSdkFactory.getInstance().getAccountMgr().getAccountId();
```

### 客户端呼叫设备端 [#客户端呼叫设备端]

1. 客户端发出呼叫请求。

   ```java
   //  通过对端账号 ID 来呼叫对端，可以同时附带一些呼叫消息
    int ret = ACallkitSdkFactory.getInstance().getCallkitMgr().callDial(peerAccountId, attachMsg);
   // 呼叫完成后，触发 ICallkitMgr.ICallback.onDialDone()回调事件
   // 可以根据 errCode 错误码判断呼叫是否成功，或者对端是否忙
    @Override
    public void onDialDone(int errCode, final String peerAccountId) {  }
   ```

2. 客户端发出呼叫请求成功后，设备端会有三种状态回应：

    - 设备端正常接听，客户端接收到 `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("=", "");
	```

3. 正常接通时，客户端会接收到 `ICallkitMgr.ICallback.onPeerFirstVideo(IotDevice iotDevice, int videoWidth, int videoHeight)` 事件，表示接收到设备端首帧视频帧，应用层可以根据自己实际的显示业务逻辑进行处理。正常通话过程中，客户端可以发送音视频，对端也可以发送音视频。客户端可以调用 `AIotAppSdkFactory.getInstance().getCallkitMgr().setAudioEffect();` 来设置各种变声效果。客户端也可以设置设备端是否推送音视频流等达到静音等效果，详见 `ICallkitMgr` 接口提供的方法。

4. 如果客户端想要结束通话，调用 `AIotAppSdkFactory.getInstance().getCallkitMgr().callHangup();` 方法主动挂断电话。在通话过程中接收到 `ICallkitMgr.ICallback.onPeerHangup(IotDevice iotDev)`  事件，说明对端设备主动挂断，结束通话。

### 设备端呼叫客户端 [#设备端呼叫客户端]

1. 客户端处于空闲状态时，如果有设备端呼叫，会接收到来电呼叫事件 `ICallkitMgr.ICallback.onPeerIncoming()`，你可以根据自己的业务逻辑显示来电界面。

    ```java
    // 来自于设备端的呼叫，附带消息
    @Override
    public void onPeerIncoming(IotDevice iotDevice, String attachMsg)
    ```

2. 客户端根据实际的业务逻辑进行处理。

    - 如果客户端正常接听，则调用 `ICallkitMgr.getCallkitMgr. callAnswer(IotDevice iotDev)` 方法，进入双方通话状态。
    - 如果客户端拒绝接听，则调用 `ICallkitMgr.getCallkitMgr.callHangup(IotDevice iotDev)` 方法挂断 ，结束本次通话。
    - 如果客户端不回应，设备端会超时主动挂断，客户端接收到 `ICallkitMgr.ICallback. onPeerHangup(String peerAccount)`  回调事件，结束本次通话。

3. 正常接通时，客户端会接收到 `ICallkitMgr.ICallback.onPeerFirstVideo(IotDevice iotDevice, int videoWidth, int videoHeight)` 事件，表示接收到设备端首帧视频帧，应用层可以根据自己实际的显示业务逻辑进行处理。正常通话过程中，客户端可以发送音视频，对端也可以发送音视频。客户端可以调用 `ACallkitSdkFactory.getInstance().getCallkitMgr().setAudioEffect();` 来设置各种变声效果。客户端也可以设置设备端是否推送音视频流等达到静音等效果，详见 `ICallkitMgr` 接口提供的方法。

4. 如果客户端想要结束通话，调用 `ACallkitSdkFactory.getInstance().getCallkitMgr().callHangup();` 方法主动挂断电话。在通话过程中接收到 `ICallkitMgr.ICallback. onPeerHangup(String peerAccount)`  事件，说明对端设备主动挂断，结束通话。

## 参考信息 [#参考信息]

你可以通过客户端 Android API 参考（纯呼叫）了解 API 的详细信息。

- [客户端纯呼叫 API 参考](/zh-CN/api-reference/iot-apaas/client-api/call)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
本文介绍如何使用声网灵隼**纯呼叫版本**的客户端示例项目在 iOS 客户端实现呼叫和通话等功能。

:::info[信息]
**纯呼叫版本的客户端示例项目**提供呼叫、实时音视频等功能。你需要自行实现账号管理和设备管理相关逻辑。

如果你已有自研的设备管理等模块，声网建议你使用纯呼叫版本示例项目。
:::

## 前提条件 [#前提条件-1]

开始前，请确保你的开发环境满足以下条件：

- Xcode（推荐最新版本）。
- iOS 12.0 或以上版本的 Apple 设备。
- 参考[开通并配置声网灵隼物联网服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)开通灵隼服务。

## 业务流程 [#业务流程-1]

在纯呼叫模式下，你需要自行实现账号系统与设备管理功能。基本流程如下：

![纯呼叫业务流程](https://web-cdn.agora.io/docs-files/1659003380889)

## 跑通示例项目 [#跑通示例项目-1]

1. 从 [GitHub](https://github.com/AgoraIO-Community/ag-iot-callkit-ios-app) 上下载最新版本的 iOS 客户端示例项目。

2. 在项目的根目录运行 `pod install` 安装依赖。
3. 连接上 iOS 设备后，点击 `.xcworkspace` 文件打开项目，并在 iOS Device 选项中勾选上你的 iOS 设备。
4. 使用 Xcode 打开项目。修改配置文件 `Config.swift`，将以下配置项修改为你在灵隼控制台的**应用配置&gt;&gt;开发者选项**页面中获取的相关参数。详见[开通并配置声网灵隼服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)。

    ```swift
    // 声网灵隼控制台的应用配置>>开发者选项中的 App ID
    public static let appId = "4b31f**********************3037"  //appid
    // 声网灵隼控制台的应用配置>>开发者选项中的 Project ID
    public static let projectId:String = "a******_C" // 项目id"
    //  声网灵隼控制台的应用配置>>开发者选项中的 Easemob App Key
    public static let ntfAppKey: String = "52****88#3****2" //离线推送的appkey
    // APNS 证书名。用于离线推送。在开通声网灵隼服务之后，可以在应用配置页面中的开发者选项选项卡中获取
    public static let ntfApnsCertName:String = "xxx.xxx.xxx"//离线推送的AnpsCertName
    // 声网灵隼控制台的应用配置>>开发者选项中的 Master Server URL
    public static let masterServerUrl:String = "https://un2nfllop5.execute-api.cn-north-1.amazonaws.com.cn/Prod"
    // 声网灵隼控制台的应用配置>>开发者选项中的 Slave Server URL
    public static let slaveServerUrl:String = "https://api.sd-rtn.com/agoralink/cn/api"
    ```

5. 在项目 TARGETS 下的 **Signing & Capabilities** 界面勾选 **Automatically manage signing**，配置你的苹果开发者账号和 Bundle Identifier。
6. 连接上 Apple 设备后，编译并运行项目。
7. 进入主界面，直接输入要登录的账号名称。
8. 登录成功后，会显示当前账号信息。 如果要呼叫的对端是一个设备，你可以根据设备的 (Porduct Key + deviceId) 来计算出设备的账号 ID。
9. 当对端有呼叫进来时，可以进行接听处理，接听之后进入正常的通话界面。

## 客户端实现 [#客户端实现-1]

iOS 客户端的核心逻辑如下。

### 初始化 [#初始化-1]

参考以下步骤调用 `initialize` 方法进行初始化。

```swift
let param:InitParam = InitParam()
// 声网 App ID。在开通声网灵隼服务之后，可以在应用配置页面中的开发者选项选项卡中获取
param.rtcAppId = Config.appId
param.logFilePath = FileCenter.logFilePath()
param.subscribeVideo = true
param.subscribeAudio = true
param.publishVideo = false
param.publishAudio = false
// APNS 证书名。在开通声网灵隼服务之后，可以在应用配置页面中的开发者选项选项卡中获取
param.ntfApnsCertName = Config.ntfApnsCertName
// APNS 证书秘钥。在开通声网灵隼服务之后，可以在应用配置页面中的开发者选项选项卡中获取
param.ntfAppKey = Config.ntfAppKey
// 开发者选项>>呼叫服务>>Master Server URL 获取
param.masterServerUrl = "http://iot-api-gateway.sh.agoralab.co/api"
// 开发者选项>>呼叫服务>>Slave Server URL 获取
param.slaveServerUrl = "https://un2nfllop5.execute-api.cn-north-1.amazonaws.com.cn/Prod"
// 开发者选项>>呼叫服务>>Project ID 获取
param.projectId = "Your Project ID"
let ret = AgoraIotSdk.iotsdk.initialize(initParam: param, netStatus: {status,msg in}, callBackFilter: { ec, msg in return (ec,msg)})
```

### 登录用户账号 [#登录用户账号-1]

纯呼叫模式中不需要进行账号注册，直接输入用户账号名字进行无密码登录。如果该账号不存在，系统会自动分配新账号。

```swift
AgoraIotSdk.iotsdk.accountMgr.login(account: acc, result: {errCode,errMsg in})
```

当用户账号成功登录后，可以开始进行正式的操作，包括设备管理、告警信息查询、系统信息查询、用户信息更新等。

### 客户端呼叫设备端 [#客户端呼叫设备端-1]

1. 客户端发出呼叫请求。

   ```swift
   // 呼叫设备端，可以同时附带一些呼叫消息
   AgoraIotSdk.iotsdk.callkitMgr.callDial(device:dev,attachMsg: "",result:{
                (ec,msg) in
                cb(ec == ErrCode.XOK ? true : false , msg)
            },peerAction: {s in
                log.i("peer \(s)")
            })
   ```

2. 客户端发出呼叫请求成功后，设备端会有三种状态回应：

    - 如果设备端正常接听，`peerAction` 返回 `.Answer`，开始音视频通话。
    - 如果设备端拒绝接听，`peerAction` 返回 `.Hangup`，结束本次通话。
    - 如果设备端接听超时，`peerAction` 返回 `.Timeout`，结束本次通话。

3. 正常通话过程中，客户端可以发送音视频，对端也发送音视频。当设备接听后挂断电话，`peerAction` 返回 `.Hangup`。

4. 如果客户端想要结束通话，调用 `AgoraIotSdk.iotsdk.callkitMgr.callHangup()` 方法主动挂断电话。

    ```swift
    AgoraIotSdk.iotsdk.callkitMgr.callHangup(result: {errCode,errMsg in})
    ```

### 设备端呼叫客户端 [#设备端呼叫客户端-1]

1. 接收到来电呼叫事件需要在初始化完成后注册来电回调接口

    ```swift
    AgoraIotSdk.iotsdk.callkitMgr.register(incoming: {peerId,msg,action in})
    ```

2. 客户端处于空闲状态时，如果有设备端呼叫，会接收到来电呼叫事件 `AgoraIotSdk.iotsdk.ICallkitMgrCallback.onPeerIncoming`，你可以根据自己的业务逻辑显示来电界面。

3. 客户端根据实际的业务逻辑进行处理。

    - 如果客户端拒绝，则调用 `AgoraIotSdk.iotsdk.callkitMgr.callHangup()` 方法挂断 ，结束本次通话。
    - 如果客户端接听，则调用 `AgoraIotSdk.iotsdk.callkitMgr.callAnswer()` 方法，进入双方通话状态。客户端可以使用 `AgoraIotSdk.iotsdk.callkitMgr.callHangup()` 主动挂断通话。如果设备端挂断通话，客户端会收到 `AgoraIotSdk.iotsdk.ICallkitMgrCallback.onPeerHangup()` 回调。

### 登出用户账号 [#登出用户账号]

你可以通过以下方法登出用户账号。

```swift
AgoraIotSdk.iotsdk.accountMgr.logout(result: {errCode,errMsg in})
```

## 参考信息 [#参考信息-1]

- [客户端纯呼叫 API 参考](/zh-CN/api-reference/iot-apaas/client-api/call)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="mini-program" />
本文介绍如何使用声网灵隼**纯呼叫版本**的客户端示例项目在 Android 客户端实现呼叫和通话等功能。

:::info[信息]
**纯呼叫版本的客户端示例项目**提供呼叫、实时音视频等功能。你需要自行实现账号管理和设备管理相关逻辑。

如果你已有自研的设备管理等模块，声网建议你使用纯呼叫版本示例项目。
:::

## 前提条件 [#前提条件-2]

开始前，请确保你已参考[开通并配置声网灵隼物联网服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)开通灵隼服务。

## 业务流程 [#业务流程-2]

在纯呼叫模式下，你需要自行实现账号系统与设备管理功能。基本流程如下：

![纯呼叫业务流程](https://web-cdn.agora.io/docs-files/1659003380889)

## 环境准备 [#环境准备]

### 注册微信小程序账号并获取微信开发者工具 [#注册微信小程序账号并获取微信开发者工具]

1. 注册微信小程序企业账号。只有特定行业的认证企业账号才可以使用微信小程序的 live-pusher 和 live--player 组件。详见[小程序官网文档](https://developers.weixin.qq.com/miniprogram/dev/component/live-player.html)。
2. 下载并安装最新版本的[微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)。

### 获取小程序组件权限 [#获取小程序组件权限]

在[微信公众平台](https://mp.weixin.qq.com/)的小程序**开发**选项中，切换到**接口设置**页签，打开**实时播放音视频流**和**实时录制音视频流**的开关。

![打开媒体开关](https://web-cdn.agora.io/docs-files/1605069073929)

### 配置服务器域名 [#配置服务器域名]

你需要在 **request 合法域名**及 **socket 合法域名**中添加以下域名：

```text
wss://a1g2ouvmztk85d.ats.iot.cn-north-1.amazonaws.com.cn;
wss://miniapp.agoraio.cn;
https://ap-web-1.agoraio.cn;
https://ap-web-2.agoraio.cn;
https://ap-web-3.agoraio.cn;
https://ap-web-4.agoraio.cn;
https://apaas-iot.sd-rtn.com;
https://iot-api-gateway.sh.agoralab.co;
https://iot.sh.agoralab.co;
https://miniapp.agoraio.cn;
https://report-ad.agoralab.co;
https://rest-argus-ad.agoralab.co;
https://uap-ap-web-1.agora.io;
https://uap-ap-web-2.agoraio.cn;
https://uap-ap-web-3.agora.io;
https://uap-ap-web-4.agoraio.cn;
https://un2nfllop5.execute-api.cn-north-1.amazonaws.com.cn;
https://uni-webcollector.agora.io;
```

## 跑通示例项目 [#跑通示例项目-2]

1. 下载[示例项目](https://github.com/AgoraIO-Community/ag-iot-callkit-wechat-app)并使用微信开发者工具打开项目。修改项目根目录下的 `callkit.config.js` 文件。参考[开通并配置声网灵隼物联网服务](/zh-CN/solutions/iot-apaas/build/setup-and-access/enable-service)从声网灵隼控制台的**应用配置&gt;&gt;开发者选项**页面获取所需参数。

	```javascript
	module.exports = {
			DEBUG: true,
			// 声网灵隼控制台的应用配置>>开发者选项中的 App ID
			APPID: 'YOUR_APPID',
			// 声网灵隼控制台的应用配置>>开发者选项中的 Product Key
			PRODUCT_KEY: 'YOUR_PRODUCT_KEY',
			// 声网灵隼控制台的应用配置>>开发者选项中的 Project ID 
			PROJECT_ID: 'YOUR_PROJECT_ID',
	}
	```

2. 在开发者工具中选择**工具 >> 构建 npm**。完成项目构建。

3. 在登录页面输入你的账号名，进行登录。

<DocsImage alt="登录页面" src="https://web-cdn.agora.io/docs-files/1658988564785" displayWidth="40%" />

4. 登录完成后，输入需要呼叫的设备 ID 并点击**呼叫设备**。

<DocsImage alt="点击呼叫设备" src="https://web-cdn.agora.io/docs-files/1658988561192" displayWidth="40%" />

## 实现客户端 [#实现客户端]

### 创建小程序项目 [#创建小程序项目]

参考如下步骤创建一个新的小程序。

1. 打开**微信开发者工具**，然后按照屏幕提示扫码登录。
2. 完成登录后，点击小程序界面的 **+**。
3. 在弹出的**新建项目**界面中，依次填入小程序的**项目名称**、本地存储**目录**、小程序 **AppID**，然后点击**新建**。

成功创建小程序后，微信开发者工具的左侧会显示当前小程序的界面。你还可以点击**真机调试**，并扫描生成的二维码，在手机上进行体验。

### 引用 index.js 文件 [#引用-indexjs-文件]

1. 下载[示例项目](https://github.com/AgoraIO-Community/ag-iot-callkit-wechat-app)。
2. 使用 `require` 将 `main/callkit/index.js` 集成到项目中：

    ```javascript
    const CallKitSDK = require('./callkit/index.js');
    ```

### 获取配置信息 [#获取配置信息]

通过灵隼控制台的**应用配置**页面中的**开发者选项**选项卡获取以下配置信息：

- App ID：声网的 App ID。
- Product Key：声网灵隼产品 ID。

本文将获取的配置定义为以下常量：

```javascript
export const appid = 'Your App ID'; 
export const PRODUCT_KEY = 'Your product key'; 
```

## 添加微信小程序组件 [#添加微信小程序组件]

音视频通话页面的 `.wxml` 文件中需要使用微信的 `live-player` 组件和 `live-pusher` 组件。

```javascript
<view>
    <view class="box flex-center-row">
        <live-player src="{{playUrl}}" mode="RTC" autoplay bindstatechange="statechange" binderror="error" style="width: 225px; height: 225px;" />
    </view>
    <view class="box flex-center-row">
        <live-pusher url="{{pushUrl}}" mode="RTC" autopush bindstatechange="statechange" style="width: 225px; height: 225px;" />
    </view>
    <view class="box flex-center-row">
        <button type="default" size="mini" bindtap="onHangup">挂断</button>
    </view>
</view>
```

## 初始化 SDK 并登录 [#初始化-sdk-并登录]

 ```javascript
 // 引入 callkit config 文件，在 init 项目的时候传入config文件
const config = require('../../callkit.config.js');

const username = 'agorauser'
CallKitSDK.getAccountManager().initAndLogin(config, username)
            .then(this.onUserLoginSuccess)
            .catch(this.onLoginFail);
 ```

## 注册呼叫事件回调 [#注册呼叫事件回调]

```javascript
onLoad() {
        // peer 呼叫请求事件
        this.peerRequestEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerRequestEventCallback((event) => {
            log.i('peerRequestEventCallback invoked');
            const attachMsg = event.attachMsg || '';
            // 调转到待接听界面
            wx.navigateTo({
                url: `/pages/answer/answer?attachMsg=${attachMsg}`,
            });
        });

        // peer 正在忙事件
        this.peerBusyEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerBusyEventCallback((event) => {
            log.i('peerBusyEventCallback invoked');
            wx.showToast({
                title: '对方正忙',
                icon: 'error',
                mask: true,
            });
        });

        // peer 接听事件
        this.peerAnswerEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerAnswerEventCallback((event) => {
            log.i('peerAnswerEventCallback invoked');
            wx.hideLoading();
            // peer 接听后跳转到 live 界面
            wx.navigateTo({
                url: '/pages/live/live',
            });
        });

        // peer 挂断事件
        this.peerHangupEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerHangupEventCallback((event) => {
            log.i('peerHangupEventCallback invoked');
            wx.showToast({
                title: '对方挂断',
                icon: 'error',
                mask: true,
            });
        });
    },
```

当退出小程序或者页面卸载时，可以调用返回的 unsubscribe 函数来删除订阅事件，节省资源。

```javascript
onUnload() {
        // 退出界面时解除所有事件的绑定
        this.peerRequestEventCallbackUnsubscribe();
        this.peerBusyEventCallbackUnsubscribe();
        this.peerAnswerEventCallbackUnsubscribe();
        this.peerHangupEventCallbackUnsubscribe();
    },
```

## 呼叫设备端 [#呼叫设备端]

```javascript
const deviceId = 'mydoorbell' // 设备端ID
const attachMsg = 'Hello, stranger.' // optional，携带给设备端的消息

CallKitSDK.getCallkitManager().callDevice(deviceId, attachMsg)
            .then((res) => {
            console.log('正在呼叫')
            }).catch((err) => {
                log.e(err);
                wx.showToast({
                    title: '呼叫设备失败',
                    icon: 'error',
                });
            });
```

当对方接听时，触发 `peerAnswerEventCallback` 回调。如果对方挂断，会收到 `peerHangupEventCallback` 的事件。如果对方忙，会收到 `peerBusyEventCallback` 的事件。如果此时设备端呼叫请求，那么 `peerRequestEventCallback` 会触发回调事件。

## 开始视频通话 [#开始视频通话]

通过 `peerStreamAddedEventCallback` 的回调获取拉流地址。然后调用 `userPublishStream` 接口来推送本地的推流地址。同时监听 `peerHangupEventCallback` 事件。

```javascript
/**
     * 生命周期函数--监听页面加载
     */
    onLoad(options) {
        wx.showLoading({
            title: '正在拉取流',
            mask: true,
        });

        // peer加入频道，获得播放地址
        this.peerStreamAddedEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerStreamAddedEventCallback((playUrl) => {
            log.i('peerStreamAddedEventCallback invoked');
            wx.hideLoading();
            this.setData({
                playUrl,
            });

            // 获得推流地址，开始推流
            CallKitSDK.getCallkitManager().userPublishStream().then((pushUrl) => {
                this.setData({
                    pushUrl,
                });
            }).catch((err) => {
                log.e(err);
                wx.switchTab({ url: '/pages/device/device' });
            });
        });

        // 如果peer挂断呼叫，本地则返回主界面
        this.peerHangupEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerHangupEventCallback((event) => {
            log.i('peerHangupEventCallback invoked');
            wx.showToast({
                title: '对方已挂断',
                duration: 1500,
            });
            setTimeout(() => {
                wx.switchTab({ url: '/pages/device/device' });
            }, 1500);
        });
    },
```

## 挂断通话 [#挂断通话]

调用 `hangupDevice` 接口来挂断当前通话。

```javascript
onHangup() {
        CallKitSDK.getCallkitManager().hangupDevice()
            .then((res) => {
                wx.switchTab({ url: '/pages/device/device' });
            }).catch((err) => {
                wx.switchTab({ url: '/pages/device/device' });
            });
    },
```

通过注册的回调处理设备端呼叫。

```javascript
onLoad(options) {
        this.setData({
            name: options.name || '',
            attachMsg: options.attachMsg || '',
        });

        // peer挂断事件，返回主界面
        this.peerHangupEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerHangupEventCallback((event) => {
            log.i('peerHangupEventCallback invoked');
            wx.showToast({
                title: '对方已挂断',
                icon: 'error',
                duration: 2000,
            });
            setTimeout(() => {
                wx.switchTab({ url: '/pages/device/device' });
            }, 2000);
        });

        // 待接听时，可以显示peer的预览画面
        this.peerStreamAddedEventCallbackUnsubscribe = CallKitSDK.getCallkitManager().peerStreamAddedEventCallback((playUrl) => {
            log.i('peerStreamAddedEventCallback invoked');
            this.setData({
                playUrl,
            });
        });
    },

    onAccept() {
        CallKitSDK.getCallkitManager().answerDevice()
            .then((res) => {
                wx.navigateTo({
                    url: '/pages/live/live',
                });
            }).catch((err) => {
                wx.switchTab({ url: '/pages/device/device' });
            });
    },

    onHangup() {
        CallKitSDK.getCallkitManager().hangupDevice()
            .then((res) => {
                wx.switchTab({ url: '/pages/device/device' });
            }).catch((err) => {
                wx.switchTab({ url: '/pages/device/device' });
            });
    },
```

## 参考信息 [#参考信息-2]

### 开发注意事项 [#开发注意事项]

声网推荐在 `app.js` 或者全局下订阅 `userSessionEndEventCallback` 事件，来监听用户登录状态的改变和 mqtt 连接状态的改变。如果发生改变，可以强制登出用户并且返回到登录界面。

```javascript
onLaunch() {
  CallKitSDK.getInstanceManager().userSessionEndEventCallback((user) => {
      wx.showToast({
          title: "登录超时",
          icon: 'error',
      })
      wx.redirectTo({
          url: '/pages/login/login',
      })
  })
}
```

### API 参考 [#api-参考]

- [客户端纯呼叫 API 参考](/zh-CN/api-reference/iot-apaas/client-api/call)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
