迁移指南
自 2023 年 6 月起,声网针对市场及行业需求,先后推出了 RTM v2(以下简称 v2)系列版本。此系列版本在功能、性能、体验上都有创新性提升:
-
功能覆盖:该版本通过引入
Channel
、Message
、Topic
、Presence
、Storage
和Lock
等功能模块,能覆盖更多业务场景,你可以把更多的精力集中在自己的业务创新上。 -
性能提升:我们在新版本中重构了后台架构,通过优化网络连接进一步提升性能,提供长时间低延迟、高可靠、大并发、易扩展的实时网络接入能力,让你无需为业务质量担忧。
-
体验优化:我们重新设计并简化了 API 接口;优化了包括用户指南、API 参考在内的所有文档,提供了更全面的示例程序,支持开发者低成本学习使用 SDK,快速完成集成,提高开发效率。
本文提供 RTM v1 和 v2 的主要区别及相关示例代码,从而帮助用户顺利地从 v1 迁移到 v2。
开通服务
开通 v2 和 v1 的步骤相同:
- 注册并登录声网控制台
- 创建声网项目
- 进入功能配置,开通 RTM 服务
- 获取开发所需参数,例如 App ID
如果你没有声网账号,点击此处注册账号。关于如何开通服务和如何创建项目,详见开通服务。
集成 SDK
v2 和 v1 的 SDK 包名不同,你需要注意区分。不变的是,二者都支持 CDN 和 Maven Central 两种集成方式。其中,通过 Maven Central 集成 SDK 的区别如下:
-
v1 的引用方式:
Java...
dependencies {
...
// 将 x.y.z 替换为具体的 SDK 版本号,如 1.5.1
implementation 'io.agora.rtm:rtm-sdk:x.y.z'
} -
v2 的引用方式:
Java...
dependencies {
...
// 将 x.y.z 替换为具体的 SDK 版本号,如 2.1.9
implementation 'io.agora:agora-rtm:x.y.z'
}
v2 的 SDK 包名为 agora-rtm
,而 v1 的 SDK 包名为 rtm-sdk
。
初始化实例
相比于 v1,v2 对初始化参数做了很大的调整,增加了很多新特性,例如端侧加密、云代理等,详见 API 参考。此外,v2 还丰富了接口调用的错误信息,你可以通过 ErrorInfo
数据结构获取错误码、错误原因及 API 操作名称,以便于你能更快地排查故障。结合错误排查文档,你也可以快速找到解决方法。
RTM v2 版本在命名字符集的支持上与 v1 版本存在差异。例如,v2 版本不支持以 "_"
开头或包含 "."
字符的频道名、用户名和 Topic 名等。在从 v1 升级到 v2 或混合使用这两个版本时,需注意字符集差异可能导致的不兼容问题。建议在迁移时统一使用 v2 版本支持的字符集。
-
v1 的示例代码如下:
Javatry {
rtmClient = RtmClient.createInstance(getBaseContext(), "your_appId", new RtmClientListener() {
@Override
public void onConnectionStateChanged(int state, int reason) {
}
@Override
public void onMessageReceived(RtmMessage rtmMessage, String peerId) {
}
@Override
public void onTokenExpired() {
}
@Override
public void onTokenPrivilegeWillExpire() {
}
@Override
public void onPeersOnlineStatusChanged(Map<String, Integer> status) {
}
});
} catch (Exception e) {
throw new RuntimeException("RTM initialization failed!");
} -
v2 的示例代码如下:
JavaRtmConfig rtmConfig = new RtmConfig.Builder("your_appId", "your_userId")
.eventListener(eventListener)
.build();
try {
rtmClient = RtmClient.create(rtmConfig);
} catch (Exception e) {
e.printStackTrace();
}
登录服务
v2 登录服务的方法和 v1 有区别,如下所示:
-
v1 的示例代码如下:
JavartmClient.login("your_token", "your_userId", new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理登录结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
}); -
v2 的示例代码如下:
JavartmClient.login("your_token", new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理登录结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
事件通知
相比于 v1,v2 重新设计了系统事件通知方式和 API 接口,对事件通知类型进行了更详细的分类和聚合,对事件通知的负载数据结构进行了优化。
v2 的事件通知类型有 8 种,如下所示:
事件类型 | 描述 |
---|---|
onMessageEvent | 消息事件通知:接收用户所订阅的 Message Channel 及 Topic 中的所有消息事件通知。 |
onPresenceEvent | 用户出席与自定义状态变更事件通知(简称 Presence 事件通知):接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Presence 事件通知。 |
onTopicEvent | Topic 变更事件通知:接收用户所加入的 Stream Channel 中所有 Topic 变更事件通知。 |
onStorageEvent | 频道属性和用户属性事件通知:接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Channel Metadata 事件通知,及订阅用户的 User Metadata 事件通知。 |
onLockEvent | 锁变更事件通知:接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Lock 事件通知。 |
onConnectionStateChanged | (已废弃)网络连接状态变更事件通知:接收客户端网络连接状态变更的事件通知。 |
onLinkStateEvent | 接收客户端网络连接状态变更的事件通知,包含变更前后的连接状态、服务类型、导致变更的操作类型、变更原因、频道列表等信息。 |
onTokenPrivilegeWillExpire | 接收客户端 Token 将要过期的事件通知。 |
关于事件通知的更多信息及负载数据结构见事件监听。
以监听频道消息 onMessageEvent
事件通知为例,示例代码如下:
-
v1:
JavartmChannel = rtmClient.createChannel("channelName", new RtmChannelListener() {
@Override
public void onMessageReceived(final RtmMessage message, final RtmChannelMember fromMember) {
// 处理消息事件
}
// 添加其他事件通知
}); -
v2:
JavaRtmEventListener eventListener = new RtmEventListener() {
@Override
public void onMessageEvent(MessageEvent event) {
// 处理消息事件
}
// 添加其他事件通知
};
// 方式一:调用 create 方法初始化 RTM Client 实例时添加事件监听
RtmConfig rtmConfig = new RtmConfig.Builder("your_appId", "your_userId")
.eventListener(eventListener)
.build();
try {
rtmClient = RtmClient.create(rtmConfig);
} catch (Exception e) {
e.printStackTrace();
}
// 方式二:在 App 生命周期的任意时间中添加事件监听
rtmClient.addEventListener(eventListener);
从上面的示例代码可以看出以下显著区别:
-
v1 的频道消息事件通知绑定在具体的
channel
实例上,用户需要先调用createChannel()
方法创建channel
实例,然后注册onMessageReceived
回调处理事件,SDK 会在收到消息后通过该回调通知处理程序,并且多个频道要实现绑定多次。而 v2 的消息事件通知绑定在客户端实例上,是全局设置,调用addEventListener()
方法注册回调程序,只需要绑定一次,就可以监听所有订阅的频道或 Topic。 -
v1 的消息事件通知负载数据结构包含的信息相对较少,而 v2 的消息事件通知负载数据结构包含了更多的信息,可以帮助你更好地实现自己的业务。
频道消息
在 1.x 中,发送频道消息的步骤如下:
-
创建一个频道实例
-
加入频道
-
发送频道消息
这种设计会有个弊端,你无法实现发送消息而不收到消息。因为发送消息和接收消息没有解耦。v2 采用了基于 Pub/Sub 的新设计,将频道消息的发送和接收解耦:你无需加入频道即可向指定频道发送消息,你只需订阅指定频道即可接收该频道中的消息,两种操作互不影响。
-
v1 的示例代码如下:
Java// 创建频道
rtmChannel = rtmClient.createChannel("channelName", new RtmChannelListener() {
@Override
public void onMessageReceived(final RtmMessage message, final RtmChannelMember fromMember) {
// 处理消息事件
}
// 添加其他事件通知
});
// 加入频道
rtmChannel.join(new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理加入频道结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
// 发送消息
RtmMessage message = rtmClient.createMessage();
message.setText("Hello World!");
rtmChannel.sendMessage(message, new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
}); -
v2 的示例代码如下:
Java// 在 Message Channel 中发送消息
String message = "Hello world";
PublishOptions options = new PublishOptions();
options.customType = 'PlainText';
rtmClient.publish("channelName", message, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
// 订阅 Message Channel
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
rtmClient.subscribe("channelName", options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息订阅结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
点对点消息
v1 版本中的点对点消息 API 用于向指定的用户发送消息,例如,你如果需要向用户 ID 为 Tony
的用户发送消息,你可以按照如下方式实现:
// v1
RtmMessage message = rtmClient.createMessage();
message.setText("Hello World!");
SendMessageOptions options = new SendMessageOptions();
rtmClient.sendMessageToPeer("Tony", message, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
自 2.2.0 版本起,我们对点对点消息进行了调整,将其定义为用户频道(User Channel):通过 publish
方法发送消息,通过 onMessageEvent
事件通知接收消息。这一新方法的实现效果与 v1 版本中点对点消息的功能完全一致你,你可以按照以下方式去实现:
// v2
// 适用于 2.2.0 及之后版本
PublishOptions options = new PublishOptions();
options.setChannelType(RtmChannelType.USER);
options.setCustomType("PlainText");
rtmClient.publish("Tony", "Hello world", options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
当然,你也可以通过 Message Channel 来实现收件箱功能。只不过这种方式,发送方无法收到送达回执。
// v2
// 1. 订阅自己的收件箱
String channelName = "inbox_Tony";
SubscribeOptions options = new SubscribeOptions();
options.setWithMessage(true);
rtmClient.subscribe(channelName, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理订阅结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
// 2. 向 Lily 发送消息
String channelName = "inbox_Lily";
String message = "This is a message";
PublishOptions options = new PublishOptions();
rtmClient.publish(channelName, message, options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
图片与文件消息
出于对用户数据和隐私保护合规性、成本优化的考虑,RTM 自 1.5.0 版本起不再直接支持传输图片和文件消息,相关的 API 也已经下架。你可以结合 RTM 和第三方对象存储服务(例如 Amazon S3 或者阿里云 OSS)来构建图片与文件消息功能,在获得极佳的实时消息传输体验的同时,还能实现更灵活的技术构建方案,例如,实现 CDN 静态资源加速、图片文本审核等业务需求。以下示例代码向你展示如何使用 v2 和 Amazon S3 对象性存储服务实现图片和文件消息的构建和发送:
// 1. 上传文件至 Amazon S3
// 2. 通知 RTM
JSONObject jsonObject = new JSONObject();
// 文件类型,接收方可以根据此字段解析消息包结构
jsonObject.put("type", "file");
// 你在 Amazon S3 上的 bucket 名称,接收方需要此字段来下载文件
jsonObject.put("bucket", "uploadParams.Bucket");
// 文件在 Amazon S3 存储的 Key,接收方需要此字段来下载文件
jsonObject.put("key", "uploadParams.Key");
// 文件内容类型
jsonObject.put("contentType", "uploadParams.ContentType");
// 文件 URL 地址
jsonObject.put("url", "data.Location");
PublishOptions options = new PublishOptions();
rtmClient.publish("receiver", jsonObject.toString(), options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息发送结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
使用 Amazon S3 实现静态文件存储时,你需要进入 Amazon S3 控制台,然后设置正确的用户权限和访问策略。详见访问控制最佳实践。
用户出席与自定义状态
在 v1 中,你可以订阅或查询多个用户的在线状态、查询频道人数或频道在线成员列表等。v2 不仅保留了这些能力,还在 v1 的基础上进行了升级和扩展,新设计了 Presence 模块。Presence 模块提供监控用户上线、下线及用户临时状态变更的能力,你可以实时获取以下信息:
- 用户加入或离开指定频道
- 自定义临时用户状态及其变更
- 查询指定用户加入或订阅了哪些频道
- 查询指定频道有哪些用户加入及其用户临时状态数据
你可以调用 whoNow
方法,实时查询指定频道的在线用户数量、在线用户列表及在线用户的临时状态等信息。
// v2
PresenceOptions options = new PresenceOptions();
options.setIncludeUserId(true);
options.setIncludeState(true);
options.setPage("yourBookMark");
rtmClient.getPresence().whoNow("channelName", RtmChannelType.MESSAGE, options, new ResultCallback<WhoNowResult>() {
@Override
public void onSuccess(WhoNowResult result) {
// 处理 whoNow 调用结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
你可以调用 whereNow
方法,实时获取指定用户所在频道的列表。
// v2
rtmClient.getPresence().whereNow("Tony", new ResultCallback<ArrayList<ChannelInfo>>() {
@Override
public void onSuccess(ArrayList<ChannelInfo> channels) {
// 处理 whereNow 调用结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
为满足业务场景对用户状态的设置需求,v2 提供了设置临时用户状态的能力,你可以通过 setState
方法自定义临时用户状态。用户可以为自己添加分数、游戏状态、位置、心情、连麦状态等自定义状态。
// v2
HashMap<String, String> stateItems = new HashMap<String, String>();
stateItems.put("mood", "pumped");
rtmClient.getPresence().setState("channelName", RtmChannelType.MESSAGE, stateItems, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理 setState 调用结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
你也可以随时通过 getState
方法获取用户的在线状态,或者通过 removeState
方法删除用户的状态。用户临时状态变更后,RTM 服务器会触发 REMOTE_STATE_CHANGED
类型的 onPresenceEvent
事件通知。具体用法见临时用户状态。
在 v2 中,实时监听频道中用户加入、离开、超时掉线或临时状态变更通知会更加方便,你只需实现如下步骤:
-
实现 Presence 事件监听程序
-
加入频道时,开启
withPresence
开关
// v2
// 1. 实现 Presence 事件监听程序
RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onPresenceEvent(PresenceEvent event) {
// 处理 Precense 事件通知
}
};
rtmClient.addEventListener(eventListener);
// 2. 订阅频道时,开启 withPresence 开关
SubscribeOptions options = new SubscribeOptions();
options.setWithPresence(true);
rtmClient.subscribe("channelName", options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息订阅结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
v2 对实时通知进行了全新的设计,在频道中采取两种模式将 Presence 事件通知到订阅用户:实时通知模式 (Announce) 和定时通知模式 (Interval)。你可以在控制台的项目设置中通过实时通知最大人数大小来决定两种模式相互切换的条件。其中,定时通知模式可防止频道内在线用户过多而导致的事件嘈杂,详见事件通知模式。
用户属性与频道属性
基于 v1 的用户属性和频道属性功能,v2 新增了版本控制和锁控制等能力,并优化了 API 接口形式,让功能的使用更加简单。v2中把用户属性和频道属性挂载在 Storage 模块下,以设置频道属性为例,示例代码如下:
// v2
// 创建 Metadata 实例
Metadata metadata = new Metadata();
// 设置 Major Revision
metadata.setMajorRevision(174298270);
// 添加Metadata Item
ArrayList<MetadataItem> items = new ArrayList<MetadataItem>();
items.add(new MetadataItem("Apple", "100", 174298200));
items.add(new MetadataItem("Banana", "200", 174298100));
metadata.setItems(items);
// 记录设置 Metadata Item 的时间戳和用户 ID
MetadataOptions options = new MetadataOptions();
options.setRecordTs(true);
options.setRecordUserId(true);
rtmClient.getStorage().setChannelMetadata("channelName", RtmChannelType.MESSAGE, metadata, options, "lockName", new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理 setChannelMetadata 结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
如何获取、更新、删除频道属性,如何使用 CAS 控制、锁控制等可以参考频道属性。用户属性的使用与频道属性是类似的,详见用户属性。
v2 通过 onStorageEvent
类型的事件通知将频道属性和用户属性分发给用户,监听 onStorageEvent
事件通知的步骤如下:
-
实现 Storage 事件监听程序
-
加入频道时,开启
withMetadata
开关
// v2
// 1. 实现 Storage 事件监听程序
RtmEventListener eventListener = new RtmEventListener() {
@Override
public void onStorageEvent(StorageEvent event) {
// 处理 Storage 事件通知
}
// 添加其他事件通知
};
rtmClient.addEventListener(eventListener);
// 2. 加入频道时,开启 withMetadata 开关
SubscribeOptions options = new SubscribeOptions();
options.setWithMetadata(true);
rtmClient.subscribe("channelName", options, new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) {
// 处理消息订阅结果
}
@Override
public void onFailure(ErrorInfo errorInfo) {
// 处理报错
}
});
访问区域限定
RTM 支持限定访问区域功能,以适应不同国家或地区的法律法规。开启限定访问区域功能后,不论用户在哪个区域使用你的 App,SDK 都只会访问指定区域的声网服务器。v2 实现访问区域限定的代码和 v1 有区别。
- v1 的示例代码如下:
// v1
RtmServiceContext context = new RtmServiceContext();
context.areaCode = RtmAreaCode.AREA_CODE_GLOB;
setRtmServiceContext(context);
- v2 的示例代码如下:
// v2
RtmConfig rtmConfig = new RtmConfig.Builder(appId, userId)
.areaCode(EnumSet.of(RtmAreaCode.AS))
.eventListener(eventListener)
.build();
其他新特性
除上述版本之间的功能差异之外,v2 还额外增加了很多被广泛验证和使用的新功能,你可以根据项目的需要选择和使用,相信革新版本的 v2 一定可以帮助你快速接入实时互动领域。v2 所具备的功能特性见特性列表。