Skip to content

4.即时通讯 1 基本功能

刘冲 edited this page Aug 26, 2021 · 5 revisions

即时通讯 基本功能说明

即时通讯(Instant Messaging,简称 IM)是一种通过网络进行实时通信的系统,允许两人或者多人使用网络即时的传递文字消息、文件、语音与视频交流(来自维基百科的解释)。LeanCloud 即时通讯服务,提供了全面的 IM 能力,可以帮助开发者在自己产品中快速集成 QQ 或微信的功能,如:

  • 终端用户实时在线沟通,发送文字、语音、视频、位置信息等等多媒体消息;
  • 大型聊天室,如网红直播间和球迷论坛的弹幕互动;
  • 公众号的全局消息推送与互动;
  • 在线客服,等等。

下面我们以一系列简单的例子来逐步说明 LenaCloud 即时通讯的使用方法,先从一对一单聊入手。

零,前提

为了节省资源和移动端电量,LeanCloud 的即时通讯是与推送服务共享同一条长链接的,所以 Android 平台要使用即时通讯,首先必须满足以下两个前提:

  1. AndroidManifest.xml 文件中增加 PushService 和各种必要权限的声明,如下所示,也可参考文档
<!-- 基础模块(必须加入以下声明)START -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 基础模块 END -->

<application
  ...
  android:name=".MyLeanCloudApp" >

  <!-- 即时通讯模块、推送(均需要加入以下声明) START -->
  <!-- 即时通讯模块、推送都要使用 PushService -->
  <service android:name="cn.leancloud.push.PushService"/>
  <receiver android:name="cn.leancloud.push.AVBroadcastReceiver">
    <intent-filter>
      <action android:name="android.intent.action.BOOT_COMPLETED"/>
      <action android:name="android.intent.action.USER_PRESENT"/>
      <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
  </receiver>
  <!-- 即时通讯模块、推送 END -->

  <!-- 反馈组件 END -->
</application>
  1. 程序启动的时候,同时启动 PushService,也可参考文档; 通过调用以下代码启动推送服务,同时设置默认打开的 Activity。
// 设置默认打开的 Activity
PushService.setDefaultPushCallback(this, PushDemoActivity.class);

一,一对一单聊

Tom 和 Jerry 是一对好友,Tom 时常要拉着 Jerry 聊聊日常的工作、生活感受。这时候该如何使用即时通讯 SDK 完成这一需求呢?

1.1 用户与 AVIMClient

在所有的聊天场景里,都少不了参与者——「用户」这一核心角色,在 LeanCloud 即时通讯系统中,一个AVIMClient对象代表一个实际的用户,我们首先要为每一个用户创建一个属于他的AVIMClient 实例,方法如下:

// Tom 用自己的名字作为clientId,获取 AVIMClient 对象实例
AVIMClient client = AVIMClient.getInstance("Tom");

AVIMClientgetInstance(String clientId) 方法,可以通过传入一个字符串 clientId,获得唯一确定的实例,也即是说只要传入的 clientId 不变,多次调用时得到的都会是同一个实例对象。在 LeanCloud 即时通讯服务里面,clientId 代表实际产品中的一个用户,它必须满足如下要求:

  • clientId 不能为空,也不能超过 64 个字符;
  • clientId 由产品自己解释,应用需要自己保证 clientId 是全局唯一的。如果两个不同用户使用相同的 clientId 登录会被认为是同一个即时通讯用户,会导致消息串发。

通过 getInstance 得到 AVIMClient 实例,这时候还不能马上开始聊天,因为该实例还处于断网状态。AVIMClient 一共有三种状态:

  • AVIMClientStatusNone:这是 AVIMClient 的初始状态,表示此 client 尚未登录。
  • AVIMClientStatusOpened:登录状态,此时 client 可以正常的收发消息。
  • AVIMClientStatusPaused:client 登录之后,由于网络中断而处于连接断开状态,此时无法收发消息。

AVIMClient 实例只有处于登录状态才能开始与其他人的聊天互动,接下来我们来看看如何登录。

1.2 登录聊天系统

AVIMClient 实例通过执行 open 操作可以建立与即时通讯云端的连接,方法如下:

