本文详细介绍如何建立一个简单的项目并使用声网 RTM SDK 实现消息发送与接收。
登录 RTM 系统包括以下流程:
发送和接收点对点消息包括以下流程:
发送和接收频道消息包括以下流程:
参考以下步骤创建一个声网项目:
声网会给每个项目自动分配一个 App ID 作为项目唯一标识。
在声网控制台的项目管理页面,找到你的项目,点击 App ID 右侧的 图标,即可获取项目的 App ID。
参考以下步骤获取 App 证书:
在声网控制台的项目管理页面,找到你的项目,点击配置。
点击主要证书下面的复制图标,即可获取项目的 App 证书。
为提高项目的安全性,声网推荐使用 Token 对即将登录 RTM 系统的用户进行鉴权。
为了方便测试,声网服务器提供部署签发 RTM Token 的功能。参考以下步骤获取 RTM Token:
login
时,请确保填入的用户 ID 与生成 RTM Token 时填入的用户 ID 一致。使用 Android Studio 创建一个 Android 项目。
RtmQuickstart
。com.example.rtmquickstart
。在 AndroidManifest.xml
中添加以下权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
// 自 v1.4.10 起,你无需添加 WRITE_EXTERNAL_STORAGE 权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
// 自 v1.4.9 起,你还需要添加如下权限,以便检测 WIFI 网络的连接状态:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
选择以下任意一种方式将声网 RTM Android SDK 集成到你的项目中。本文使用方法 1 进行集成。
a. 在 /Gradle Scripts/build.gradle(Project: <projectname>)
文件中添加如下代码,以添加 Maven Central 依赖:
buildscript {
repositories {
...
mavenCentral()
}
...
}
allprojects {
repositories {
...
mavenCentral()
}
}
b. 在 /Gradle Scripts/build.gradle(Module: <projectname>.app)
文件中添加如下代码,将声网 RTM Android SDK 集成到你的 Android 项目中:
...
dependencies {
...
// x.y.z,请填写具体的 SDK 版本号,声网推荐你使用最新版本的 SDK。
// 通过发版说明获取最新版本号。
// 对于 RTM Android SDK 1.4.5 版,使用 implementation 'io.agora.rtm:rtm-sdk:1.4.5.0'。
implementation 'io.agora.rtm:rtm-sdk:x.y.z'
}
io.agora.rtm:rtm-sdk:1.4.5.0
集成 SDK。文件 | 对应项目文件夹 |
---|---|
agora-rtm_sdk.jar | ~/app/libs/ |
/arm64-v8a/libagora-rtm-sdk-jni.so | ~/app/src/main/jniLibs/arm64-v8a/ |
/armeabi-v7a/libagora-rtm-sdk-jni.so | ~/app/src/main/jniLibs/armeabi-v7a/ |
/x86/libagora-rtm-jni.so | ~/app/src/main/jniLibs/x86/ |
/x86_64/libagora-rtm-sdk-jni.so | ~/app/src/main/jniLibs/x86_64/ |
在 app/proguard-rules.pro
文件中添加如下行,防止代码混淆:
-keep class io.agora.**{*;}
如果上述代码报错,可修改为:
-keep class io.agora**{*;}
为了帮助你快速实现并理解相关功能,本文通过最简方式,在一个 Activity 里实现以下操作:
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">
<Button
android:id="@+id/login_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="40dp"
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/uid"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginStart="37dp"
android:layout_marginTop="40dp"
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" />
<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_constraintBottom_toTopOf="@+id/msg_box"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/join_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="28dp"
android:layout_marginBottom="24dp"
android:onClick="onClickJoin"
android:text="@string/join_button"
app:layout_constraintBottom_toTopOf="@+id/msg_box"
app:layout_constraintStart_toEndOf="@+id/channel_name"
app:layout_constraintTop_toBottomOf="@+id/login_button" />
<EditText
android:id="@+id/msg_box"
android:layout_width="198dp"
android:layout_height="57dp"
android:layout_marginStart="32dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="12dp"
android:autofillHints=""
android:ems="10"
android:hint="@string/msg"
android:inputType="textPersonName"
android:singleLine="false"
app:layout_constraintBottom_toTopOf="@+id/peer_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/join_button" />
<Button
android:id="@+id/send_channel_msg_button"
android:layout_width="126dp"
android:layout_height="45dp"
android:layout_marginTop="38dp"
android:layout_marginEnd="36dp"
android:layout_marginBottom="26dp"
android:onClick="onClickSendChannelMsg"
android:text="@string/send_channel_msg_button"
app:layout_constraintBottom_toTopOf="@+id/send_peer_msg_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/leave_button" />
<Button
android:id="@+id/logout_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:layout_marginTop="40dp"
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/leave_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
android:layout_marginEnd="28dp"
android:onClick="onClickLeave"
android:text="@string/leave_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/logout_button" />
<EditText
android:id="@+id/peer_name"
android:layout_width="121dp"
android:layout_height="48dp"
android:layout_marginStart="64dp"
android:layout_marginTop="24dp"
android:ems="10"
android:hint="@string/peer_name"
android:inputType="text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/send_channel_msg_button" />
<Button
android:id="@+id/send_peer_msg_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:layout_marginEnd="56dp"
android:onClick="onClickSendPeerMsg"
android:text="@string/send_peer_msg_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/send_channel_msg_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="join_button">Join</string>
<string name="leave_button">Leave</string>
<string name="send_peer_msg_button">Peer MSG</string>
<string name="send_channel_msg_button">Group MSG</string>
<string name="uid">Enter your uid</string>
<string name="msg">Enter your message</string>
<string name="channel_name">Channel name</string>
<string name="peer_name">Peer name</string>
<string name="app_id">Your App ID</string>
<string name="token">Your Token</string>
</resources>
你需要编辑以下字段:
打开 app/java/com.example.rtmquickstart/MainActivity.java
并将内容替换为以下 Java 代码:
package com.example.rtmquickstart;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import java.util.Map;
import io.agora.rtm.ErrorInfo;
import io.agora.rtm.ResultCallback;
import io.agora.rtm.RtmChannel;
import io.agora.rtm.RtmChannelAttribute;
import io.agora.rtm.RtmChannelListener;
import io.agora.rtm.RtmChannelMember;
import io.agora.rtm.RtmClient;
import io.agora.rtm.RtmClientListener;
import io.agora.rtm.RtmFileMessage;
import io.agora.rtm.RtmImageMessage;
import io.agora.rtm.RtmMediaOperationProgress;
import io.agora.rtm.RtmMessage;
import io.agora.rtm.SendMessageOptions;
public class MainActivity extends AppCompatActivity {
// 定义全局变量
// EditText 对象,用于 UI
private EditText et_uid;
private EditText et_channel_name;
private EditText et_message_content;
private EditText et_peer_id;
// 消息发送方的 RTM 用户 ID
private String uid;
// RTM channel name
private String channel_name;
//声网 App ID
private String AppID;
// RTM 客户端实例
private RtmClient mRtmClient;
// RTM 频道实例
private RtmChannel mRtmChannel;
// TextView,在界面显示消息记录
private TextView message_history;
// 消息接收方的 RTM 用户 ID
private String peer_id;
// 消息文本内容
private String message_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化 RTM 实例
try {
AppID = getBaseContext().getString(R.string.app_id);
// 初始化 RTM 客户端
mRtmClient = RtmClient.createInstance(getBaseContext(), AppID,
new RtmClientListener() {
@Override
public void onConnectionStateChanged(int state, int reason) {
String text = "Connection state changed to " + state + "Reason: " + reason + "\n";
writeToMessageHistory(text);
}
@Override
public void onImageMessageReceivedFromPeer(RtmImageMessage rtmImageMessage, String s) {
}
@Override
public void onFileMessageReceivedFromPeer(RtmFileMessage rtmFileMessage, String s) {
}
@Override
public void onMediaUploadingProgress(RtmMediaOperationProgress rtmMediaOperationProgress, long l) {
}
@Override
public void onMediaDownloadingProgress(RtmMediaOperationProgress rtmMediaOperationProgress, long l) {
}
@Override
public void onTokenExpired() {
}
@Override
public void onPeersOnlineStatusChanged(Map<String, Integer> map) {
}
@Override
public void onMessageReceived(RtmMessage rtmMessage, String peerId) {
String text = "Message received from " + peerId + " Message: " + rtmMessage.getText() + "\n";
writeToMessageHistory(text);
}
});
} catch (Exception e) {
throw new RuntimeException("RTM initialization failed!");
}
}
// 登录按钮
public void onClickLogin(View v)
{
et_uid = (EditText) findViewById(R.id.uid);
uid = et_uid.getText().toString();
String token =getBaseContext().getString(R.string.token);
// 登录 RTM 系统
mRtmClient.login(token, uid, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
}
@Override
public void onFailure(ErrorInfo errorInfo) {
CharSequence text = "User: " + uid + " failed 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 onClickJoin(View v)
{
et_channel_name = (EditText) findViewById(R.id.channel_name);
channel_name = et_channel_name.getText().toString();
// 创建频道监听器
RtmChannelListener mRtmChannelListener = new RtmChannelListener() {
@Override
public void onMemberCountUpdated(int i) {
}
@Override
public void onAttributesUpdated(List<RtmChannelAttribute> list) {
}
@Override
public void onMessageReceived(RtmMessage message, RtmChannelMember fromMember) {
String text = message.getText();
String fromUser = fromMember.getUserId();
String message_text = "Message received from " + fromUser + " : " + text + "\n";
writeToMessageHistory(message_text);
}
@Override
public void onImageMessageReceived(RtmImageMessage rtmImageMessage, RtmChannelMember rtmChannelMember) {
}
@Override
public void onFileMessageReceived(RtmFileMessage rtmFileMessage, RtmChannelMember rtmChannelMember) {
}
@Override
public void onMemberJoined(RtmChannelMember member) {
}
@Override
public void onMemberLeft(RtmChannelMember member) {
}
};
try {
// 创建 RTM 频道
mRtmChannel = mRtmClient.createChannel(channel_name, mRtmChannelListener);
} catch (RuntimeException e) {
}
// 加入 RTM 频道
mRtmChannel.join(new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
}
@Override
public void onFailure(ErrorInfo errorInfo) {
CharSequence text = "User: " + uid + " failed to join the channel!" + errorInfo.toString();
int duration = Toast.LENGTH_SHORT;
runOnUiThread(new Runnable() {
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), text, duration);
toast.show();
}
});
}
});
}
// 登出按钮
public void onClickLogout(View v)
{
// 登出 RTM 系统
mRtmClient.logout(null);
}
// 离开频道按钮
public void onClickLeave(View v)
{
// 离开 RTM 频道
mRtmChannel.leave(null);
}
// 发送点对点消息按钮
public void onClickSendPeerMsg(View v)
{
et_message_content = findViewById(R.id.msg_box);
message_content = et_message_content.getText().toString();
et_peer_id = findViewById(R.id.peer_name);
peer_id = et_peer_id.getText().toString();
// 创建消息实例
final RtmMessage message = mRtmClient.createMessage();
message.setText(message_content);
SendMessageOptions option = new SendMessageOptions();
option.enableOfflineMessaging = true;
// 发送点对点消息
mRtmClient.sendMessageToPeer(peer_id, message, option, new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
String text = "Message sent from " + uid + " To " + peer_id + " : " + message.getText() + "\n";
writeToMessageHistory(text);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
String text = "Message fails to send from " + uid + " To " + peer_id + " Error : " + errorInfo + "\n";
writeToMessageHistory(text);
}
});
}
// 发送频道消息按钮
public void onClickSendChannelMsg(View v)
{
et_message_content = findViewById(R.id.msg_box);
message_content = et_message_content.getText().toString();
// 创建消息实例
RtmMessage message = mRtmClient.createMessage();
message.setText(message_content);
// 发送频道消息
mRtmChannel.sendMessage(message, new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
String text = "Message sent to channel " + mRtmChannel.getId() + " : " + message.getText() + "\n";
writeToMessageHistory(text);
}
@Override
public void onFailure(ErrorInfo errorInfo) {
String text = "Message fails to send to channel " + mRtmChannel.getId() + " Error: " + errorInfo + "\n";
writeToMessageHistory(text);
}
});
}
// 将消息记录写入 TextView
public void writeToMessageHistory(String record)
{
message_history = findViewById(R.id.message_history);
message_history.append(record);
}
}
使用 Android Studio 在模拟器或真机上编译并运行项目。运行成功之后,你可以进行以下操作:
运行效果如下图所示:
RTM 支持多个相互独立的 RtmClient 实例。
在收发点对点消息或进行其他频道操作前,请确保你已成功登录声网 RTM 系统(即确保已经收到 onSuccess)。
使用频道核心功能前必须通过调用 createChannel 方法创建频道实例。
你可以创建多个 RtmClient 客户端实例,但是每个客户端实例最多只能同时加入 20 个频道。每个频道的 channelId
参数应该不同。
当离开了频道且不再加入该频道时,可以调用 release 方法及时释放频道实例所占用的资源。
接收到的 RtmMessage 消息对象不能重复利用再用于消息发送。