实现收发消息
- Swift
更新时间不可用
了解如何快速接入实时消息 RTM,完成环境准备、参数配置、示例运行和核心流程验证。
本文将指导你如何利用 RTM Java SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
同时,我们也提供了开源的示例项目供你参考,你可以前往下载或查看其中的源代码。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境是否满足平台支持中的最低版本要求。
构建项目
1. 项目配置
根据以下步骤配置你的项目:
- 在 Android Studio 里,创建一个 Empty Activity 类型的项目并打开。
- 通过以下任意一种方式获取最新的 RTM Java SDK 并导入你的项目。
-
点击此处下载最新版本的 Java SDK。
-
将 SDK 包内
sdk文件夹中的所有文件拷贝到项目的/app/libs路径下。 -
添加 SDK 引用。在 Project 窗口找到
/Gradle Scripts/build.gradle(Module: <projectname>.app)文件并添加如下代码:-
在
defaultConfig节点添加ndk节点,指定支持的架构。groovy Config { // ... ndk{ abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } }信息
支持的架构越多,App 的体积会越大,声网建议你根据实际情况添加需要支持的架构。一般在发布 Android App 时只需保留 armeabi-v7a 和 arm64-v8a 架构。
-
在
android节点添加sourceSets节点,指定libs所在目录。groovy android { // ... sourceSets { main { jniLibs.srcDirs = ['libs'] } } } -
在
dependencies节点引入libs路径下所有的 jar 库。groovy dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // ... }
-
-
在
/Gradle Scripts/build.gradle(Project: <projectname>)文件中添加如下代码,添加 Maven Central 依赖:Java buildscript { repositories { // ... mavenCentral() } ... } allprojects { repositories { // ... mavenCentral() } }信息
如果你的 Android 项目设置了 dependencyResolutionManagement,添加 Maven Central 依赖的方式可能存在差异。
-
在
/Gradle Scripts/build.gradle(Module: <projectname>.app)文件中添加如下代码,将 SDK 集成到你的 Android 项目中:groovy ... dependencies { // 将 x.y.z 替换为具体的 SDK 版本号,如 2.3.0 // 对于 2.2.7 及之后版本: implementation 'cn.shengwang.rtm:android-java:x.y.z' // 对于 2.2.6 及之前版本: // implementation 'io.agora:agora-rtm:x.y.z' }
信息
如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。
-
添加网络权限。打开
app/src/main/AndroidManifest.xml文件,在<manifest>的下一行添加如下代码:XML <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -
在
/Gradle Scripts/proguard-rules.pro文件中添加如下代码,以防止声网 SDK 的代码被混淆:Java -keep class io.agora.**{*;}
2. 初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先初始化一个 RtmClient 对象实例。参考如下步骤创建用户界面、初始化一个 RtmClient 对象实例。
-
创建用户界面。实时消息 App 一般会有如下输入框和按钮:
- 用户名、频道名、消息输入框
- 登录、登出按钮
- 订阅、取消订阅频道按钮
- 发送消息按钮
本节提供简易的 UI 界面设计以便于验证功能特性,你可以根据实际项目需求进行修改。
-
打开
/app/res/layout/activity_main.xml文件,将文件内容替换为如下代码:XML <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" tools:context=".MainActivity"> <EditText android:id="@+id/uid" android:layout_width="150dp" android:layout_height="40dp" android:layout_marginStart="36dp" android:layout_marginTop="48dp" android:autofillHints="" android:hint="@string/uid" android:inputType="text" android:lines="1" android:padding="5dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/logout_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="11dp" android:layout_marginTop="48dp" android:layout_marginEnd="28dp" android:onClick="onClickLogout" android:text="@string/logout_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/login_button" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/login_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:layout_marginTop="48dp" android:layout_marginEnd="132dp" android:onClick="onClickLogin" android:text="@string/login_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/uid" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/channel_name" android:layout_width="155dp" android:layout_height="41dp" android:layout_marginStart="36dp" android:layout_marginTop="124dp" android:layout_marginBottom="41dp" android:autofillHints="" android:ems="10" android:hint="@string/channel_name" android:inputType="text" app:layout_constraintTop_toBottomOf="@+id/uid" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/subscribe_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:layout_marginTop="28dp" android:layout_marginEnd="132dp" android:onClick="onClickSubscribe" android:text="@string/subscribe_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/channel_name" app:layout_constraintTop_toBottomOf="@+id/login_button" /> <Button android:id="@+id/unsubscribe_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="11dp" android:layout_marginTop="27dp" android:layout_marginEnd="28dp" android:onClick="onClickUnsubscribe" android:text="@string/unsubscribe_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/subscribe_button" app:layout_constraintTop_toBottomOf="@+id/logout_button" /> <EditText android:id="@+id/msg_box" android:layout_width="190dp" android:layout_height="57dp" android:layout_marginStart="37dp" android:layout_marginTop="195dp" android:layout_marginBottom="26dp" android:autofillHints="" android:ems="10" android:hint="@string/msg" android:inputType="textPersonName" android:singleLine="false" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/channel_name" /> <Button android:id="@+id/send_channel_msg_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="40dp" android:layout_marginEnd="40dp" android:onClick="onClickSendChannelMsg" android:text="@string/send_channel_msg_button" app:layout_constraintStart_toEndOf="@+id/msg_box" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/unsubscribe_button" /> <TextView android:id="@+id/message_history" android:layout_width="412dp" android:layout_height="339dp" android:layout_marginTop="392dp" android:background="#AEA8A8" android:freezesText="false" android:isScrollContainer="false" android:scrollbars="vertical" android:textColor="#2196F3" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.491" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.352" /> </androidx.constraintlayout.widget.ConstraintLayout> -
打开
app/res/values/strings.xml文件,将文件内容替换为如下代码:XML <resources> <string name="app_name">RtmQuickstart</string> <string name="login_button">Login</string> <string name="logout_button">Logout</string> <string name="subscribe_button">Sub</string> <string name="unsubscribe_button">Unsub</string> <string name="send_channel_msg_button">Publish MSG</string> <string name="uid">User ID</string> <string name="msg">Message content</string> <string name="channel_name">Channel name</string> <string name="app_id">your_appid</string> <string name="token">your_token</string> </resources>信息
你需要将示例中的
your_appid和your_token字段替换成你项目的 App ID 和 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后将示例中的your_appid和your_token字段都替换成你项目的 App ID。
-
打开
app/src/main/java/com/example/rtm/MainActivity.java文件,将文件内容替换为如下代码。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。信息
com.example.rtm为示例项目的路径,你需要根据实际项目路径查找文件并填写代码。Java // Replace the package path with the path of your real project. package com.example.rtm; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import io.agora.rtm.ErrorInfo; import io.agora.rtm.LinkStateEvent; import io.agora.rtm.LockEvent; import io.agora.rtm.MessageEvent; import io.agora.rtm.PresenceEvent; import io.agora.rtm.PublishOptions; import io.agora.rtm.ResultCallback; import io.agora.rtm.RtmClient; import io.agora.rtm.RtmConfig; import io.agora.rtm.RtmConstants; import io.agora.rtm.RtmEventListener; import io.agora.rtm.RtmMessage; import io.agora.rtm.StorageEvent; import io.agora.rtm.SubscribeOptions; import io.agora.rtm.TopicEvent; public class MainActivity extends AppCompatActivity { private EditText etUserId; private EditText etChannelName; private EditText etMessageContent; // The user ID of the publisher private String mUserId; // The channel name of the message channel private String mChannelName; // The RTM client instance private RtmClient mRtmClient; // The TextView to show the messages that you send private TextView mMessageHistory; // Add the event listener @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } @Override protected void onResume() { super.onResume(); } @Override protected void onDestroy() { super.onDestroy(); } public void onClickLogin(View v) { try { etUserId = (EditText) findViewById(R.id.uid); mUserId = etUserId.getText().toString(); if (mUserId == null || mUserId.isEmpty()) { showToast("invalid userId"); return; } RtmConfig config = new RtmConfig.Builder(getBaseContext().getString(R.string.app_id), mUserId) .eventListener(eventListener) .build(); mRtmClient = RtmClient.create(config); } catch (Exception e) { showToast("create rtm client is null"); } if (mRtmClient == null) { showToast("rtm client is null"); return; } // Log in the RTM server } public void onClickSubscribe(View v) { if (mRtmClient == null) { showToast("rtm client is null"); return; } // Subscribe to a channel } public void onClickLogout(View v) { if (mRtmClient == null) { showToast("rtm client is null"); } // Log out from the RTM server } public void onClickUnsubscribe(View v) { if (mRtmClient == null) { showToast("rtm client is null"); return; } // Unsubscribe from a channel } public void onClickSendChannelMsg(View v) { // Publish a message } // Write message history to TextView public void writeToMessageHistory(String record) { mMessageHistory = findViewById(R.id.message_history); mMessageHistory.append(record); } private void showToast(String text) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); } }
如需了解更多初始化的信息,查看初始配置。
3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Add the event listener" comment
private RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId() + " Message: " + event.getMessage().getData() + "\n";
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "receive presence event, user: " + event.getPublisherId() + " event: " + event.getEventType() + "\n";
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Link state event: " + event + "\n";
writeToMessageHistory(text);
}
};4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:
// Paste the following code snippet below "Log in the RTM server" comment
String token =getBaseContext().getString(R.string.token);
mRtmClient.login(token, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully log in the RTM server!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
CharSequence text = "User: " + mUserId + " Fail to log in to the RTM system!" + errorInfo.toString();
int duration = Toast.LENGTH_SHORT;
runOnUiThread(new Runnable() {
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), text, duration);
toast.show();
}
});
}
});5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
etMessageContent = findViewById(R.id.msg_box);
String message = etMessageContent.getText().toString();
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(mChannelName, message, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
String text = "Message sent to channel " + mChannelName + " : " + message + "\n";
writeToMessageHistory(text);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
String text = "Message fails to send to channel " + mChannelName + " Error: " + errorInfo + "\n";
writeToMessageHistory(text);
}
});调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a channel" comment
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(mChannelName, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribe to the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to subscribe to the channel!\n");
}
});如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道。将以下代码添加到程序中:
// Paste the following code snippet below "Unsubscribe from a channel" comment
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
mRtmClient.unsubscribe(mChannelName, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribe from the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to unsubscribe from the channel!\n");
}
});如需了解更多收发消息的信息,查看消息。
6. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:
// Paste the following code snippet below "Log out from the RTM server" comment
mRtmClient.logout(new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully log out from the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to log out from the channel!\n");
}
});信息
本操作会影响你账单中的 PCU 计费项。
7. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
// Replace the package path with the path of your real project.
package com.example.rtm;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.agora.rtm.ErrorInfo;
import io.agora.rtm.LinkStateEvent;
import io.agora.rtm.LockEvent;
import io.agora.rtm.MessageEvent;
import io.agora.rtm.PresenceEvent;
import io.agora.rtm.PublishOptions;
import io.agora.rtm.ResultCallback;
import io.agora.rtm.RtmClient;
import io.agora.rtm.RtmConfig;
import io.agora.rtm.RtmConstants;
import io.agora.rtm.RtmEventListener;
import io.agora.rtm.RtmMessage;
import io.agora.rtm.StorageEvent;
import io.agora.rtm.SubscribeOptions;
import io.agora.rtm.TopicEvent;
public class MainActivity extends AppCompatActivity {
private EditText etUserId;
private EditText etChannelName;
private EditText etMessageContent;
// The user ID of the publisher
private String mUserId;
// The channel name of the message channel
private String mChannelName;
// The RTM client instance
private RtmClient mRtmClient;
// The TextView to show the messages that you send
private TextView mMessageHistory;
// Add the event listener
private RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
String text = "Message received from " + event.getPublisherId() + " Message: " + event.getMessage().getData() + "\n";
writeToMessageHistory(text);
}
@Override
public void onPresenceEvent(PresenceEvent event) {
String text = "receive presence event, user: " + event.getPublisherId() + " event: " + event.getEventType() + "\n";
writeToMessageHistory(text);
}
@Override
public void onLinkStateEvent(LinkStateEvent event) {
String text = "Link state event: " + event + "\n";
writeToMessageHistory(text);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
public void onClickLogin(View v)
{
try {
etUserId = (EditText) findViewById(R.id.uid);
mUserId = etUserId.getText().toString();
if (mUserId == null || mUserId.isEmpty()) {
showToast("invalid userId");
return;
}
RtmConfig config = new RtmConfig.Builder(getBaseContext().getString(R.string.app_id), mUserId)
.eventListener(eventListener)
.build();
mRtmClient = RtmClient.create(config);
} catch (Exception e) {
showToast("create rtm client is null");
}
if (mRtmClient == null) {
showToast("rtm client is null");
return;
}
// Log in the RTM server
String token =getBaseContext().getString(R.string.token);
mRtmClient.login(token, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully log in the RTM server!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
CharSequence text = "User: " + mUserId + " Fail to log in to the RTM system!" + errorInfo.toString();
int duration = Toast.LENGTH_SHORT;
runOnUiThread(new Runnable() {
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), text, duration);
toast.show();
}
});
}
});
}
public void onClickSubscribe(View v)
{
if (mRtmClient == null) {
showToast("rtm client is null");
return;
}
// Subscribe to a channel
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
mRtmClient.subscribe(mChannelName, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully subscribe to the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to subscribe to the channel!\n");
}
});
}
public void onClickLogout(View v)
{
if (mRtmClient == null) {
showToast("rtm client is null");
}
// Log out from the RTM server
mRtmClient.logout(new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully log out from the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to log out from the channel!\n");
}
});
}
public void onClickUnsubscribe(View v)
{
if (mRtmClient == null) {
showToast("rtm client is null");
return;
}
// Unsubscribe from a channel
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
mRtmClient.unsubscribe(mChannelName, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
writeToMessageHistory("Successfully unsubscribe from the channel!\n");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
writeToMessageHistory("Fail to unsubscribe from the channel!\n");
}
});
}
public void onClickSendChannelMsg(View v)
{
etMessageContent = findViewById(R.id.msg_box);
String message = etMessageContent.getText().toString();
// Publish a message
etChannelName = (EditText) findViewById(R.id.channel_name);
mChannelName = etChannelName.getText().toString();
PublishOptions options = new PublishOptions();
options.setCustomType("");
mRtmClient.publish(mChannelName, message, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
String text = "Message sent to channel " + mChannelName + " : " + message + "\n";
writeToMessageHistory(text);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
String text = "Message fails to send to channel " + mChannelName + " Error: " + errorInfo + "\n";
writeToMessageHistory(text);
}
});
}
// Write message history to TextView
public void writeToMessageHistory(String record)
{
mMessageHistory = findViewById(R.id.message_history);
mMessageHistory.append(record);
}
private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}现在,你可以开始运行你的程序:
- 保存项目。
- 在菜单栏中依次点击 Run > Select Device > Select Multiple Devices 并添加两个设备。例如,选择一个虚拟机和一个真机。
- 运行项目。成功运行后,你可以在两个设备中看到 App 界面。
- 将一个设备作为接收端,进行如下操作:
- 输入用户名,点击 LOGIN。
- 输入频道名,点击 SUB。
- 将另一个设备作为发送端,进行如下操作:
- 输入不同的用户名,点击 LOGIN。
- 输入相同的频道名。
- 输入消息,点击 PUBLISH MSG。
- 将上面的发送端和设备端互换,重复步骤 4 和 5。
如果两个设备均可收发消息,那么你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 设置并初始化 RTM 对象。
- 添加
onMessageEvent、onPresenceEvent和onLinkStateEvent事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribe取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM Java SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM Objective-C SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境是否满足平台支持中的最低版本要求。
构建项目
1. 项目配置
根据以下步骤配置你的项目:
- 在 Xcode 中创建一个 iOS 平台下的 Single View App,项目设置如下:
- Product Name 设为
RtmQuickstart。 - Organization Identifier 设为
agora。 - User Interface 选择 Storyboard。
- Language 选择 Objective-C。
- Product Name 设为
- 通过以下任意一种方式获取最新的 RTM Objective-C SDK 并导入你的项目。
- 点击此处下载最新版本的 Objective-C SDK。
- 复制 SDK 包中
/libs路径下所有的xcframework文件至项目路径下。 - 打开 Xcode,进入 TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content 菜单。
- 点击 + > Add Other… > Add Files 添加所有的
xcframework文件,并确保添加的动态库 Embed 属性设置为 Embed & Sign。
添加完成后,项目会自动链接所需系统库。
-
开始前请确保你已安装 Cocoapods,如尚未安装 Cocoapods,参考 Getting Started with CocoaPods 安装说明。
-
在终端里进入项目根目录,并运行
pod init命令。项目文件夹下会生成一个Podfile文本文件。 -
打开
Podfile文件,修改文件为如下内容。注意将Your App替换为你的 Target 名称。Ruby platform :ios, '11.0' target 'Your App' do # 对于 2.2.8 及之后版本: pod 'ShengWang-Rtm', '2.3.0', :subspecs => ['RtmKit'] # 对于 2.2.7 版本: # pod 'ShengwangRtm', 'x.y.z' # 对于 2.2.6 及之前版本: # pod 'AgoraRtm', 'x.y.z' # 如需获取 2.2.0 之前的版本,请将包名修改为 AgoraRtm_iOS end -
在终端内运行
pod install命令安装声网 SDK。成功安装后,Terminal 中会显示Pod installation complete!。 -
成功安装后,项目文件夹下会生成一个后缀为
.xcworkspace的文件,通过 Xcode 打开该文件进行后续操作。
使用如下链接,通过 SPM (Swift Package Manager) 集成 SDK:
https://github.com/AgoraIO/AgoraRtm_Apple.git信息
如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。
2. 初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先初始化一个 AgoraRtmClientKit 对象实例。参考如下步骤创建用户界面、初始化一个 AgoraRtmClientKit 对象实例。
-
创建用户界面。实时消息 App 一般会有如下输入框和按钮:
- 用户名、频道名、消息输入框
- 登录、登出按钮
- 订阅、取消订阅频道按钮
- 发送消息按钮
本节提供简易的 UI 界面设计以便于验证功能特性,你可以根据实际项目需求进行修改。
-
使用 Code View 打开
Main.storyboard并将文件内容替换为以下代码:XML <?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> <device id="retina6_1" orientation="portrait" appearance="light"/> <dependencies> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--View Controller--> <scene sceneID="tne-QT-ifu"> <objects> <viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M2K-rz-dlO"> <rect key="frame" x="254" y="103" width="38" height="30"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <state key="normal" title="Login"/> <connections> <action selector="Login:" destination="BYZ-38-t0r" eventType="touchUpInside" id="UEU-up-ksL"/> </connections> </button> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CZ1-G5-AkG"> <rect key="frame" x="326" y="103" width="48" height="30"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <state key="normal" title="Logout"/> <connections> <action selector="Logout:" destination="BYZ-38-t0r" eventType="touchUpInside" id="a0d-8h-eyX"/> </connections> </button> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0px-1e-aMC"> <rect key="frame" x="242" y="172" width="62" height="30"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <state key="normal" title="Subscribe"/> <connections> <action selector="SubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="12c-US-VQk"/> </connections> </button> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SDQ-Zt-rBD"> <rect key="frame" x="307" y="172" width="87" height="30"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <state key="normal" title="Unsubscribe"/> <connections> <action selector="UnsubscribeChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="u4T-ze-wtj"/> </connections> </button> <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="User ID" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="69v-UU-ObH"> <rect key="frame" x="40" y="99" width="187" height="34"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Channel name" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="dGk-g2-qHK"> <rect key="frame" x="40" y="168" width="187" height="34"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qqk-6X-Tcb"> <rect key="frame" x="304" y="247" width="88" height="30"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <state key="normal" title="Publish MSG"/> <connections> <action selector="SendMessageToMessageChannel:" destination="BYZ-38-t0r" eventType="touchUpInside" id="XcX-nz-fPl"/> </connections> </button> <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Message content" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="lKg-ap-M6p"> <rect key="frame" x="40" y="245" width="252" height="34"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e1v-i8-spC"> <rect key="frame" x="40" y="416" width="330" height="428"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="textColor" systemColor="labelColor"/> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> </textView> </subviews> <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/> </view> <connections> <outlet property="ChannelIDTextField" destination="dGk-g2-qHK" id="Iuh-ow-1MH"/> <outlet property="GroupMsgButton" destination="Qqk-6X-Tcb" id="TV4-sg-bY7"/> <outlet property="GroupMsgTextField" destination="lKg-ap-M6p" id="2Me-Z0-QIH"/> <outlet property="LoginButton" destination="M2K-rz-dlO" id="ZxJ-oV-UKy"/> <outlet property="LogoutButton" destination="CZ1-G5-AkG" id="ZQE-B7-yya"/> <outlet property="MsgTextView" destination="e1v-i8-spC" id="76J-8Q-kKp"/> <outlet property="SubsctibeButton" destination="0px-1e-aMC" id="9TL-8M-l6w"/> <outlet property="UnSubscribeButton" destination="SDQ-Zt-rBD" id="tgd-fK-EfO"/> <outlet property="UserIDTextField" destination="69v-UU-ObH" id="3BH-ci-t7A"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> </objects> <point key="canvasLocation" x="131.8840579710145" y="97.767857142857139"/> </scene> </scenes> <resources> <systemColor name="labelColor"> <color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </systemColor> <systemColor name="systemBackgroundColor"> <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </systemColor> </resources> </document> -
打开
ViewController.h文件,将文件内容替换为如下代码:Objective-C #import <UIKit/UIKit.h> #import <AgoraRtmKit/AgoraRtmKit.h> @interface ViewController : UIViewController // Buttons @property (weak, nonatomic) IBOutlet UIButton *LoginButton; @property (weak, nonatomic) IBOutlet UIButton *LogoutButton; @property (weak, nonatomic) IBOutlet UIButton *SubsctibeButton; @property (weak, nonatomic) IBOutlet UIButton *UnSubscribeButton; @property (weak, nonatomic) IBOutlet UIButton *GroupMsgButton; // Textfields @property (weak, nonatomic) IBOutlet UITextField *UserIDTextField; @property (weak, nonatomic) IBOutlet UITextField *ChannelIDTextField; @property (weak, nonatomic) IBOutlet UITextField *GroupMsgTextField; @property (weak, nonatomic) IBOutlet UITextView *MsgTextView; @end
-
打开
ViewController.m文件,将文件内容替换为如下代码。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。信息
你需要将示例中的
your_appid和your_token字段替换成你项目的 App ID 和 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后将示例中的your_appid和your_token字段都替换成你项目的 App ID。Objective-C #import "ViewController.h" @interface ViewController ()<AgoraRtmClientDelegate> @property(nonatomic, strong)AgoraRtmClientKit* kit; @property NSString* appID; @property NSString* token; @property NSString* uid; @property NSString* peerID; @property NSString* channelID; @property NSString* peerMsg; @property NSString* channelMsg; @property NSString* text; @property NSMutableArray* textArray; - (void)AddMsgToRecord:(NSString*)text; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Enter your App ID self.appID = @"your_appid"; self.MsgTextView.textColor = UIColor.blueColor; self.textArray = [[NSMutableArray alloc]init]; } // Add the event listener // Add message to the UI TextView - (void)AddMsgToRecord:(NSString*)text { [self.textArray addObject:(self.text)]; self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"\n")]; } - (IBAction)Login:(id)sender { self.uid = self.UserIDTextField.text; // Enter your token self.token = @"your_token"; AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid]; NSError* initError = nil; _kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError]; if (initError != nil) { self.text = [NSString stringWithFormat:@"init error %@",initError]; NSLog(@"%@", self.text); [self AddMsgToRecord:(self.text)]; } // Log in the RTM server } - (IBAction)Logout:(id)sender { if (_kit != nil) { // Log out from the RTM server } } - (IBAction)SubscribeChannel:(id)sender { self.channelID = self.ChannelIDTextField.text; if (_kit != nil) { // Subscribe to a channel } } - (IBAction)UnsubscribeChannel:(id)sender { if (_kit == nil) return; // Unsubscribe from a channel } - (IBAction)SendMessageToMessageChannel:(id)sender { self.channelID = self.ChannelIDTextField.text; self.channelMsg = self.GroupMsgTextField.text; // Publish a message } @end
如需了解更多初始化的信息,查看初始配置。
3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Add the event listener" comment
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message\nFrom channel: %@\npublisher:%@\nmessage:%@\n",event.channelName,event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:
// Paste the following code snippet below "Log in the RTM server" comment
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk){
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg];
} else {
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];调用 subscribeWithChannel 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a channel" comment
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if(errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];如果你不再需要在此频道收发消息,你可以调用 unsubscribeWithChannel 方法取消订阅此频道。将以下代码添加到程序中:
// Paste the following code snippet below "Unsubscribe from a channel" comment
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Leave channel successful"];
} else {
self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];如需了解更多收发消息的信息,查看消息。
6. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:
// Paste the following code snippet below "Log out from the RTM server" comment
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];信息
本操作会影响你账单中的 PCU 计费项。
7. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
#import "ViewController.h"
@interface ViewController ()<AgoraRtmClientDelegate>
@property(nonatomic, strong)AgoraRtmClientKit* kit;
@property NSString* appID;
@property NSString* token;
@property NSString* uid;
@property NSString* peerID;
@property NSString* channelID;
@property NSString* peerMsg;
@property NSString* channelMsg;
@property NSString* text;
@property NSMutableArray* textArray;
- (void)AddMsgToRecord:(NSString*)text;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Enter your App ID
self.appID = @"your_appid";
self.MsgTextView.textColor = UIColor.blueColor;
self.textArray = [[NSMutableArray alloc]init];
}
- (void)rtmKit:(AgoraRtmClientKit *)rtmKit didReceiveMessageEvent:(AgoraRtmMessageEvent *)event {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"receive message\nFrom channel: %@\npublisher:%@\nmessage:%@\n",event.channelName,event.publisher, event.message.stringData];
[self AddMsgToRecord:(self.text)];
}
// Add message to the UI TextView
- (void)AddMsgToRecord:(NSString*)text {
[self.textArray addObject:(self.text)];
self.MsgTextView.text = [self.textArray componentsJoinedByString:(@"\n")];
}
- (IBAction)Login:(id)sender {
self.uid = self.UserIDTextField.text;
// Enter your token
self.token = @"your_token";
AgoraRtmClientConfig* rtm_config = [[AgoraRtmClientConfig alloc] initWithAppId:_appID userId:_uid];
NSError* initError = nil;
_kit = [[AgoraRtmClientKit alloc] initWithConfig:rtm_config delegate:self error:&initError];
if (initError != nil) {
self.text = [NSString stringWithFormat:@"init error %@",initError];
NSLog(@"%@", self.text);
[self AddMsgToRecord:(self.text)];
}
// Log in the RTM server
[_kit loginByToken:self.token completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo.errorCode != AgoraRtmErrorOk){
self.text = [NSString stringWithFormat:@"Login failed for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
} else {
NSLog(@"%@", self.text);
self.text = [NSString stringWithFormat:@"Login successful for user %@. Code: %ld",self.uid, (long)errorInfo.errorCode];
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)Logout:(id)sender {
if (_kit != nil) {
// Log out from the RTM server
[_kit logout:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Logout successful"];
NSLog(@"%@", self.text);
[_kit destroy];
_kit = nil;
} else {
self.text = [NSString stringWithFormat:@"Logout failed. Code: %ld",(long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)SubscribeChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
if (_kit != nil) {
// Subscribe to a channel
[_kit subscribeWithChannel:self.channelID option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if(errorInfo == nil) {
self.text = [NSString stringWithFormat:@"Successfully subscribe channel %@",self.channelID];
NSLog(@"%@", self.text);
} else {
self.text = [NSString stringWithFormat:@"Failed to subscribe channel %@ Code: %ld",self.channelID, (long)errorInfo.errorCode];
NSLog(@"%@", self.text);
}
[self AddMsgToRecord:(self.text)];
}];
}
}
- (IBAction)UnsubscribeChannel:(id)sender {
if (_kit == nil)
return;
// Unsubscribe from a channel
[_kit unsubscribeWithChannel:self.channelID completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil){
self.text = [NSString stringWithFormat:@"Leave channel successful"];
} else {
self.text = [NSString stringWithFormat:@"Failed to leave channel Code: %ld", (long)errorInfo.errorCode]; }
[self AddMsgToRecord:(self.text)];
}];
}
- (IBAction)SendMessageToMessageChannel:(id)sender {
self.channelID = self.ChannelIDTextField.text;
self.channelMsg = self.GroupMsgTextField.text;
// Publish a message
[_kit publish:self.channelID message:self.channelMsg option:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
if (errorInfo == nil)
{
self.text = [NSString stringWithFormat:@"Message sent to channel %@ : %@", self.channelID, self.channelMsg]; }
else
{
self.text = [NSString stringWithFormat:@"Message failed to send to channel %@ : %@ ErrorCode: %ld", self.channelID, self.channelMsg, (long)errorInfo.errorCode]; }
[self AddMsgToRecord:(self.text)];
}];
}
@end现在,你可以开始运行你的程序:
- 保存项目。
- 复制当前项目,使用相同的
appId和不同的userId。 - 运行上面的两个项目。成功运行后,你可以在两个设备中看到 App 界面。
- 将一个设备作为接收端,进行如下操作:
- 输入用户名,点击 Login。
- 输入频道名,点击 Subscribe。
- 将另一个设备作为发送端,进行如下操作:
- 输入不同的用户名,点击 Subscribe。
- 输入相同的频道名。
- 输入消息,点击 Publish MSG。
- 将上面的发送端和设备端互换,重复步骤 4 和 5。
如果两个设备均可收发消息,那么你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 设置并初始化 RTM 对象。
- 添加
didReceiveMessageEvent事件监听函数。 - 调用
loginByToken登录了 RTM 服务。 - 调用
subscribeWithChannel订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribeWithChannel取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM Objective-C SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM JavaScript SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的浏览器是否满足平台支持中的最低版本要求。
获取 SDK
你可以通过以下任意一种方式获取最新的 RTM JavaScript SDK。
方式一:使用 CDN 添加 SDK
点击此处下载最新版本的 JavaScript SDK,并在你的项目中添加以下代码引用它:
// 使用 CDN 直接将以下代码添加到你的 Web 应用中,或下载 SDK 文件后在本地引用
// 将 x.y.z 替换为具体的 SDK 版本号,如 2.3.0
// 可通过发版说明获取最新版本号
<script src="your_path_to_sdk/agora-rtm.x.y.z.min.js"></script>方式二:使用包管理器引入 SDK
-
根据你的应用场景和开发需要,选择一种方式和工具安装 SDK。
场景一:项目仅使用 RTM 产品,且没有集成声网任何版本的 RTC 产品。
Shell npm install agora-rtmShell pnpm add agora-rtmShell yarn add agora-rtm yarn add agora-rtc-sdk-ng@4.22.0 --save-exact场景二:项目中已经集成了 RTC 产品,需要新接入 RTM 产品。
Shell npm install agora-rtmShell pnpm add agora-rtm如果项目使用的 RTC 版本与 RTM 中依赖的 RTC 版本不一致,会收到提示:
unmet peer agora-rtc-sdk-ng@4.22.0: found 4.16.0。此时你需要升级 RTC 的版本:Shell pnpm update agora-rtc-sdk-ng@4.22.0或者通过以下命令同时保留两个版本的 RTC 库:
Shell pnpm add agora-rtc-sdk-ng-4.16@npm:agora-rtc-sdk-ng@4.16.0 --save-exact pnpm add agora-rtc-sdk-ng@4.22.0 --save-exact然后将代码中原来的
import AgoraRTC from "agora-rtc-sdk-ng"替换为如下行:JavaScript import AgoraRTC from "agora-rtc-sdk-ng-4.16"注意
保留两个版本的 RTC 库会增加项目的包体积。
Shell yarn add agora-rtm如果项目使用的 RTC 版本与 RTM 中依赖的 RTC 版本不一致,会收到提示:
unmet peer agora-rtc-sdk-ng@4.22.0: found 4.16.0。此时你需要升级 RTC 的版本:Shell yarn upgrade agora-rtc-sdk-ng@4.22.0或者通过以下命令同时保留两个版本的 RTC 库:
Shell yarn add agora-rtc-sdk-ng-4.16@npm:agora-rtc-sdk-ng@4.16.0 --save-exact yarn add agora-rtc-sdk-ng@4.22.0 --save-exact然后将代码中原来的
import AgoraRTC from "agora-rtc-sdk-ng"替换为如下行:JavaScript import AgoraRTC from "agora-rtc-sdk-ng-4.16"注意
保留两个版本的 RTC 库会增加项目的包体积。
场景三:你的项目是全新的,且需要同时集成 RTM 和 RTC,以利用其共频道、数据优先级控制等能力。这种场景下建议你集成融合版本的 SDK。
Shell npm install agora-rtxShell pnpm add agora-rtx --shamefully-hoistShell # 若版本为 2.2.0 # yarn add agora-rtx # yarn add agora-rtc-sdk-ng@4.22.0 --save-exact # 2.2.0 及之后版本 yarn add agora-rtx -
完成安装后,在你的
app.js中引入 RTM:JavaScript // 如果安装的是 agora-rtm import AgoraRTM from 'agora-rtm'; // 如果安装的是融合版 agora-rtx // import AgoraRTM from 'agora-rtx'; // import AgoraRTC from 'agora-rtc-sdk-ng';
构建项目
1. 项目配置
新建一个文件夹存放项目文件,进入文件夹中为你的 App 创建文件并安装其他依赖项。以下为在终端操作的命令行示例:
mkdir HelloWorld
cd HelloWorld
# Your app is in index.html.
touch index.html2. 初始化 RTM
初始化 rtm 时,你需要提供如下信息:
- 从控制台获取的 App ID(
appId)。 - 你自行定义的用户 ID(
userId),你需要保证其全局唯一性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Display App</title>
<style>
#container {
width: 800px;
margin: 0 auto;
padding: 20px;
}
#textDisplay {
width: 100%;
height: 800px;
border: 1px solid #b0b0b0;
margin-bottom: 20px;
overflow: auto;
text-align: left;
padding: 10px;
box-sizing: border-box;
}
#inputContainer {
display: flex;
align-items: center;
}
#textInput {
width: calc(100% - 100px);
padding: 5px;
margin-right: 10px;
}
#submitButton {
width: 90px;
padding: 5px;
}
</style>
<script src="./AgoraRTM-production.js"></script>
</head>
<body>
<script>
const { RTM } = AgoraRTM;
// Fill in the App ID of your project.
const appId = "your_appId";
// Fill in your user ID.
const userId = "your_userId";
// Fill in your channel name.
let rtm;
const msChannelName = "Chat_room";
const buttonClick = () => {
var input = document.getElementById("textInput");
publishMessage(input.value);
input.value = '';
}
const setupRTM = async () => {
// Initialize the RTM client.
try {
rtm = new RTM(appId, userId);
} catch (status) {
console.log("Error");
console.log(status);
}
// Add the event listener.
// Log in the RTM server.
// Subscribe to a channel.
}
// Publish a message.
const showMessage = (user, msg) => {
// Get text from the text box.
const inputText = textInput.value;
const newText = document.createTextNode(user + ": " + msg);
const newLine = document.createElement("br");
textDisplay.appendChild(newText);
textDisplay.appendChild(newLine);
}
window.onload = setupRTM;
</script>
<div id="container">
<h1>Hello RTM !</h1>
<div>
<div id="textDisplay"></div>
</div>
<div id="inputContainer">
<input type="text" id="textInput" placeholder="Enter text">
<button id="submitButton" onclick="buttonClick()"> Send </button>
</div>
</div>
</body>
</html>信息
你需要在初始化时填入项目的 App ID 和 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在初始化时只需填入项目的 App ID。
3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Add the event listener" comment
// Message event handler.
rtm.addEventListener("message", event => {
showMessage(event.publisher, event.message);
});
// Pressence event handler.
rtm.addEventListener("presence", event => {
if (event.eventType === "SNAPSHOT") {
showMessage("INFO", "I Join");
}
else {
showMessage("INFO", event.publisher + " is " + event.eventType);
}
});
// Link state changed event handler.
rtm.addEventListener("linkState", event => {
const currentState = event.currentState;
const previousState = event.previousState;
const serviceType = event.serviceType;
const operation = event.operation;
const reason = event.reason;
const affectedChannels = event.affectedChannels;
const timestamp = event.timestamp;
const isResumed = event.isResumed;
showMessage("INFO", JSON.stringify(event));
});4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中,并传入你在服务端自行生成的 Token:
// Paste the following code snippet below "Log in the RTM server." comment
try {
const result = await rtm.login({ token: 'your_token' });
console.log(result);
} catch (status) {
console.log(status);
}5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
const publishMessage = async (message) => {
const payload = { type: "text", message: message };
const publishMessage = JSON.stringify(payload);
const publishOptions = { channelType: 'MESSAGE'}
try {
const result = await rtm.publish(msChannelName, publishMessage, publishOptions);
showMessage(userId, publishMessage);
console.log(result);
} catch (status) {
console.log(status);
}
}调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a channel." comment
try {
const result = await rtm.subscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}如需了解更多收发消息的信息,查看消息。
6. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Display App</title>
<style>
#container {
width: 800px;
margin: 0 auto;
padding: 20px;
}
#textDisplay {
width: 100%;
height: 800px;
border: 1px solid #b0b0b0;
margin-bottom: 20px;
overflow: auto;
text-align: left;
padding: 10px;
box-sizing: border-box;
}
#inputContainer {
display: flex;
align-items: center;
}
#textInput {
width: calc(100% - 100px);
padding: 5px;
margin-right: 10px;
}
#submitButton {
width: 90px;
padding: 5px;
}
</style>
<script src="./AgoraRTM-production.js"></script>
</head>
<body>
<script>
const { RTM } = AgoraRTM;
// Fill in the App ID of your project.
const appId = "your_appId";
// Fill in your user ID.
const userId = "your_userId";
// Fill in your channel name.
let rtm;
const msChannelName = "Chat_room";
const buttonClick = () => {
var input = document.getElementById("textInput");
publishMessage(input.value);
input.value = '';
}
const setupRTM = async () => {
// Initialize the RTM client.
try {
rtm = new RTM(appId, userId);
} catch (status) {
console.log("Error");
console.log(status);
}
// Add the event listener.
// Message event handler.
rtm.addEventListener("message", event => {
showMessage(event.publisher, event.message);
});
// Pressence event handler.
rtm.addEventListener("presence", event => {
if (event.eventType === "SNAPSHOT") {
showMessage("INFO", "I Join");
}
else {
showMessage("INFO", event.publisher + " is " + event.eventType);
}
});
// Link state changed event handler.
rtm.addEventListener("linkState", event => {
const currentState = event.currentState;
const previousState = event.previousState;
const serviceType = event.serviceType;
const operation = event.operation;
const reason = event.reason;
const affectedChannels = event.affectedChannels;
const timestamp = event.timestamp;
const isResumed = event.isResumed;
showMessage("INFO", JSON.stringify(event));
});
// Log in the RTM server.
try {
const result = await rtm.login({ token: 'your_token' });
console.log(result);
} catch (status) {
console.log(status);
}
// Subscribe to a channel.
try {
const result = await rtm.subscribe(msChannelName);
console.log(result);
} catch (status) {
console.log(status);
}
}
const publishMessage = async (message) => {
const payload = { type: "text", message: message };
const publishMessage = JSON.stringify(payload);
const publishOptions = { channelType: 'MESSAGE'}
try {
const result = await rtm.publish(msChannelName, publishMessage, publishOptions);
showMessage(userId, publishMessage);
console.log(result);
} catch (status) {
console.log(status);
}
}
const showMessage = (user, msg) => {
// Get text from the text box.
const inputText = textInput.value;
const newText = document.createTextNode(user + ": " + msg);
const newLine = document.createElement("br");
textDisplay.appendChild(newText);
textDisplay.appendChild(newLine);
}
window.onload = setupRTM;
</script>
<div id="container">
<h1>Hello RTM !</h1>
<div>
<div id="textDisplay"></div>
</div>
<div id="inputContainer">
<input type="text" id="textInput" placeholder="Enter text">
<button id="submitButton" onclick="buttonClick()"> Send </button>
</div>
</div>
</body>
</html>现在,你可以开始运行你的程序:
- 保存 index.html,并在浏览器中运行。我们称此时看到的页面为 A 页面。
- 复制当前项目,在 index.html 的
userId变量中填入一个不同的用户 ID,保存后运行。我们称此时看到的页面为 B 页面。 - 在 A 页面文本输入框中输入要发送的信息,点击 Send 按钮发送,然后在 B 页面中观察是否收到信息。
- 在 B 页面文本输入框中输入要发送的信息,点击 Send 按钮发送,然后在 A 页面中观察是否收到信息。
如果步骤 3 和步骤 4 中都能看到正确的信息,那么你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 设置并初始化 RTM 对象。
- 添加
message、presence和linkState事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。
下一步
现在,你已经学会了如何使用 RTM JavaScript SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM HarmonyOS SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境是否满足平台支持中的最低版本要求。
构建项目
1. 获取 SDK
点击此处下载最新版本的 HarmonyOS SDK。
下载完成后,解压 SDK 包,你将获得一个 .har 文件,这是 HarmonyOS 的静态共享包格式。
信息
如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。
2. 项目配置
根据以下步骤配置你的项目:
-
在 DevEco Studio 中,创建一个新的 HarmonyOS 应用项目。
-
将下载的
.har文件导入到你的项目中:- 在项目根目录下创建
libs文件夹(如果不存在) - 将
.har文件复制到libs文件夹中 - 在模块的
oh-package.json5文件中添加依赖:
JSON { "dependencies": { "@shengwang/rtm-full": "file:./libs/AgoraRtmSDK.har" } } - 在项目根目录下创建
-
在模块的
module.json5文件中添加网络权限:JSON { "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.GET_NETWORK_INFO" } ] } } -
同步项目依赖,确保 SDK 正确导入。
3. 创建并初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先创建一个 RtmClient 对象实例。在你的项目中创建一个 ArkTS 文件(例如 RtmManager.ets),添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。
信息
你需要在初始化时填入已开通 RTM 服务的项目的 App ID 和 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在示例中填入项目的 App ID 即可。
import { RtmClient, RtmConfig, RtmChannelType, SubscribeOptions, PublishOptions } from '@shengwang/rtm-full';
import type { LinkStateEvent, MessageEvent, PresenceEvent } from '@shengwang/rtm-full';
import { RtmError } from '@shengwang/rtm-full';
// 填入你的 App ID
const APP_ID = '<Your App ID>';
// 填入你的 Token(测试阶段可以为空字符串)
const TOKEN = '';
export class RtmManager {
private rtmClient: RtmClient | null = null;
private userId: string = '';
/**
* 初始化 RTM 客户端
*/
async initialize(userId: string): Promise<void> {
try {
this.userId = userId;
// 创建 RTM 配置
const config: RtmConfig = {
appId: APP_ID,
userId: userId
};
// 创建 RTM 客户端实例
this.rtmClient = new RtmClient(config);
// 添加事件监听(后续步骤中补充)
console.log('RTM client initialized successfully');
} catch (error) {
console.error('Failed to initialize RTM client:', error);
if (error instanceof RtmError) {
throw new Error(`初始化失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('RTM client initialization failed');
}
}
}
/**
* 登录 RTM 服务
*/
async login(): Promise<void> {
// 后续步骤中补充
}
/**
* 订阅频道
*/
async subscribe(channelName: string): Promise<void> {
// 后续步骤中补充
}
/**
* 取消订阅频道
*/
async unsubscribe(channelName: string): Promise<void> {
// 后续步骤中补充
}
/**
* 发送消息
*/
async publish(channelName: string, message: string): Promise<void> {
// 后续步骤中补充
}
/**
* 登出 RTM 服务
*/
async logout(): Promise<void> {
// 后续步骤中补充
}
/**
* 释放资源
*/
release(): void {
if (this.rtmClient) {
this.rtmClient.release();
this.rtmClient = null;
}
}
}4. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑。在 initialize 方法中添加事件监听器,以显示收到的消息或事件通知:
// 在 initialize 方法中,创建 RTM 客户端后添加以下代码
// 添加事件监听(替换之前的注释)
// 监听连接状态变化
this.rtmClient.addEventListener('linkState', (event) => {
console.log('Link state changed:', event);
console.log(`State: ${event.previousState} -> ${event.currentState}`);
console.log(`Reason: ${event.reason}`);
});
// 监听接收到的消息
this.rtmClient.addEventListener('message', (event) => {
console.log('Received message:', event);
console.log(`From: ${event.publisher}`);
console.log(`Channel: ${event.channelName}`);
console.log(`Message: ${event.message}`);
});
// 监听 Presence 事件(用户加入/离开)
this.rtmClient.addEventListener('presence', (event) => {
console.log('Presence event:', event);
console.log(`Event type: ${event.eventType}`);
console.log(`User: ${event.publisher}`);
});5. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。在 login 方法中添加以下代码:
// 在 login 方法中添加以下代码
async login(): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.login(TOKEN);
console.log('Login successful:', response);
} catch (error) {
console.error('Login failed:', error);
if (error instanceof RtmError) {
throw new Error(`登录失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Login failed');
}
}
}信息
你需要在登录时传入 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在登录时传入空字符串即可。
6. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者。以下代码演示如何发送字符串类型的消息:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// 在 publish 方法中添加以下代码
async publish(channelName: string, message: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const options: PublishOptions = {
channelType: RtmChannelType.MESSAGE,
customType: ''
};
const response = await this.rtmClient.publish(channelName, message, options);
console.log('Message published successfully:', response);
} catch (error) {
console.error('Failed to publish message:', error);
if (error instanceof RtmError) {
throw new Error(`发送消息失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Publish failed');
}
}
}调用 subscribe 方法订阅此频道以接收此频道中的消息:
// 在 subscribe 方法中添加以下代码
async subscribe(channelName: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const options: SubscribeOptions = {
withMessage: true,
withMetadata: false,
withPresence: true,
withLock: false
};
const response = await this.rtmClient.subscribe(channelName, options);
console.log('Subscribed to channel successfully:', response);
} catch (error) {
console.error('Failed to subscribe to channel:', error);
if (error instanceof RtmError) {
throw new Error(`订阅失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Subscribe failed');
}
}
}如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道:
// 在 unsubscribe 方法中添加以下代码
async unsubscribe(channelName: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.unsubscribe(channelName);
console.log('Unsubscribed from channel successfully:', response);
} catch (error) {
console.error('Failed to unsubscribe from channel:', error);
if (error instanceof RtmError) {
throw new Error(`取消订阅失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Unsubscribe failed');
}
}
}如需了解更多收发消息的信息,查看消息。
7. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统:
// 在 logout 方法中添加以下代码
async logout(): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.logout();
console.log('Logout successful:', response);
} catch (error) {
console.error('Logout failed:', error);
if (error instanceof RtmError) {
throw new Error(`登出失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Logout failed');
}
}
}信息
本操作会影响你账单中的 PCU 计费项。
8. 组合到一起
经过上述步骤,你的完整代码应该如下所示:
import { RtmClient, RtmConfig, RtmChannelType, SubscribeOptions, PublishOptions } from '@shengwang/rtm-full';
import type { LinkStateEvent, MessageEvent, PresenceEvent } from '@shengwang/rtm-full';
import { RtmError } from '@shengwang/rtm-full';
// 填入你的 App ID
const APP_ID = '<Your App ID>';
// 填入你的 Token(测试阶段可以为空字符串)
const TOKEN = '';
export class RtmManager {
private rtmClient: RtmClient | null = null;
private userId: string = '';
/**
* 初始化 RTM 客户端
*/
async initialize(userId: string): Promise<void> {
try {
this.userId = userId;
// 创建 RTM 配置
const config: RtmConfig = {
appId: APP_ID,
userId: userId
};
// 创建 RTM 客户端实例
this.rtmClient = new RtmClient(config);
// 监听连接状态变化
this.rtmClient.addEventListener('linkState', (event: object) => {
const linkEvent = event as LinkStateEvent;
console.log('Link state changed:', linkEvent);
console.log(`State: ${linkEvent.previousState} -> ${linkEvent.currentState}`);
console.log(`Reason: ${linkEvent.reason}`);
});
// 监听接收到的消息
this.rtmClient.addEventListener('message', (event: object) => {
const msgEvent = event as MessageEvent;
console.log('Received message:', msgEvent);
console.log(`From: ${msgEvent.publisher}`);
console.log(`Channel: ${msgEvent.channelName}`);
console.log(`Message: ${msgEvent.message}`);
});
// 监听 Presence 事件(用户加入/离开)
this.rtmClient.addEventListener('presence', (event: object) => {
const presenceEvent = event as PresenceEvent;
console.log('Presence event:', presenceEvent);
console.log(`Event type: ${presenceEvent.eventType}`);
console.log(`User: ${presenceEvent.publisher}`);
});
console.log('RTM client initialized successfully');
} catch (error) {
console.error('Failed to initialize RTM client:', error);
if (error instanceof RtmError) {
throw new Error(`初始化失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('RTM client initialization failed');
}
}
}
/**
* 登录 RTM 服务
*/
async login(): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.login(TOKEN);
console.log('Login successful:', response);
} catch (error) {
console.error('Login failed:', error);
if (error instanceof RtmError) {
throw new Error(`登录失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Login failed');
}
}
}
/**
* 订阅频道
*/
async subscribe(channelName: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const options: SubscribeOptions = {
withMessage: true,
withMetadata: false,
withPresence: true,
withLock: false
};
const response = await this.rtmClient.subscribe(channelName, options);
console.log('Subscribed to channel successfully:', response);
} catch (error) {
console.error('Failed to subscribe to channel:', error);
if (error instanceof RtmError) {
throw new Error(`订阅失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Subscribe failed');
}
}
}
/**
* 取消订阅频道
*/
async unsubscribe(channelName: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.unsubscribe(channelName);
console.log('Unsubscribed from channel successfully:', response);
} catch (error) {
console.error('Failed to unsubscribe from channel:', error);
if (error instanceof RtmError) {
throw new Error(`取消订阅失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Unsubscribe failed');
}
}
}
/**
* 发送消息
*/
async publish(channelName: string, message: string): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const options: PublishOptions = {
channelType: RtmChannelType.MESSAGE,
customType: ''
};
const response = await this.rtmClient.publish(channelName, message, options);
console.log('Message published successfully:', response);
} catch (error) {
console.error('Failed to publish message:', error);
if (error instanceof RtmError) {
throw new Error(`发送消息失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Publish failed');
}
}
}
/**
* 登出 RTM 服务
*/
async logout(): Promise<void> {
if (!this.rtmClient) {
throw new Error('RTM client not initialized');
}
try {
const response = await this.rtmClient.logout();
console.log('Logout successful:', response);
} catch (error) {
console.error('Logout failed:', error);
if (error instanceof RtmError) {
throw new Error(`登出失败: ${error.reason} (code: ${error.errorCode})`);
} else {
throw new Error('Logout failed');
}
}
}
/**
* 释放资源
*/
release(): void {
if (this.rtmClient) {
this.rtmClient.release();
this.rtmClient = null;
}
}
}现在,你可以在你的 HarmonyOS 应用中使用这个 RtmManager 类。以下是一个简单的页面示例,展示如何在 Index 页面中使用 RTM 功能:
// Index.ets
import { RtmManager } from './RtmManager';
import { RtmChannelType } from '@shengwang/rtm-full';
@Entry
@Component
struct Index {
@State userId: string = '';
@State channelName: string = '';
@State message: string = '';
@State messageHistory: string = '';
@State isLoggedIn: boolean = false;
private rtmManager: RtmManager = new RtmManager();
build() {
Column() {
// 标题
Text('RTM 快速开始')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
// 用户 ID 输入
Row() {
Text('用户 ID:')
.width(80)
TextInput({ placeholder: '请输入用户 ID' })
.layoutWeight(1)
.onChange((value: string) => {
this.userId = value;
})
}
.width('90%')
.margin({ bottom: 10 })
// 登录/登出按钮
Row() {
Button(this.isLoggedIn ? '登出' : '登录')
.width('45%')
.onClick(() => {
if (this.isLoggedIn) {
this.handleLogout();
} else {
this.handleLogin();
}
})
}
.width('90%')
.margin({ bottom: 20 })
Divider()
.margin({ bottom: 20 })
// 频道名输入
Row() {
Text('频道名:')
.width(80)
TextInput({ placeholder: '请输入频道名' })
.layoutWeight(1)
.onChange((value: string) => {
this.channelName = value;
})
}
.width('90%')
.margin({ bottom: 10 })
// 订阅/取消订阅按钮
Row() {
Button('订阅')
.width('45%')
.onClick(() => {
this.handleSubscribe();
})
.enabled(this.isLoggedIn)
Button('取消订阅')
.width('45%')
.onClick(() => {
this.handleUnsubscribe();
})
.enabled(this.isLoggedIn)
}
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 20 })
// 消息输入
Row() {
Text('消息:')
.width(80)
TextInput({ placeholder: '请输入消息内容' })
.layoutWeight(1)
.onChange((value: string) => {
this.message = value;
})
}
.width('90%')
.margin({ bottom: 10 })
// 发送消息按钮
Button('发送消息')
.width('90%')
.onClick(() => {
this.handlePublish();
})
.enabled(this.isLoggedIn)
.margin({ bottom: 20 })
Divider()
.margin({ bottom: 10 })
// 消息历史
Text('消息历史:')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.alignSelf(ItemAlign.Start)
.margin({ left: '5%', bottom: 10 })
Scroll() {
Text(this.messageHistory)
.width('100%')
.padding(10)
}
.width('90%')
.height(200)
.backgroundColor('#F0F0F0')
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding({ top: 20 })
}
// 登录处理
async handleLogin() {
if (!this.userId) {
this.addMessage('请输入用户 ID');
return;
}
try {
await this.rtmManager.initialize(this.userId);
await this.rtmManager.login();
this.isLoggedIn = true;
this.addMessage(`用户 ${this.userId} 登录成功`);
} catch (error) {
this.addMessage(`登录失败: ${error}`);
}
}
// 登出处理
async handleLogout() {
try {
await this.rtmManager.logout();
this.rtmManager.release();
this.isLoggedIn = false;
this.addMessage('登出成功');
} catch (error) {
this.addMessage(`登出失败: ${error}`);
}
}
// 订阅频道
async handleSubscribe() {
if (!this.channelName) {
this.addMessage('请输入频道名');
return;
}
try {
await this.rtmManager.subscribe(this.channelName);
this.addMessage(`订阅频道 ${this.channelName} 成功`);
} catch (error) {
this.addMessage(`订阅失败: ${error}`);
}
}
// 取消订阅频道
async handleUnsubscribe() {
if (!this.channelName) {
this.addMessage('请输入频道名');
return;
}
try {
await this.rtmManager.unsubscribe(this.channelName);
this.addMessage(`取消订阅频道 ${this.channelName} 成功`);
} catch (error) {
this.addMessage(`取消订阅失败: ${error}`);
}
}
// 发送消息
async handlePublish() {
if (!this.channelName) {
this.addMessage('请输入频道名');
return;
}
if (!this.message) {
this.addMessage('请输入消息内容');
return;
}
try {
await this.rtmManager.publish(this.channelName, this.message);
this.addMessage(`发送消息到 ${this.channelName}: ${this.message}`);
} catch (error) {
this.addMessage(`发送消息失败: ${error}`);
}
}
// 添加消息到历史记录
addMessage(msg: string) {
const timestamp = new Date().toLocaleTimeString();
this.messageHistory += `[${timestamp}] ${msg}\n`;
}
}这个示例展示了如何:
- 创建 RTM 管理器实例:在组件中创建
RtmManager实例 - 用户登录:输入用户 ID,点击登录按钮初始化并登录 RTM
- 订阅频道:输入频道名,点击订阅按钮订阅消息频道
- 发送消息:输入消息内容,点击发送按钮向频道发送消息
- 取消订阅:点击取消订阅按钮退出频道
- 用户登出:点击登出按钮断开 RTM 连接
你也可以使用更简单的方式来测试 RTM 功能:
// 在你的页面或组件中使用
import { RtmManager } from './RtmManager';
// 创建 RTM 管理器实例
const rtmManager = new RtmManager();
// 初始化并登录
await rtmManager.initialize('user123');
await rtmManager.login();
// 订阅频道
await rtmManager.subscribe('test-channel');
// 发送消息
await rtmManager.publish('test-channel', 'Hello, RTM!');
// 取消订阅
await rtmManager.unsubscribe('test-channel');
// 登出
await rtmManager.logout();
// 释放资源
rtmManager.release();至此,你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 创建并初始化 RTM 对象。
- 添加
linkState、message和presence事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribe取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM HarmonyOS SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境和运行环境是否满足如下要求:
-
Unity Hub 任意版本
-
Unity 2018.4.0 或以上版本
-
操作系统与编译器要求:
表格内容超出当前宽度,可横向滚动查看更多列。开发平台 操作系统版本 编译器版本 Android Android 4.1 或以上 Android Studio 3.0 或以上 iOS iOS 11.0 或以上 Xcode 9.0 或以上 macOS macOS 10.10 或以上 Xcode 9.0 或以上 Windows Windows 7 或以上 Microsoft Visual Studio 2017 或以上
获取 SDK
点击此处下载最新版本的 Unity SDK,并在你的项目中导入它。
导入 SDK
-
打开 Unity Hub,点击 New Project 新建一个 Unity 3D 项目。
-
点击顶部菜单栏的 Assets 菜单,依次选择 Import Package、Custom Package 向项目中导入你刚刚获取的 RTM Unity Package。
初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先初始化一个 IRtmClient 对象实例。参考如下步骤创建用户界面、初始化一个 IRtmClient 对象实例并添加项目脚本:
初始化配置
初始化 RTM 的时候可以通过配置某些参数改变 IRtmClient 其后续行为,例如配置事件监听函数、端侧消息加密等。详见初始配置。
- 我们为你提供了一个简单的示例项目,其中进行了简易的 UI 界面设计以便于验证功能特性。UI 界面及组件架构如下图所示:
SampleScene
- Main Camera
- Canvas
- Image // 窗口背景
- Txt1 // 文本标签,文本内容为 Send string message to message channel
- InputField1 // 消息输入框
- Btn1 // 发送消息按钮
- Txt2 // 文本标签,文本内容为 Send binary message to stream channel
- Btn2 // 创建 Stream Channel 按钮
- Btn3 // 加入 Stream Channel 按钮
- Btn4 // 加入 Topic 按钮
- Btn5 // 订阅 Topic 中的消息发布者按钮
- InputField2 // 消息输入框
- Btn6 // 发送消息按钮
- Btn7 // 释放 Stream Channel 按钮
- Btn8 // 离开 Stream Channel 按钮
- Btn9 // 离开 Topic 按钮
- Btn10 // 取消订阅 Topic 中的消息发布者按钮
- Pannel // 信息输出面板
- Txt3 // 信息输出文字
- EventSystem-
在 Unity 编辑器中,选择顶部菜单栏的 Assets 菜单,依次点击 Create、C# Script 为项目新建脚本,名为 HelloWorld.cs。
-
在 HelloWorld.cs 脚本中添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。
Assets/Hello_world.cs using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Agora.Rtm; public class HelloWorld : MonoBehaviour { [SerializeField] private string appId = "your_appId"; // Get an App ID from console portal private string userId = "your_userId"; // Change the user ID by yourself private string msChannelName = "SeeYou"; private string stChannelName = "Greeting"; private string topicName = "Hello_world"; private IRtmClient rtmClient; private IStreamChannel streamChannel; public void Awake() { Initialize(); } // Initialize the RTM public async void Initialize() { // Initialize the RTM Client // Add listener if (rtmClient != null) { // Add message event listener rtmClient.OnMessageEvent += OnMessageEvent; // Add the presence event listener rtmClient.OnPresenceEvent += OnPresenceEvent; // Add the connection state change listener rtmClient.OnConnectionStateChanged += OnConnectionStateChanged; // Log in to the RTM server // Subscribe to a Message Channel } } private void ChangeComponentView(string[] componentsList, string viewType) { for (int i = 0; i < componentsList.Length; i++) { if (viewType == "ACTIVATE") GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = true; else if (viewType == "INACTIVATE") GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = false; } } private void ShowMessage(string msg) { GameObject.Find("Txt3").GetComponent<Text>().text += "\n" + msg; } // Implement the event listener handler // OnClick event handler for Btn1 public async void PublishStringMessage() { // Publish string messages to Message Channel } // OnClick event handler for Btn6 public async void PublishBinaryMessage() { // Publish binary messages to a Stream Channel } // Onclick event handler for Btn2 public void CreateStChannel() { string[] activeComponentsList = { "Btn3", "Btn7" }; string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" }; // Create a Stream Channel } // Onclick event handler for Btn3 public async void JoinStChannel() { string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" }; // Join a Stream Channel } // Onclick event handler for Btn4 public async void JoinTopic() { string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4", "Btn7" }; // Join a topic } // Onclick event handler for Btn5 public async void SubscribeTopic() { string[] activeComponentsList = { "Btn8", "Btn10" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5", "Btn7" }; // Subscribe a publisher from a topic } // Onclick event handler for Btn7 public async void ReleaseStChannel() { string[] activeComponentsList = { "Btn2" }; string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" }; // Release a Stream Channel } // Onclick event handler for Btn8 public async void LeaveStChannel() { string[] activeComponentsList = { "Btn3", "Btn7" }; string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" }; // Leave a Stream Channel } // Onclick event handler for Btn9 public async void LeaveTopic() { string[] activeComponentsList = { "Btn4", "Btn8" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" }; // Leave a topic } // Onclick event handler for Btn10 public async void UnsubscribeTopic() { string[] activeComponentsList = { "Btn5", "Btn8" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" }; // Unsubscribe a publisher from a topic } private async void OnDestroy() { string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" }; string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" }; // Dispose a Stream Channel if (rtmClient != null && streamChannel != null) { var result = await streamChannel.LeaveAsync(); var status = result.Status; var response = result.Response; if (status.Error) { ShowMessage(string.Format("{0} is failed", status.Operation)); ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason)); } else { ShowMessage("Dispose Channel Success!"); ChangeComponentView(activeComponentsList, "ACTIVATE"); ChangeComponentView(inactiveComponentList, "INACTIVATE"); } var status2 = streamChannel.Dispose(); } if (rtmClient != null) { var result = await rtmClient.UnsubscribeAsync(msChannelName); var status2 = result.Status; var response2 = result.Response; if (status3.Error) { Debug.Log(string.Format("{0} is failed", status3.Operation)); Debug.Log(string.Format("The error code is {0}, because of: {1}", status3.ErrorCode, status3.Reason)); } else { Debug.Log("Unsubscribe Channel Success!"); } var status4 = rtmClient.Dispose(); Debug.Log("Dispose rtmClient Success!"); rtmClient = null; } } }
消息格式
收发信息是 RTM 基础的能力之一,基于 RTM 实时网络,你在全球任何地方发送的信息都可以在 100 毫秒内安全可靠的送到到接收端。用户可以给一个人发送消息或一次性给成千上万人广播消息。
RTM 对在 Message 类型频道和 Stream 类型频道中发送的消息负载包体积有限制:Message 类型频道中的限制为 32 KB,Stream 类型频道中的限制为 1 KB。所有类型的频道中均支持字符串格式和二进制格式消息负载。
字符串类型消息格式:
string message = "This is a string message";二进制类型消息格式:
byte[] message = System.Text.Encoding.Default.GetBytes("This message will be converted to binary");根据你的应用场景需要,你可以选择字符串类型消息格式或者二进制类型消息格式,收到消息后通过 MessageEvent 中 messageType 字段加以区分。
你也可以使用 JSON 或 Protobuf 等方式来构建你的负载结构以便于简化业务逻辑,此部分内容可以参考消息负载序列化。但请记住在把消息负载转交给 RTM 发送前,需要对消息负载进行序列化。
初始化 RTM
在 HelloWorld.cs 中调用 CreateAgoraRtmClient 方法初始化 IRtmClient。你需要提供如下信息:
- 从控制台获取的 App ID(
appId)。 - 你需要定义用户 ID(
userId),并保证其全局唯一性。
将以下代码添加到程序中,并将程序模版中的 "your_appId"、"your_userId" 替换成你的 appId 和 userId。
// Paste the following code snippet below "Initialize RTM Client" comment
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set the log file path.
logConfig.filePath = "myFilePath";
// Set the file size of the agora.log file.
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Implement the event listener handler" comment
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}"), channelType);
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}登录服务
我们需要执行登录操作以建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。调用 LoginAsync 方法并传入 token 参数以验证身份。
将以下代码添加到 HelloWorld.cs 中:
// Paste the following code snippet below "Log in to the RTM server" comment
var result = await rtmClient.LoginAsync(appId);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Login Successfully");
}信息
你需要在登录时传入 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在登录时只需传入项目的 App ID。
在消息频道中收发消息
调用 PublishAsync 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送 String 类型的消息,将此代码片段添加到程序中:
// Paste the following code snippet below "Publish string messages to a message channel" comment
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}调用 SubscribeAsync 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a Message Channel" comment
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}成功订阅后,你可以通过之前监听的 OnMessageEvent 事件通知接收消息。
在流频道中收发消息
在 Stream Channel 中的消息需要依赖 Topic 功能,你需要依次完成以下步骤:
创建流频道
调用 Stream Channel 的所有方法前,你需要创建频道并获得 IStreamChannel 对象实例。将以下代码添加到程序中:
// Paste the following code snippet below "Create a Stream Channel" comment
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}加入流频道
加入 Stream Channel 才可以在后续进行发布和订阅消息的操作。调用 JoinAsync 方法并在 options 中传入 token 参数以验证身份。
信息
当前项目在创建的时候选择的是调试模式以便于快速验证功能,所以可以用 appId 代替 token 执行加入频道操作。
在 HelloWorld.cs 中添加如下代码:
// Paste the following code snippet below "Join a Stream Channel" comment
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = appId;
var result = await streamChannel.JoinAsync(options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}加入 Topic
调用 JoinTopicAsync 方法会将你注册成为 Topic 的消息发布者之一,以便于你后续向该 Topic 发送消息。将以下代码添加到频道中:
// Paste the following code snippet below "Join a topic" comment
if (streamChannel != null)
{
JoinTopicOptions options = new JoinTopicOptions();
options.qos = RTM_MESSAGE_QOS.ORDERED;
options.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}在 Topic 中发布消息
成功注册为 Topic 的发布者后,你就可以调用 PublishTopicMessageAsync 方法在 Topic 中发送消息了,RTM 会把此消息分发给该 Topic 的所有订阅者。以下示例向你展示如何发送 Binary 类型消息:
// Paste the following code snippet below "Publish binary messages to a Stream Channel" comment
var message = GameObject.Find("InputField2").GetComponent<InputField>().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
TopicMessageOptions options = new TopicMessageOptions();
options.customType = "PlainBinary";
options.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}订阅 Topic 中的发布者
你可以通过调用 SubscribeTopicAsync 方法订阅某个 Topic 中的一位或多位消息发布者以便于接收他们发送的消息。单个客户端在单个 Topic 中最多只能订阅 64 个消息发布者,详见 API 调用限制。
将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe a publisher from a topic" comment
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}成功订阅后,你可以通过之前监听的 OnMessageEvent 事件通知接收消息。
取消订阅 Topic 中发布者
如果你不再需要接收某 Topic 中某一位发布者的消息,调用 UnsubscribeTopicAsync 方法取消对他的订阅。如需取消订阅整个 Topic,在 topicOptions 中不填 users 参数即可。
将以下代码添加到程序中:
// Paste the following code snippet below "Unsubscribe a publisher from a topic" comment
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (rstatus.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}离开 Topic
如果你不再需要在某 Topic 中发布消息,调用 LeaveTopicAsync 方法取消成为该 Topic 的消息发布者。
信息
离开 Topic 不会影响你的订阅操作,你依旧可以从你订阅的 Topic 中接收消息。
将以下代码添加到程序中:
// Paste the following code snippet below "Leave a topic" comment
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}退出频道
当你不再关心此频道中的动态,即不再发送和接收消息,可以调用 LeaveTopicAsync 方法离开该频道。
将以下代码添加到程序中:
// Paste the following code snippet below "Release a Stream Channel" comment
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Leave Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}销毁频道
如果你不再需要某个 Stream Channel,你可以调用 Dispose 方法销毁该频道以释放资源。
在 HelloWorld.cs 中添加如下代码:
// Paste the following code snippet below "Dispose a Stream Channel" comment
var result1 = streamChannel.Dispose();组合到一起
经过上述步骤,你的 HelloWorld.cs 文件中的代码应该如下所示:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
public class HelloWorld : MonoBehaviour
{
[SerializeField]
private string appId = "your_appId";
// Get an App ID from console portal
private string userId = "your_userId";
// Change the user ID by yourself
private string msChannelName = "SeeYou";
private string stChannelName = "Greeting";
private string topicName = "Hello_world";
private IRtmClient rtmClient;
private IStreamChannel streamChannel;
public void Awake()
{
Initialize();
}
// Initialize RTM
public async void Initialize()
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
RtmLogConfig logConfig = new RtmLogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = RTM_LOG_LEVEL.INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
// Add listener
if (rtmClient != null)
{
// Add the message event listener
rtmClient.OnMessageEvent += OnMessageEvent;
// Add the presence event listener
rtmClient.OnPresenceEvent += OnPresenceEvent;
// Add the connection state change listener
rtmClient.OnConnectionStateChanged += OnConnectionStateChanged;
// Log in to the RTM server
var result = await rtmClient.LoginAsync(appId);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
Debug.Log(string.Format("Login failed"));
}
else
{
ShowMessage("Login Successfully");
}
//Subscribe to a Message Channel
SubscribeOptions options = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, options);
var status2 = result2.Status;
var response2 = result2.Response;
if (status2.Error)
{
ShowMessage(string.Format("{0} is failed", status2.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status2.ErrorCode, status2.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", response2.ChannelName));
}
}
}
private void ChangeComponentView(string[] componentsList, string viewType)
{
for (int i = 0; i < componentsList.Length; i++)
{
if (viewType == "ACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = true;
else if (viewType == "INACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = false;
}
}
private void ShowMessage(string msg)
{
GameObject.Find("Txt3").GetComponent<Text>().text += "\n" + msg;
}
// Implement the event listener handler
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
Debug.Log(string.Format("The Event : {0} Happend", eventType));
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.SNAPSHOT:
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.INTERVAL:
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_JOIN:
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_LEAVE:
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_TIMEOUT:
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.REMOTE_STATE_CHANGED:
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.ERROR_OUT_OF_SERVICE:
ShowMessage(string.Format("User:{0} joined channel without presence service:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var customType = eve.customType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stMessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: {0},from: {1} in channel:{2}", System.BitConverter.ToString(biMessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}", channelType));
}
}
// OnClick Event Handler for Btn1
public async void PublishStringMessage()
{
// Publish string messages to a message channel
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions options = new PublishOptions()
{
channelType = RTM_CHANNEL_TYPE.MESSAGE,
customType = "PlainText"
};
var result = await rtmClient.PublishAsync(msChannelName, message, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", status.Operation, status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
}
// OnClick Event Handler for Btn6
public async void PublishBinaryMessage()
{
// Publish Binary Messahe to Stream Channel
var message = GameObject.Find("InputField2").GetComponent<InputField>().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
TopicMessageOptions options = new TopicMessageOptions();
options.customType = "PlainBinary";
options.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}
}
// Onclick event handler for Btn2
public void CreateStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Create a Stream Channel
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn3
public async void JoinStChannel()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Join a Stream Channel
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = appId;
var result = await streamChannel.JoinAsync(options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn4
public async void JoinTopic()
{
string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4", "Btn7" };
// Join a topic
if (streamChannel != null)
{
JoinTopicOptions options = new JoinTopicOptions();
options.qos = RTM_MESSAGE_QOS.ORDERED;
options.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn5
public async void SubscribeTopic()
{
string[] activeComponentsList = { "Btn8", "Btn10" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5", "Btn7" };
// Subscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn7
public async void ReleaseStChannel()
{
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
// Release a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
}
// Onclick event handler for Btn8
public async void LeaveStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Leave a Stream Channel
if (streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave stream channel success! at Channel:{1}", response.UserId, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn9
public async void LeaveTopic()
{
string[] activeComponentsList = { "Btn4", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" };
// Leave a topic
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", response.UserId, response.Topic, response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn10
public async void UnsubscribeTopic()
{
string[] activeComponentsList = { "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" };
// Unsubscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions options = new TopicOptions();
options.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, options);
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
private async void OnDestroy()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10", "Btn7" };
// Dispose a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
var status = result.Status;
var response = result.Response;
if (status.Error)
{
ShowMessage(string.Format("{0} is failed", status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", status.ErrorCode, status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var status1 = streamChannel.Dispose();
}
if (rtmClient != null)
{
var result = await rtmClient.UnsubscribeAsync(msChannelName);
var status2 = result.Status;
var response2 = result.Response;
if (status2.Error)
{
Debug.Log(string.Format("{0} is failed", status2.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}", status2.ErrorCode, status2.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var status3 = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
}
}现在,你可以开始运行你的程序:
- 保存项目。
- 在 Unity Editor 中将代码中定义的事件处理函数分别挂载到不同的 Button 组件上。
- 复制项目,以相同的
appId、不同的userId再运行一个实例,验证功能。 - 同时运行上面的两个项目。
- 在其中的一个项目中的 Send string messasge to message channel 下方的输入框中输入消息,然后点击 Send Message,在另外一个项目中观察是否收到消息。
- 在其中的一个项目中的 Send binary messasge to stream channel 下方依次点击 Create Stream Channel、Join Stream Channel、Join Topic、Sub Topic Publisher 按钮,你将激活发送文本的输入框和按钮。在输入框中输入消息,并点击 Send Message 按钮,在另外一个项目中观察是否收到消息。
至此,你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。完成以上程序经历了以下几个过程:
- 设置并初始化 RTM 对象。
- 添加
OnMessageEvent、OnPresenceEvent和OnConnectionStateChanged事件监听函数。 - 对于 Message Channel:
- 订阅了一个频道。
- 往频道中发送了字符串消息。
- 对于 Stream Channel:
- 创建了一个名为
Greeting的频道。 - 加入了该频道。
- 加入了一个名叫
Hello_world的 Topic。 - 订阅了该 Topic 中名为
Tony的消息发布者(我自己)。 - 向该 Topic 发送了一条二进制消息。
- 取消订阅了该 Topic 中名为
Tony的消息发布者(我自己)。 - 离开了该 Topic。
- 退出了该频道。
- 销毁了该频道。
- 创建了一个名为
下一步
现在,你已经学会了如何使用 RTM SDK 来实现 Message Channel 和 Stream Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM Flutter SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。 同时,我们也提供了开源的实时消息示例项目供你参考,你可以前往下载或查看其中的源代码。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境是否满足平台支持中的最低版本要求。
构建项目
1. 创建项目
根据以下步骤创建并配置项目:
-
打开终端或命令行,执行以下命令来创建一个新的 Flutter 项目:
Shell # 创建 Flutter 项目 flutter create getting_started -
使用的你的 IDE 工具打开项目,编辑
pubspec.yaml文件,添加以下依赖:YAML dependencies: # 添加 RTM Flutter SDK 依赖项,请使用最新版本的 agora_rtm agora_rtm: ^2.2.6信息
如果你同时集成了 2.2.1 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。
-
添加依赖并保存文件后,打开终端,在项目路径下执行以下命令获取并安装依赖:
Shell flutter pub get
打开 main.dart 文件,删除其中所有代码,并将以下代码复制进去,作为项目的模板文件。
import 'package:agora_rtm/agora_rtm.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // 等待 Widgets 初始化完成。
// create rtm instance
// login rtm service
// subscribe to a channel
// publish messages
// unsubscribe frome a channel
// logout rtm service
}信息
(仅 iOS)在后续构建和发布 IPA 文件阶段,为避免因符号剥离而引发的问题,你还需要在 Xcode 中导航至 Target Runner > Build Settings > Strip Style,并将其设置为 Non-Global Symbols。
2. 创建并初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先创建一个 RtmClient 对象实例。添加以下代码到 main.dart 文件中,注意:请将 your_appId、your_userId 替换成你的 appId 和 userId.
import 'package:agora_rtm/agora_rtm.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // 等待 Widgets 初始化完成。
const userId = 'your_userId';
const appId = 'your_appId';
const channelName = 'getting-started';
late RtmClient rtmClient;
//create rtm instance
try {
// create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// add events listner
} catch (e) {
print('Initialize falid!:${e}');
}
}3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "add event listener" comment
rtmClient.addListener(
// add message event handler
message: (event) {
print('recieved a message from channel: ${event.channelName}, channel type : ${event.channelType}');
print('message content: ${utf8.decode(event.message!)}, custome type: ${event.customType}');
},
// add link state event handler
linkState: (event) {
print('link state changed from ${event.previousState} to ${event.currentState}');
print('reason: ${event.reason}, due to operation ${event.operation}');
});4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:
// Paste the following code snippet below "login rtm service" comment
try {
// login rtm service
var (status,response) = await rtmClient.login(appId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}
信息
你需要在登录时传入 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在登录时只需传入项目的 App ID。
5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('${status.operation} failed, errorCode: ${status.errorCode}, due to ${status.reason}');
} else {
print('${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "subscribe to a channel" comment
try {
// subscribe to 'getting-started' channel
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('subscribe channel: ${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}
如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道。
// Paste the following code snippet below "unsubscribe frome a channel" comment
try {
// unsubscribe channel
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}如需了解更多收发消息的信息,查看消息。
6. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:
// Paste the following code snippet below "logout rtm service" comment
try {
// logout rtm service
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}信息
本操作会影响你账单中的 PCU 计费项。
7. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
import 'package:flutter/material.dart';
import 'package:agora_rtm/agora_rtm.dart';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const userId = 'your_userId';
const appId = 'your_appId';
const channelName = 'getting-started';
late RtmClient rtmClient;
//create rtm instance
try {
// create rtm client
final (status, client) = await RTM( appId, userId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
rtmClient = client;
print('Initialize success!');
}
// add events listner
rtmClient.addListener(
// add message event handler
message: (event) {
print('recieved a message from channel: ${event.channelName}, channel type : ${event.channelType}');
print('message content: ${utf8.decode(event.message!)}, custome type: ${event.customType}');
},
// add link state event handler
linkState: (event) {
print('link state changed from ${event.previousState} to ${event.currentState}');
print('reason: ${event.reason}, due to operation ${event.operation}');
});
} catch (e) {
print('Initialize falid!:${e}');
}
// login rtm service
try {
// login rtm service
var (status,response) = await rtmClient.login(appId);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('login RTM success!');
}
} catch (e) {
print('Failed to login: $e');
}
// subscribe to a channel
try {
// subscribe to 'getting-started' channel
var (status,response) = await rtmClient.subscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('subscribe channel: ${channelName} success!');
}
} catch (e) {
print('Failed to subscribe channel: $e');
}
// publish messages
// Send a message every second for 100 seconds
for (var i = 0; i < 100; i++) {
try {
var (status, response) = await rtmClient.publish(
channelName,
'message number : $i',
channelType: RtmChannelType.message,
customType: 'PlainText'
);
if (status.error == true ){
print('${status.operation} failed, errorCode: ${status.errorCode}, due to ${status.reason}');
} else {
print('${status.operation} success! message number:$i');
}
} catch (e) {
print('Failed to publish message: $e');
}
await Future.delayed(Duration(seconds: 1));
}
// unsubscribe frome a channel
try {
// unsubscribe channel
var (status,response) = await rtmClient.unsubscribe(channelName);
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('unsubscribe success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
// logout rtm service
try {
// logout rtm service
var (status,response) = await rtmClient.logout();
if (status.error == true) {
print('${status.operation} failed due to ${status.reason}, error code: ${status.errorCode}');
} else {
print('logout RTM success!');
}
} catch (e) {
print('something went wrong with logout: $e');
}
}现在,你可以开始运行你的程序:
-
保存项目。
-
将目标设备连接到电脑,或者打开模拟器。
-
打开终端,在项目路径下执行以下命令在目标设备上运行示例项目:
Shell flutter run成功运行后,在终端上你将看到类似以下信息:
Shell flutter: publish success! message number:0 flutter: publish success! message number:1 flutter: publish success! message number:2 flutter: publish success! message number:3 -
复制项目,在 IDE 上打开,编辑
main.dart文件,修改userId保存后按照上述步骤运行第二个进程,观察终端上的输出,你将收到类似以下信息:Shell flutter: recieved a message from channel: getting-started, channel type : RtmChannelType.message flutter: message content: message number : 0, custome type: PlainText flutter: recieved a message from channel: getting-started, channel type : RtmChannelType.message flutter: message content: message number : 1, custome type: PlainText
至此,你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 创建并初始化 RTM 对象。
- 添加
linkState、message事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribe取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM Flutter SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将从 Linux 环境视角指导你如何利用 RTM C++ SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。对于 Windows 平台,我们提供了完整的 Visual Studio 工程,开发者可以直接参考工程配置,来运行你的第一个 RTM 程序。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境是否满足平台支持中的最低版本要求。
构建项目
1. 项目配置
根据以下步骤配置你的项目:
-
新建一个目录
RTMProject,目录中包含以下文件夹:Shell RTMProject/ ├── include └── lib -
点击此处下载最新版本的 C++ SDK 并解压。将 SDK 包中的如下文件复制到项目的对应目录下:
-
将
/rtm/sdk/libagora_rtm_sdk.so文件复制到项目的lib目录下。 -
将
/rtm/sdk/high_level_api/include路径下的所有*.h文件复制到项目的include目录下。注意
如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请手动删除更低版本的动态库文件。
-
在
RTMProject目录下新建CMakeLists.txt和build.sh文件,以便后续使用 CMake 编译项目。文件内容如下:Text # CMakeLists.txt cmake_minimum_required (VERSION 2.8) project(RTMQuickStart) set(TARGET_NAME rtm_demo) set(SOURCES rtm_quick_start.cpp) set(HEADERS) set(TARGET_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS "-fPIC -O2 -g -std=c++11 -msse2") include_directories(${CMAKE_SOURCE_DIR}/include) link_directories(${CMAKE_SOURCE_DIR}/lib) add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) target_link_libraries(${TARGET_NAME} agora_rtm_sdk pthread)Shell # build.sh rm -fr build mkdir -p build cd build cmake .. make cd ..
2. 创建并初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先创建一个 IRtmClient 对象实例,再初始化该实例。在 RTMProject 目录下新建 RTMQuickStart.cpp 文件,添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。
信息
你需要在初始化时填入已开通 RTM 服务的项目的 App ID 和 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在示例中填入项目的 App ID 即可。
#include <iostream>
#include <memory>
#include <string>
#include <exception>
#include "IAgoraRtmClient.h"
// Pass in your App ID here
const std::string APP_ID = "<Your App ID>";
// Terminal color codes for UBUNTU/LINUX
#define RESET "\033[0m"
#define BLACK "\033[30m" /* Black */
#define RED "\033[31m" /* Red */
#define GREEN "\033[32m" /* Green */
#define YELLOW "\033[33m" /* Yellow */
#define BLUE "\033[34m" /* Blue */
#define MAGENTA "\033[35m" /* Magenta */
#define CYAN "\033[36m" /* Cyan */
#define WHITE "\033[37m" /* White */
#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */
#define BOLDRED "\033[1m\033[31m" /* Bold Red */
#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */
#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */
#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */
#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */
#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */
#define BOLDWHITE "\033[1m\033[37m" /* Bold White */
using namespace agora::rtm;
class RtmEventHandler : public IRtmEventHandler {
public:
// Add the event listener
private:
void cbPrint(const char* fmt, ...) {
printf("\x1b[32m<< RTM async callback: ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf(" >>\x1b[0m\n");
}
};
class RtmDemo {
public:
RtmDemo()
: eventHandler_(new RtmEventHandler()),
rtmClient_(nullptr) { Init(); }
void Init() {
std::cout << YELLOW << "Please enter userID (literal \"null\" or starting"
<< "with space is not allowed, no more than 64 characters!):" << std::endl;
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}
}
void start() {
// Log in the RTM server
}
// Log out from the RTM server
// Subscribe to a channel
// Unsubscribe from a channel
// Publish a message
// Sample codes for the user interface
void mainMeun() {
bool quit = false;
while (!quit) {
std::cout << WHITE
<< "1: subscribe channel\n"
<< "2: unsubscribe channel\n"
<< "3: publish message\n"
<< "4: logout" << std::endl;
std::cout << YELLOW <<"please input your choice" << std::endl;
std::string input;
std::getline(std::cin, input);
int32_t choice = 0;
try {
choice = std::stoi(input);
} catch(...) {
std::cout <<RED << "invalid input" << std::endl;
continue;
}
switch (choice)
{
case 1: {
std::cout << YELLOW << "please input dst channel id" << std::endl;
std::string dst;
std::getline(std::cin, dst);
subscribeChannel(dst);
break;
}
case 2: {
std::cout << YELLOW << "please input dst channel id" << std::endl;
std::string dst;
std::getline(std::cin, dst);
unsubscribeChannel(dst);
break;
}
case 3: {
std::cout << YELLOW << "please input channel id" << std::endl;
std::string channel;
std::getline(std::cin, channel);
groupChat(channel);
break;
}
case 4: {
logout();
return;
}
default: {
std::cout <<RED << "invalid input" << std::endl;
break;
}
}
}
}
void groupChat(const std::string& channelName) {
std::string message;
while (true) {
std::cout << YELLOW << "please input message "
<< "or input \"quit\" to leave group chat"
<< std::endl;
std::getline(std::cin, message);
if (message.compare("quit") == 0) {
return;
} else {
publishMessage(channelName, message);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
private:
std::unique_ptr<IRtmEventHandler> eventHandler_;
IRtmClient* rtmClient_;
};
int main(int argc, const char * argv[]) {
RtmClient messaging;
messaging.login();
return 0;
}3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Add the event listener" comment
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason code: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reasonCode, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:
// Paste the following code snippet below "Log in the RTM server" comment
uint64_t requestId = 0;
rtmClient_->login(APP_ID.c_str(), requestId);
// Sample codes for the user interface
mainMeun();
std::cout << YELLOW << "quit ? yes/no" << std::endl;
std::string input;
std::getline(std::cin, input);
if (input.compare("yes") == 0) {
exit(0);
}信息
你需要在登录时传入 Token。在测试阶段,为快速验证功能,我们建议你在创建声网项目时将鉴权模式设置为调试模式,然后在登录时只需传入项目的 App ID。
5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a channel" comment
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道。
// Paste the following code snippet below "Unsubscribe from a channel" comment
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}如需了解更多收发消息的信息,查看消息。
6. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:
// Paste the following code snippet below "Log out from the RTM server" comment
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}信息
本操作会影响你账单中的 PCU 计费项。
7. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
#include <iostream>
#include <memory>
#include <string>
#include <exception>
#include "IAgoraRtmClient.h"
// Pass in your App ID here
const std::string APP_ID = "<Your App ID>";
// Terminal color codes for UBUNTU/LINUX
#define RESET "\033[0m"
#define BLACK "\033[30m" /* Black */
#define RED "\033[31m" /* Red */
#define GREEN "\033[32m" /* Green */
#define YELLOW "\033[33m" /* Yellow */
#define BLUE "\033[34m" /* Blue */
#define MAGENTA "\033[35m" /* Magenta */
#define CYAN "\033[36m" /* Cyan */
#define WHITE "\033[37m" /* White */
#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */
#define BOLDRED "\033[1m\033[31m" /* Bold Red */
#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */
#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */
#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */
#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */
#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */
#define BOLDWHITE "\033[1m\033[37m" /* Bold White */
using namespace agora::rtm;
class RtmEventHandler : public IRtmEventHandler {
public:
// Add the event listener
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onLoginResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLogoutResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
cbPrint("onLogoutResult, request id: %lld, errorCode: %d", requestId, errorCode);
}
void onLinkStateEvent(const LinkStateEvent& event) override {
cbPrint("onLinkStateEvent, state: %d -> %d, operation: %d, reason code: %d, reason: %s", event.previousState, event.currentState, event.operation, event.reasonCode, event.reason);
}
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
cbPrint("onPublishResult request id: %lld result: %d", requestId, errorCode);
}
void onMessageEvent(const MessageEvent &event) override {
cbPrint("receive message from: %s, message: %s", event.publisher, event.message);
}
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onSubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
void onUnsubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) override {
cbPrint("onUnsubscribeResult: channel:%s, request id: %lld result: %d", channelName, requestId, errorCode);
}
private:
void cbPrint(const char* fmt, ...) {
printf("\x1b[32m<< RTM async callback: ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf(" >>\x1b[0m\n");
}
};
class RtmDemo {
public:
RtmDemo()
: eventHandler_(new RtmEventHandler()),
rtmClient_(nullptr) { Init(); }
void Init() {
std::cout << YELLOW << "Please enter userID (literal \"null\" or starting"
<< "with space is not allowed, no more than 64 characters!):" << std::endl;
std::string userID;
std::getline(std::cin, userID);
RtmConfig config;
config.appId = APP_ID.c_str();
config.userId = userID.c_str();
config.eventHandler = eventHandler_.get();
// Create an IRtmClient instance
int errorCode = 0;
rtmClient_ = createAgoraRtmClient(config, errorCode);
if (!rtmClient_ || errorCode != 0) {
std::cout << RED <<"error creating rtm service!" << std::endl;
exit(0);
}
}
void start() {
// Log in the RTM server
uint64_t requestId = 0;
rtmClient_->login(APP_ID.c_str(), requestId);
// Sample codes for the user interface
mainMeun();
std::cout << YELLOW << "quit ? yes/no" << std::endl;
std::string input;
std::getline(std::cin, input);
if (input.compare("yes") == 0) {
exit(0);
}
}
// Log out from the RTM server
void logout() {
uint64_t requestId = 0;
rtmClient_->logout(requestId);
}
// Subscribe to a channel
void subscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->subscribe(channelName.c_str(), SubscribeOptions(), requestId);
}
// Unsubscribe from a channel
void unsubscribeChannel(const std::string& channelName) {
uint64_t requestId = 0;
rtmClient_->unsubscribe(channelName.c_str(), requestId);
}
// Publish a message
void publishMessage(const std::string& channelName, const std::string& message) {
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
uint64_t requestId;
rtmClient_->publish(channelName.c_str(), message.c_str(), message.size(), options, requestId);
}
// Sample codes for the user interface
void mainMeun() {
bool quit = false;
while (!quit) {
std::cout << WHITE
<< "1: subscribe channel\n"
<< "2: unsubscribe channel\n"
<< "3: publish message\n"
<< "4: logout" << std::endl;
std::cout << YELLOW <<"please input your choice: " << std::endl;
std::string input;
std::getline(std::cin, input);
int32_t choice = 0;
try {
choice = std::stoi(input);
} catch(...) {
std::cout <<RED << "invalid input" << std::endl;
continue;
}
switch (choice)
{
case 1: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
subscribeChannel(channelName);
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
case 2: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
unsubscribeChannel(channelName);
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
case 3: {
std::cout << YELLOW << "please input channel name:" << std::endl;
std::string channelName;
std::getline(std::cin, channelName);
groupChat(channelName);
break;
}
case 4: {
logout();
return;
}
default: {
std::cout <<RED << "invalid input" << std::endl;
break;
}
}
}
}
void groupChat(const std::string& channelName) {
std::string message;
while (true) {
std::cout << YELLOW << "please input message "
<< "or input \"quit\" to leave group chat"
<< std::endl;
std::getline(std::cin, message);
if (message.compare("quit") == 0) {
return;
} else {
publishMessage(channelName, message);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
private:
std::unique_ptr<IRtmEventHandler> eventHandler_;
IRtmClient* rtmClient_;
};
int main(int argc, const char * argv[]) {
RtmClient messaging;
messaging.login();
return 0;
}现在,你可以开始运行你的程序:
-
保存项目。
-
在命令行工具中,运行以下命令编译项目:
Shell $ sh build.sh -
编译完成后,运行以下命令进入
build目录并运行编译完成的可执行文件:Shell $ cd build $ ./rtm_demo -
运行项目。成功运行后,你可以在命令行工具中看到消息收发的提示信息。
信息
如果提示找不到 libaosl.so,需要添加一下动态库的加载路径,例如 export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH,其中,./lib 为存放RTM SDK so 库的路径。
至此,你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 创建并初始化 RTM 对象。
- 添加
onLoginResult、onLinkStateEvent、onPublishResult、onMessageEvent和onSubscribeResult事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribe取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM C++ SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文将指导你如何利用 RTM Swift SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
准备工作
开始构建项目前,你需要完成开通 RTM 服务,并检查你的开发环境和运行环境是否满足平台支持中的最低版本要求。
构建项目
1. 项目配置
根据以下步骤配置你的项目:
- 在 Xcode 中创建一个 iOS 平台下的 Single View App,项目设置如下:
- Product Name 设为
RtmQuickstart。 - Organization Identifier 设为
agora。 - User Interface 选择 Storyboard。
- Language 选择 Swift。
- Product Name 设为
- 通过以下任意一种方式获取最新的 RTM Swift SDK 并导入你的项目。
- 点击此处下载最新版本的 Objective-C SDK。
- 复制 SDK 包中
/libs路径下所有的xcframework文件至项目路径下。 - 打开 Xcode,进入 TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content 菜单。
- 点击 + > Add Other… > Add Files 添加所有的
xcframework文件,并确保添加的动态库 Embed 属性设置为 Embed & Sign。
添加完成后,项目会自动链接所需系统库。
-
开始前请确保你已安装 Cocoapods,如尚未安装 Cocoapods,参考 Getting Started with CocoaPods 安装说明。
-
在终端里进入项目根目录,并运行
pod init命令。项目文件夹下会生成一个Podfile文本文件。 -
打开
Podfile文件,修改文件为如下内容。注意将Your App替换为你的 Target 名称。Ruby platform :ios, '11.0' target 'Your App' do # 对于 2.2.8 及之后版本: pod 'ShengWang-Rtm', '2.3.0', :subspecs => ['RtmKit'] # 对于 2.2.7 版本: # pod 'ShengwangRtm', 'x.y.z' # 对于 2.2.6 及之前版本: # pod 'AgoraRtm', 'x.y.z' # 如需获取 2.2.0 之前的版本,请将包名修改为 AgoraRtm_iOS end -
在终端内运行
pod install命令安装声网 SDK。成功安装后,Terminal 中会显示Pod installation complete!。 -
成功安装后,项目文件夹下会生成一个后缀为
.xcworkspace的文件,通过 Xcode 打开该文件进行后续操作。
使用如下链接,通过 SPM (Swift Package Manager) 集成 SDK:
https://github.com/AgoraIO/AgoraRtm_Apple.git信息
如果你同时集成了 2.2.0 及以上版本的 RTM SDK 和 4.3.0 及以上版本的 RTC SDK,请参考 FAQ 处理集成问题。
2. 初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先创建一个 AgoraRtmClientKit 对象实例,再初始化该实例。在 ContentView.swift 文件中,添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。
信息
你需要将示例中的 <#YOUR APPID#> 字段替换成你项目的 App ID。
import SwiftUI
import Combine
import AgoraRtmKit
struct Message: Identifiable, Equatable {
let id = UUID()
let content: String
}
class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate{
// Initialize the AgoraRtmClientKit instance
var appid: String = <#YOUR APPID#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []
// Add the event listener
// Log in the RTM server
// Log out from the RTM server
// Subscribe to a channel
// Unsubscribe from a channel
// Publish a message
}
// Set user interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("input user name", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button(action: viewModel.login) {
Text("login")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Button(action: viewModel.logout) {
Text("logout")
.font(.title)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
TextField("channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button(action: viewModel.subscribe) {
Text("subscribe")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Button(action: viewModel.unsubscribe) {
Text("unsubscribe")
.font(.title)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
TextField("input message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button(action: viewModel.sendMessage) {
Text("send")
.font(.title)
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
// display log
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}如需了解更多初始化的信息,查看初始配置。
3. 添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Add the event listener" comment
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "RTM link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received.\n channel: \(event.channelName), publisher: \(event.publisher), message content: \(event.message.stringData!)")
}4. 登录服务
你需要执行登录操作才能建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。将以下代码添加到程序中:
// Paste the following code snippet below "Log in the RTM server" comment
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM alreay login! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username);
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(token, completion: { response, error in
if error != nil {
self.addToMessageList(str: "login failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "\(self.username) login succsss")
}
})
}5. 收发消息
调用 publish 方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送字符串类型的消息,将此代码片段添加到程序中:
信息
你需要先对消息负载进行字符串序列化,才能调用 publish 方法发送消息。
// Paste the following code snippet below "Publish a message" comment
func sendMessage() {
guard !message.isEmpty else { return }
if (rtmKit != nil) {
rtmKit?.publish(channelName: channel, message: message, option: nil, completion: { response, error in
if error != nil {
self.addToMessageList(str: "publish failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "publish message to channel: \(self.channel) succsss")
}
})
}
message = ""
}
func addToMessageList(str: String) {
messages.append(Message(content: str))
}调用 subscribe 方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a channel" comment
func subscribe() {
rtmKit?.subscribe(channelName: channel, option: nil, completion: { response, error in
if error != nil {
self.addToMessageList(str: "subscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "susbcribe channel: \(self.channel) succsss")
}
})
}如果你不再需要在此频道收发消息,你可以调用 unsubscribe 方法取消订阅此频道。将以下代码添加到程序中:
// Paste the following code snippet below "Unsubscribe from a channel" comment
func unsubscribe() {
rtmKit?.unsubscribe(channel, completion: { response, error in
if error != nil {
self.addToMessageList(str: "unsubscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "unsusbcribe channel: \(self.channel) succsss")
}
})
}如需了解更多收发消息的信息,查看消息。
6. 登出服务
当不再需要使用 RTM 服务时,你可以调用 logout 方法登出 RTM 系统。将以下代码添加到程序中:
// Paste the following code snippet below "Log out from the RTM server" comment
func logout() {
if rtmKit == nil {
addToMessageList(str: "RTM alreay logout!")
return
}
rtmKit?.logout()
rtmKit?.destroy()
rtmKit = nil
addToMessageList(str: "RTM logout!")
}信息
本操作会影响你账单中的 PCU 计费项。
7. 组合到一起
经过上述步骤,你程序中的代码应该如下所示:
import SwiftUI
import Combine
import AgoraRtmKit
struct Message: Identifiable, Equatable {
let id = UUID()
let content: String
}
class ChatViewModel: NSObject, ObservableObject, AgoraRtmClientDelegate{
// Initialize the AgoraRtmClientKit instance
var appid: String = <#YOUR APPID#>
var rtmKit: AgoraRtmClientKit? = nil
@Published var username: String = ""
@Published var message: String = ""
@Published var channel: String = ""
@Published var messages: [Message] = []
// Add the event listener
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveLinkStateEvent event: AgoraRtmLinkStateEvent) {
addToMessageList(str: "RTM link state change current state is: \(event.currentState.rawValue) previous state is :\(event.previousState.rawValue)")
}
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
addToMessageList(str: "Message received.\n channel: \(event.channelName), publisher: \(event.publisher), message content: \(event.message.stringData!)")
}
// Log in the RTM server
func login() {
if rtmKit != nil {
addToMessageList(str: "RTM alreay login! Logout first!")
return
}
let config = AgoraRtmClientConfig(appId: appid, userId: username);
rtmKit = try! AgoraRtmClientKit(config, delegate: self)
rtmKit?.login(appid, completion: { response, error in
if error != nil {
self.addToMessageList(str: "login failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "\(self.username) login succsss")
}
})
}
// Log out from the RTM server
func logout() {
if rtmKit == nil {
addToMessageList(str: "RTM alreay logout!")
return
}
rtmKit?.logout()
rtmKit?.destroy()
rtmKit = nil
addToMessageList(str: "RTM logout!")
}
// Subscribe to a channel
func subscribe() {
rtmKit?.subscribe(channelName: channel, option: nil, completion: { response, error in
if error != nil {
self.addToMessageList(str: "subscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "susbcribe channel: \(self.channel) succsss")
}
})
}
// Unsubscribe from a channel
func unsubscribe() {
rtmKit?.unsubscribe(channel, completion: { response, error in
if error != nil {
self.addToMessageList(str: "unsubscribe channel: \(self.channel) failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "unsusbcribe channel: \(self.channel) succsss")
}
})
}
// Publish a message
func sendMessage() {
guard !message.isEmpty else { return }
if (rtmKit != nil) {
rtmKit?.publish(channelName: channel, message: message, option: nil, completion: { response, error in
if error != nil {
self.addToMessageList(str: "publish failed error code is \(error?.errorCode.rawValue), reason is \(error?.reason)")
} else {
self.addToMessageList(str: "publish message to channel: \(self.channel) succsss")
}
})
}
message = ""
}
func addToMessageList(str: String) {
messages.append(Message(content: str))
}
}
// Set user interface
struct ContentView: View {
@StateObject private var viewModel = ChatViewModel()
var body: some View {
VStack {
TextField("input user name", text: $viewModel.username)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button(action: viewModel.login) {
Text("login")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Button(action: viewModel.logout) {
Text("logout")
.font(.title)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
TextField("channel name", text: $viewModel.channel)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
HStack {
Button(action: viewModel.subscribe) {
Text("subscribe")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Button(action: viewModel.unsubscribe) {
Text("unsubscribe")
.font(.title)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
TextField("input message", text: $viewModel.message)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
Button(action: viewModel.sendMessage) {
Text("send")
.font(.title)
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
// display log
ScrollViewReader { scrollProxy in
List(viewModel.messages) { message in
Text(message.content)
.id(message.id)
}
.onChange(of: viewModel.messages) { _ in
if let lastMessage = viewModel.messages.last {
withAnimation {
scrollProxy.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
}
.padding()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}现在,你可以开始运行你的程序:
- 保存项目。
- 复制当前项目,使用相同的
appId和不同的userId。 - 运行上面的两个项目。成功运行后,你可以在两个设备中看到 App 界面。
- 将一个设备作为接收端,进行如下操作:
- 输入用户名,点击 login。
- 输入频道名,点击 subscribe。
- 将另一个设备作为发送端,进行如下操作:
- 输入不同的用户名,点击 subscribe。
- 输入相同的频道名。
- 输入消息,点击 send。
- 将上面的发送端和设备端互换,重复步骤 4 和 5。
如果两个设备均可收发消息,那么你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。上述程序依次完成了以下操作,让你可以正确地收发消息:
- 设置并初始化 RTM 对象。
- 添加
didReceiveMessageEvent事件监听函数。 - 调用
login登录了 RTM 服务。 - 调用
subscribe订阅了一个 Message Channel。 - 调用
publish发送消息。 - 调用
unsubscribe取消订阅一个 Message Channel。 - 调用
logout登出 RTM 系统。
下一步
现在,你已经学会了如何使用 RTM Swift SDK 来实现 Message Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。
本文介绍如何在微信小程序项目中集成 RTM 微信小程序 SDK,并实现基础的 Message Channel 消息收发。
注意
本 SDK 专为微信小程序运行环境构建,不能作为 RTM Web SDK 的直接替代品使用。当前小程序 SDK 不支持 StreamChannel、Topic、cloudProxy,也不支持私有化部署和私有化配置。
准备工作
开始前,请确保你已完成以下准备:
- 已创建支持 npm 包的微信小程序项目。
- 已安装当前稳定版本的微信开发者工具。
- 开发环境中已安装 Node.js 和 npm。
- 如需在主小程序 appservice 中加载 RTM SDK,微信小程序基础库版本需为 2.13.0 或以上。
- 已完成开通服务,并获取项目的 App ID。
- 生产环境中已准备由业务服务器生成的 RTM Token。
- 已将 SDK 所需的 request 和 socket 域名添加到微信公众平台的服务器域名白名单。
1. 创建声网项目
安装 RTM SDK 前,请先在声网控制台创建项目。初始化 SDK 时需要使用项目的 App ID。在生产环境中,请使用由业务服务器生成的 RTM Token。
信息
使用不同 App ID 的客户端无法互通。需要互发消息的所有客户端必须使用相同的 App ID。
2. 安装 RTM SDK
中国大陆地区使用声网服务的项目,安装 shengwang-rtm-miniapp:
npm install shengwang-rtm-miniapp中国大陆以外地区使用 Agora 服务的项目,安装 agora-rtm-miniapp:
npm install agora-rtm-miniapp安装后,将已安装软件包中的 WebAssembly 资源复制到小程序项目根目录下的 wasm/ 目录。请根据安装的软件包运行对应命令:
node ./node_modules/shengwang-rtm-miniapp/scripts/load-wasm.jsnode ./node_modules/agora-rtm-miniapp/scripts/load-wasm.js安装软件包并复制资源后,在微信开发者工具中执行构建 npm。
3. 导入 RTM SDK
以下示例使用 shengwang-rtm-miniapp:
import AgoraRTM from 'shengwang-rtm-miniapp';4. 初始化 RTM 客户端实例
使用 App ID 和用户 ID 创建 RTM 实例:
import AgoraRTM from 'shengwang-rtm-miniapp';
const { RTM } = AgoraRTM;
const appId = 'your-app-id';
const userId = 'your-user-id';
const messageChannelName = 'chat-room';
let rtm;
try {
rtm = new RTM(appId, userId);
} catch (error) {
console.error('RTM 初始化失败:', error);
}请将 your-app-id 和 your-user-id 替换为项目的 App ID 和当前用户的用户 ID。
5. 添加事件监听
登录前注册事件监听,以便小程序及时处理收到的消息和连接状态变化。
rtm.addEventListener('message', event => {
console.log('收到消息:', event.publisher, event.message);
});
rtm.addEventListener('presence', event => {
console.log('Presence 事件:', event);
});
rtm.addEventListener('linkState', event => {
console.log('连接状态变化:', event.currentState, event.reasonCode);
});6. 登录 RTM 服务
SDK 初始化完成后,登录 RTM 服务。如果在登录前调用需要连接的 RTM 操作,SDK 会返回 RTM_ERROR_NOT_LOGIN (-10002)。
try {
await rtm.login({ token: 'your-token' });
console.log('RTM 登录成功');
} catch (error) {
console.error('RTM 登录失败:', error);
}7. 订阅频道
发送消息前先订阅 Message Channel,以便小程序接收其他用户发布的消息:
try {
await rtm.subscribe(messageChannelName);
console.log('已订阅频道:', messageChannelName);
} catch (error) {
console.error('订阅失败:', error);
}8. 发布消息
向频道发布字符串消息。对象类型的消息内容需要先序列化,再调用 publish。
const message = JSON.stringify({ text: '你好,微信小程序' });
try {
await rtm.publish(messageChannelName, message);
console.log('消息发送成功');
} catch (error) {
console.error('消息发送失败:', error);
}9. 取消订阅
小程序不再需要接收该频道的消息时,取消订阅:
try {
await rtm.unsubscribe(messageChannelName);
} catch (error) {
console.error('取消订阅失败:', error);
}更多信息
至此,你已经完成 RTM 小程序 SDK 的安装和 WebAssembly 资源准备,并实现了登录及 Message Channel 消息收发。
当前 RTM JavaScript API 参考 可用于理解 API 概念。微信小程序 SDK 与 Web SDK API 范围不同,使用具体 API 前,请以已安装的 shengwang-rtm-miniapp 包中的类型定义为准。
如有意见、问题或功能建议,请发送邮件至 rtm-support@agora.io。
附录:微信小程序域名白名单
微信小程序项目需要配置以下 13 个域名。其中,12 个 HTTPS 域名需要添加到微信公众平台的 request 合法域名,1 个 WSS 域名需要添加到 socket 合法域名。
request 合法域名
AP 域名用于登录、边缘节点分配和配置拉取,通过 wx.request 访问:
https://ap-web-1.agora.io;
https://ap-web-2.agora.io;
https://ap-web-3.agora.io;
https://ap-web-4.agora.io;
https://web-1.ap.sd-rtn.com;
https://web-2.ap.sd-rtn.com;
https://web-3.ap.sd-rtn.com;
https://web-4.ap.sd-rtn.com;事件上报域名用于 SDK 事件上报,通过 wx.request 访问:
https://webcollector-rtm.agora.io;
https://rtm.statscollector.sd-rtn.com;日志上报域名在开启 logUpload 后用于 SDK 日志上报,通过 wx.request 访问:
https://logservice-rtm.agora.io;
https://rtm.logservice.sd-rtn.com;socket 合法域名
RTM 长连接通过 wx.connectSocket 访问:
wss://miniapp.agoraio.cn;SDK 不使用 wx.uploadFile 或 wx.downloadFile,因此无需为这两个接口额外配置域名白名单。