client.open(new AVIMClientCallback() {
  @Override
  public void done(AVIMClient client, AVIMException e) {
  	if (null != e) {
  	  // 发生错误,open 失败
  	} else {
  	  // open 成功,client 此时进入联网状态,接下来就可以收发消息了。
  	}
  }
});

open 操作在整个 client 的使用周期内只需要调用一次,以后就可以一直使用了。

我们把 open 操作叫做「登录」,但是这里的登录与通常进行用户认证的登录并不相同,因为不需要提供任何的用户名/密码,AVIMClient 的登录,只是让客户端与 LeanCloud 即时通讯云端建立连接,从而开始进入下面实际的消息交互环节。

1.3 创建对话

Tom 要给 Jerry 发送消息,首先需要创建一个包含两个人的「对话(AVIMConversation)」。「对话(AVIMConversation)」是 LeanCloud 即时通讯系统里另一个核心概念,和实际生活中的聊天室、频道、房间等对应,表示若干用户组成的一个聊天群组。LeanCloud 即时通讯系统里,所有的聊天消息、成员事件都是围绕「对话(AVIMConversation)」进行的。

在一个已经登录的 AVIMClient 上调用 createConversation 方法,可以创建一个对话,示例如下:

client.createConversation(Arrays.asList("Jerry"), "Tom & Jerry", null, new AVIMConversationCreatedCallback() {
  @Override
  public void done(AVIMConversation conversation, AVIMException e) {
  	if (null != e) {
  	  // 发生错误,创建失败
  	} else {
  	  // 创建成功,conversation 就是生成的对话实例。
  	}
  }
});

createConversation 方法的参数有:

  • List conversationMembers(成员列表)。可以在创建对话的时候,就指定需要的成员,这些成员会收到邀请加入对话的通知。当前登录用户即为对话的创建者,也会被自动加入到成员列表之中。在创建对话时成员列表也可以为空,可以将来再加入更多成员。
  • String name(对话名字)。你可以为每一个对话都指定一个便于识别的名字(例如聊天室),也可以不指定(例如一对一的单聊)。
  • Map<String, Object> attributes(对话附属属性)。允许你给对话指定附加的属性,以 key-value 的形式存储,内容由应用自己解释。对话属性允许为空。
  • AVIMConversationCreatedCallback callback(结果回调函数)。如上例所示,创建对话的结果通过回调函数通知创建者。

当对话创建成功之后,你可以在 LeanCloud 控制台 > 存储 > 数据 > _Conversation 中看到其结果。_Conversation 表中各字段的含义如下:

名称 类型 描述
name String 对话的名字
m Array 对话中成员的列表
attr Object 开发者设置的对话的自定义属性
c String 对话的创建者的 ClientId
lm Date 对话中最后一条消息发送的时间
mu Array 对话中设置了静音的成员。Android 设备需要先开启混合推送服务,才能接收离线消息推送。

对话创建好之后,我们就可以往对话中发送消息了。

扩展阅读 —— 唯一对话

上面代码演示了 Tom 希望和 Jerry 聊天时,就新建一个包含两人的对话,而所有的聊天消息都是从属于某一个对话的,所以这样也带来一个问题:每次都新建一个对话的话,我们是会遗失历史消息的。如果希望复用同一个已经存在的对话(也就是要求 createConversation 的结果是新建或者返回已有的对话),该怎么做呢?

AVIMClient还有另一个方法:

public void createConversation(final List<String> conversationMembers, String name, final Map<String, Object> attributes, final boolean isUnique, final AVIMConversationCreatedCallback callback)

新增了一个 isUnique 参数,表示如果已经存在符合条件的对话,是否返回已有对话实例。

  • 为 false 时,则一直为创建新的对话。isUnique 默认为 false。

  • 为 true 时,则先查询,如果已有符合条件的对话(仅 conversationMembers 为有效查询条件),则返回已有的,否则,创建新的并返回。

1.4 发送消息

LeanCloud 即时通讯服务默认支持多种消息类型:文本,图像,语音,视频,位置,二进制消息,等等。后面会有详细说明,现在我们先试试让 Tom 给 Jerry 发送一条文本消息。示例如下:

// 构建文本消息
AVIMTextMessage msg = new AVIMTextMessage();
msg.setText("耗子,起床!");
// 把消息发到对话里
conversation.sendMessage(msg, new AVIMConversationCallback() {
  @Override
  public void done(AVIMException e) {
  	if (null != e) {
  	  // 发生错误,消息没有发送出去。
  	} else {
  	  // 消息发送成功。
  	}
  }
});

AVIMConversation#sendMessage 方法支持两个参数:

  • AVIMMessage message(待发送的消息)。AVIMTextMessage 是 AVIMMessage 的一个子类。
  • AVIMConversationCallback callback(回调函数)。通过该回调函数通知调用者发送操作是否成功。

现在 Tom 这一方完成了一系列操作,给 Jerry 发过去了一条消息,那 Jerry 该如何接收并响应这条消息呢?

1.5 接收方的操作

Jerry 要接收到别人发给他的消息,和 Tom 一样,他首先也要创建自己的 AVIMClient 实例并登录聊天系统,之后因为消息接收方是被动接受系统的通知,所以 Jerry 需要注册一些通知/事件响应函数,才能及时接收到实时消息。

LeanCloud 即时通讯服务中主要包含两大类事件通知:成员变化的事件通知和聊天消息的通知。

1.5.1 处理对话成员相关的事件通知

诸如「你被 xx 邀请加入 xx 对话」、「xx 成员被 xx 邀请加入 xx 对话」、「你被 xx 踢出 xx 对话」、「xx 成员被 xx 踢出 xx 对话」等等,主要是当前用户被邀请加入 / 踢出某个对话,以及当前用户参与的对话中发生了新的成员变化事件。对话成员变化的相关通知在 AVIMConversationEventHandler 抽象类中进行了定义,开发者必须实现的方法如下:

public abstract class AVIMConversationEventHandler {
  /**
   * 实现本方法来处理当前用户被邀请到某个聊天对话事件
   *
   * @param client
   * @param conversation 被邀请的聊天对话
   * @param operator 邀请你的人
   */
  public abstract void onInvited(AVIMClient client, AVIMConversation conversation,
      String operator);

  /**
   * 实现本方法来处理当前用户被踢出某个聊天对话事件
   *
   * @param client
   * @param conversation
   * @param kickedBy 踢出你的人
   */
  public abstract void onKicked(AVIMClient client, AVIMConversation conversation,
      String kickedBy);

  /**
   * 实现本方法以处理聊天对话中的参与者离开事件
   *
   * @param client
   * @param conversation
   * @param members 离开的参与者
   * @param kickedBy 离开事件的发动者,有可能是离开的参与者本身
   */
  public abstract void onMemberLeft(AVIMClient client,
      AVIMConversation conversation, List<String> members, String kickedBy);

  /**
   * 实现本方法以处理聊天对话中的参与者加入事件
   *
   * @param client
   * @param conversation
   * @param members 加入的参与者
   * @param invitedBy 加入事件的邀请人,有可能是加入的参与者本身
   */
  public abstract void onMemberJoined(AVIMClient client,
      AVIMConversation conversation, List<String> members, String invitedBy);
}

开发者需要实现自己的 AVIMConversationEventHandler,并调用 AVIMMessageManager.setConversationEventHandler(conversationEventHandler) 进行设置。完整的示例代码如下:

public class MyConversationEventHandler extends AVIMConversationEventHandler {
  public void onInvited(AVIMClient client, AVIMConversation conversation, String operator) {
    ;
  }

  public void onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy) {
  	;
  }

  public void onMemberLeft(AVIMClient client,
      AVIMConversation conversation, List<String> members, String kickedBy) {
    ;
  }

  public void onMemberJoined(AVIMClient client,
      AVIMConversation conversation, List<String> members, String invitedBy) {
    ;
  }
}

// 设置 handler
AVIMMessageManager.setConversationEventHandler(new MyConversationEventHandler());
1.5.2 新消息通知

当前用户参与对话中的新消息,会通过消息处理 handler 通知用户。MessageHandler 的定义如下:

public abstract class MessageHandler<T extends AVIMMessage> {
  public abstract void onMessage(T message, AVIMConversation conversation, AVIMClient client);

  public abstract void onMessageReceipt(T message, AVIMConversation conversation, AVIMClient client);
}

其中 onMessage 方法即表示有新消息到达,它的三个参数分别表示关联的消息、对话、client 实例。onMessageReceipt 方法以后会解释,现在先不管。

开发者需要实现自己的 MessageHandler,并调用 AVIMMessageManager.registerDefaultMessageHandler(handler) 来设置全局的消息处理 handler。完整的示例代码如下:

public class MyDefaultMessageHandler<AVIMMessage> extends MessageHandler {
  public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) {
    ;
  }

  public void onMessageReceipt(T message, AVIMConversation conversation, AVIMClient client) {
  	;
  }
}

// 设置 handler
AVIMMessageManager.registerDefaultMessageHandler(new MyDefaultMessageHandler());

扩展阅读 —— 设置不同的消息处理 handler

消息处理的 MessageHandler 在 AVIMMessageManager 中进行注册时有两个不同的方法: registerDefaultMessageHandlerregisterMessageHandler。其中 registerMessageHandler 支持给特定类型的消息指定特定的处理 handler:

public static void registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)

当即时通讯 SDK 收到一条消息的时候,会优先根据消息类型查找与之匹配的特定 messageHandler,如果发现当前没有任何注册的普通的 messageHandler,才会去调用 defaultMessageHandler。

在 AVIMMessageManager 中多次注册 defaultMessageHandler,只有最后一次调用的才是有效的;而通过 registerMessageHandler 注册的 MessageHandler,则是可以同存的。

二,多人群聊

接下来我们看看如何实现一个稍微复杂一点的聊天场景:多人群聊。与一对一聊天相比,多人群聊在消息收发流程上是完全一致的,这里的差异主要在于成员的添加和删除。

AVIMConversation 上涉及到成员增减的方法有:

操作目的 接口名
自身主动加入 AVIMConversation#join
添加其他成员 AVIMConversation#addMembers
自身主动退出 AVIMConversation#quit
剔除其他成员 AVIMConversation#kickMembers

2.1 加入一个群聊对话

假设 Jerry、Bob、Harry 和 William 的四个人创建了一个多人群聊对话(id:551260efe4b01608686c3e0f),现在 Tom 希望加入其中,其示例代码如下:

AVIMConversation conversation = client.getConversation("551260efe4b01608686c3e0f");
conversation.join(new AVIMConversationCallback(){
  @Override
  public void done(AVIMException e){
    if(null == e){
      // 加入成功
    }
  }
});

Tom 成功加入这个群聊对话之后,该群内的其他成员会收到 onMemberJoined 通知。

2.2 从对话里面退出

假如 Tom 加入 Jerry 四人的群聊对话之后,又想自己退出去,其操作代码如下:

AVIMConversation conversation = client.getConversation("551260efe4b01608686c3e0f");
conversation.quit(new AVIMConversationCallback(){
  @Override
  public void done(AVIMException e){
    if(null == e){
      // 退出成功
    }
  }
});

Tom 成功退群之后,该群内的其他成员会收到 onMemberLeft 通知。

2.3 邀请他人加入群聊对话

假如 Tom 加入 Jerry 四人的群聊对话之后,还想把大家的好友 Mary 也拉进去,其操作代码如下:

AVIMConversation conversation = client.getConversation("551260efe4b01608686c3e0f");
conversation.addMembers(Arrays.asList("Mary"), new AVIMConversationCallback() {
  @Override
  public void done(AVIMException e){
    if(null == e){
      // 邀请成功
    }
  }
});

只有已经加入到对话里面的用户才可以邀请其他人。

Tom 邀请成功之后,该群内的其他成员会收到 onMemberJoined 通知,Mary 则会收到一条 onInvited 通知。

No. 邀请者 被邀请者 其他人
1 发出请求 addMembers
2 收到 onInvited 通知
3 收到 onMemberJoined 通知 收到 onMemberJoined 通知

2.4 从群聊对话中踢出部分成员

假如 Tom 加入之后,Jerry 发现他经常发一些不合适的消息,被群里好几个朋友投诉,他决定把 Tom 踢出当前群聊,其操作代码如下:

AVIMConversation conversation = client.getConversation("551260efe4b01608686c3e0f");
conversation.kickMembers(Arrays.asList("Tom"), new AVIMConversationCallback() {
  @Override
  public void done(AVIMException e){
    if(e==null){
      // 踢人成功
    }
  }
});

与邀请用户一样,只有已经加入到对话里面的用户才可以踢出其他人。

Jerry 操作成功之后,Tom 会收到 onKicked 通知,该群内的其他成员会收到 onMemberLeft 通知。

No. 操作者(管理员) 被踢者 其他人
1 发出请求 kickMembers
2 收到 onKicked 通知
3 收到 onMemberLeft 通知 收到 onMemberLeft 通知

2.5 查询群内成员信息

从上面例子可以看出,如果一个对话内发生了成员变动,即时通讯云端会实时通知客户端。所以如果客户端能够一直保持在线,并且及时响应云端发过来的通知,那么在应用层通过调用 AVIMConversation#getMembers 方法是能够得到准确的成员列表的,但是对于移动设备来说,产品一直保持在线几乎是不可能的,所以 SDK 中 AVIMConversation#getMembers 返回的成员列表信息并不完全可靠。 要得到最新的成员列表信息,可以先通过更新 AVIMConversation 属性,然后再调用 getMembers 方法获得当前时点的成员列表,示例代码如下:

conversation.fetchInfoInBackground(new AVIMConversationCallback() {
  @Override
  public void done(AVIMException e) {
    if (null != e) {
      // 发生错误
    } else {
      // members is result.
      List<String> members = conversation.getMembers();
    }
  }
});

还有更多的时候,我们不需要获取所有的成员列表详细信息,而只需要知晓对话内成员数量,则可以调用 AVIMConversation.getMemberCount 这个方法,来实时查询成员数量。其示例代码为:

conversation.getMemberCounter(new AVIMConversationMemberCountCallback() {
  @Override
  public void done(Integer memberCount, AVIMException e) {
    if (null != e) {
      // 发生错误
    } else {
      // memberCount is result.
    }
  }
});

扩展阅读 — 对话成员角色管理

在上面的例子中我们可以看到,在默认情况下,给对话增减成员并不需要特别的权限,所有参与其中的用户都可以进行操作。但很多时候,我们会给成员分配不同的角色,譬如管理员,我们约定只有管理员才可以邀请或者踢出对话成员;或者,对于一些不受欢迎的用户,仅仅踢出对话还不够,我们希望把他们拉入一个黑名单中,永远也无法进入对话并发言。

类似的功能,可以参考后面的章节:高级功能之聊天模式与角色管理

三,消息

前面的例子都是基于普通文本消息的,LeanCloud 即时通讯服务则支持多种消息类型:

  • 文本消息:AVIMTextMessage
  • 图像消息:AVIMImageMessage
  • 音频消息:AVIMAudioMessage
  • 视频消息:AVIMVideoMessage
  • 文件消息:AVIMFileMessage
  • 位置消息:AVIMLocationMessage
  • 二进制消息:AVIMBinaryMessage

下面我们来逐一演示这些消息的发送和接收流程。

3.1 图像消息

可以从系统提供的拍照 API 或本地媒体库中获取完整的图像数据,也可以用网络上有效的图像 URL,来构造图像消息。

通过本地文件或系统拍照 API 获取图像数据,构造图像消息的示例如下:

String filePath = ... // 本地文件路径
AVIMImageMessage picture = new AVIMImageMessage(filePath);
picture.setText("发自我的小米"); // 可以为图像增加多项说明信息
Map < String, Object > attributes = new HashMap < String, Object > ();
attributes.put("location", "旧金山");
picture.setAttribute(attributes);

通过直接使用网络图片,例如从微博上复制的一个图像链接来创建图像消息,其示例如下:

AVFile file =new AVFile("萌妹子","http://pic2.zhimg.com/6c10e6053c739ed0ce676a0aff15cf1c.gif", null);
AVIMImageMessage picture = new AVIMImageMessage(file);
picture.setText("萌妹子一枚");

发送图像消息时,直接把上面生成的图像消息实例当做参数交由 AVIMConversation 发送出去即可:

conversation.sendMessage(picture, new AVIMConversationCallback() {
  @Override
  public void done(AVIMException e) {
    if (null == e) {
      // 发送成功
    }
  }
});

即时通讯 SDK 内部,对不同来源的图像消息的处理流程是完全不一样的:

  • 通过提供图像完整数据构造的消息:SDK 获取了完整的图像数据流,先上传文件到云端,再将文件的元数据以及 URL 等一并包装,发送出去。
  • 通过引用网络图像构造的消息:SDK 并没有将图像实际上传到云端,而仅仅把 URL 包装在消息体内发送出去,这种情况下接收方是无法从消息体中获取图像的元信息数据,但是接收方可以自行通过客户端技术去分析图片的格式、大小、长宽之类的元数据。

3.2 音频消息

与图像消息类似,音频消息也支持通过两种不同的来源创建:

  • 通过本地文件或者系统 API 提供完整音频数据创建,例如:
AVFile file = AVFile.withAbsoluteLocalPath("忐忑.mp3",localFilePath);
AVIMAudioMessage msg = new AVIMAudioMessage(file);
msg.setText("听听人类的神曲~");
  • 通过引用网络音频文件创建,例如:
AVFile file = new AVFile("music", "http://ac-lhzo7z96.clouddn.com/1427444393952", null);
AVIMAudioMessage msg = new AVIMAudioMessage(file);

音频消息的发送也与图像类似,在此不再赘述。

3.3 视频消息

与图像消息类似,视频消息也支持通过两种不同的来源创建:

  • 通过本地文件或者系统 API 提供完整视频数据创建,例如:
AVFile file = AVFile.withAbsoluteLocalPath("bbc_奶酪.mp4", localFilePath);
AVIMVideoMessage msg = new AVIMVideoMessage(file);
msg.setText("听听人类的神曲~");
  • 通过引用网络视频文件创建,例如:
AVFile file = new AVFile("video", "http://ac-lhzo7z96.clouddn.com/1427267336319", null);
AVIMVideoMessage msg = new AVIMVideoMessage(file);

视频消息的发送也与图像类似,在此不再赘述。

3.4 文件消息

图像、音频、视频是三种特殊的文件消息,LeanCloud 也支持发送普通的文件消息(例如一个 pdf 文档,一份代码压缩包等)。文件消息支持两种创建方式:

  • 通过本地文件或者系统 API 提供完整视频数据创建,例如:
AVFile file = AVFile.withAbsoluteLocalPath("bbc_奶酪.zip", localFilePath);
AVIMFileMessage msg = new AVIMFileMessage(file);
msg.setText("最新的压缩包~");
  • 通过引用网络视频文件创建,例如:
AVFile file = new AVFile("zip", "http://ac-lhzo7z96.clouddn.com/1427267336319", null);
AVIMFileMessage msg = new AVIMFileMessage(file);

文件消息的发送也与图像类似,在此不再赘述。

3.5 地理位置消息

地址位置消息的构建方式如下:

AVIMLocationMessage msg = new AVIMLocationMessage();
msg.setLocation(new AVGeoPoint(45.0,34.0));
msg.setText("新开的蛋糕店!耗子咱们有福了…");

地理位置消息的发送也与图像类似,在此不再赘述。

3.6 二进制消息

3.7 消息的本地缓存与有效期

一个对话的消息记录会在云端保留 6 个月,也就是说一个对话可以查询到半年之内的历史消息记录,同时 LeanCloud 即时通讯服务也允许开发者通过额外付费来延长这一期限。 与此同时,即时通讯 SDK 则是有本地消息缓存的,这一缓存没有时间限制。

3.8 拉取历史消息的方法

AVIMConversation 类中提供了多种方法来获取该对话中的历史消息。

3.8.1 通用消息查询接口

AVIMConversation 提供了最简单的方法,可以查询对话中最新的 20 条消息记录(如果不足 20 条则以实际条数为准): void queryMessages(final AVIMMessagesQueryCallback callback)

如果想一次获得更多的结果,可以使用另一个方法:

void queryMessages(final int limit, final AVIMMessagesQueryCallback callback)

这里 limit 就是期望的消息结果数量,有效值在 1 - 200(含)之间。

考虑到网络状况以及本地缓存的存在,即时通讯 SDK 也额外提供了强制从服务端或本地缓存查询的接口(不过一般情况下不建议开发者调用):

/**
 * 强制从服务器端拉取最新消息
 */
void queryMessagesFromServer(int limit, final AVIMMessagesQueryCallback callback);

/**
 * 强制从本地缓存中拉取消息
 */
void queryMessagesFromCache(int limit, AVIMMessagesQueryCallback callback);
3.8.2 消息分类查询接口

上面的消息都是完全按照时间先后顺序来查询,有的产品中需要按照消息类型来查询,譬如只希望展示当前对话中的所有图像消息,AVIMConversation 也提供了接口支持这一需求:

void queryMessagesByType(int msgType, int limit, final AVIMMessagesQueryCallback callback)

开发者可以指定特定的消息类型(具体请参看 AVIMMessageType 里的定义),以及希望获取的结果集大小,来查询特定的历史消息。注意,这个操作总是会从云端来获取消息。

3.8.3 消息分页查询接口

上面的例子都只能获取最新的若干条消息记录,如果我们希望分页,例如客户端根据用户的滑动操作来加载更多历史消息,就需要使用分页查询的接口了。 LeanCloud 即时通讯云端在存储历史消息的时候,是通过 <conversationid,messageId,messageTimestamp> 这样的三元组来唯一定位一条消息的,所以在对历史消息进行分页查询的时候,我们需要明确查询的起点消息(需要提供 messageId 和 messageTimestamp),以及单页消息的数量多少,其接口定义如下:

void queryMessages(final String messageId, final long timestamp, final int limit, final AVIMMessagesQueryCallback callback)

返回的消息列表中不会包含起点消息。如果我们不指定 messageId(为空) 和 timestamp(为 0),那么这个函数就等同于

void queryMessages(final int limit, final AVIMMessagesQueryCallback callback)

了;如果我们在获取一页数据之后,取最老消息记录的 messageId 和 timestamp 继续调用分页查询方法,如此重复就可以继续获取更老的历史消息了。

与分类查询历史消息的接口对应,我们也提供了对特定类型消息的分页查询接口,其定义如下:

void queryMessagesByType(int msgType, final String msgId, final long timestamp, final int limit, final AVIMMessagesQueryCallback callback)

与前面的方法类似,这里只是增加了 msgType 参数,表示指定的消息类型。

上面的方法只支持按时间先后顺序查询更老的历史消息,AVIMConversation 还提供了方法,支持按照不同的方向(向前,即由新到旧,或者向后,即由旧到新)、起始区间(开闭)来查询历史消息,这是一种更加自由和强大的方式,其定义如下:

/**
 * 根据指定的区间来查询历史消息,可以指定区间开闭、查询方向以及最大条目限制
 * @param interval  - 区间,由起止 AVIMMessageIntervalBound 组成
 * @param direction - 查询方向,支持向前(AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromNewToOld)
 *                    或向后(AVIMMessageQueryDirection.AVIMMessageQueryDirectionFromOldToNew)查询
 * @param limit     - 结果最大条目限制
 * @param callback  - 结果回调函数
 */
public void queryMessages(final AVIMMessageInterval interval, AVIMMessageQueryDirection direction, final int limit,
                          final AVIMMessagesQueryCallback callback);

扩展阅读 —— 自定义消息

LeanCloud 即时通讯服务提供了默认的几种多媒体消息,但是如果产品中要实现更多的其他样式的消息,例如红包消息,这时候该怎么办呢?我们也提供了自定义消息类型的扩展机制,具体内容可以参考下一章:高级功能之消息与状态

到目前为止,我们熟悉了最基本的聊天流程,如果你想了解更多的功能和用法,请参看下一章。本文档中提及的完整代码,可以参考文件 即时通讯-基本功能 demo