自去年 LeanCloud 发布实时通讯(IM)服务以后,基于用户反馈和工程师对需求的消化和对业务的提炼,上周正式发布了「实时通讯 2.0 」。设计理念依然是「灵活、解耦、可组合、可定制」,具体能够参考《实时通讯开发指南》,了解 LeanCloud 实时通讯的基本概念和模型。html
能够到 LeanCloud 官方下载点下载 LeanCloud IM SDK v2 版本。将下载到的 jar 包加入工程便可。android
若是您以为一点点阅读文档较慢,能够直接看咱们的「Demo 代码」,而且下载本身运行一下试试看。git
咱们先从最简单的环节入手,看看怎么用 LeanCloud IM SDK 实现一对一文本聊天。github
和 LeanCloud 其余服务同样,实时聊天服务的初始化也是在 Application 的 onCreate
方法中进行的:web
public class MyApplication extends Application{ public void onCreate(){ ... AVOSCloud.initialize(this,"{{appId}}","{{appKey}}"); ... } }
而且在AndroidManifest.xml中间声明:算法
<manifest> ... <application android:name=".MyApplication" ....> ... <service android:name="com.avos.avoscloud.PushService" /> <receiver android:name="com.avos.avoscloud.AVBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.USER_PRESENT" /> </intent-filter> </receiver> ... </application> </manifest>
接下来咱们开始一步一步接入聊天服务。数组
用户在开始聊天以前,须要先登陆 LeanCloud 云端。这个登陆并不须要用户名、密码认证,只是与 LeanCloud 云端创建一个长链接,因此只须要传入一个可惟一标识当前用户的 clientId
便可。缓存
在本 SDK 中,咱们会为每个终端用户开启一个 AVIMClient
实例,获取这一实例的方法位于 com.avos.avoscloud.im.v2.AVIMClient
类中,其声明以下:安全
public static AVIMClient getInstance(String clientId)
SDK 内部会为每个 clientId 建立惟一的 AVIMClient
实例,同一个 clientId 屡次调用该方法,获得的都是同一个结果。因此若是要支持同一个客户端内多帐号登陆,只要使用不一样的 clientId 屡次调用该方法便可。LeanCloud IM SDK 自己是支持多帐户同时登陆的。服务器
获得 AVIMClient
实例以后,咱们须要登陆 LeanCloud 云端。这是经过调用 AVIMClient 的 open
方法实现的,其声明以下:
public void open(final AVIMClientCallback callback)
open
函数返回的时候,会把 AVIMClient
实例和 AVException
信息(若是发生错误的话)传给 AVIMClientCallback
回调接口。
好了,咱们如今来实际看一下这个过程如何实现。假定聊天发起方名叫 Tom,为直观起见,咱们使用用户名来做为 clientId
登陆聊天系统(LeanCloud 云端只要求 clientId
在应用内惟一便可,具体用什么数据由应用层决定),代码以下:
AVIMClient imClient = AVIMClient.getInstance("Tom"); imClient.open(new IMClientCallback(){ @Override public void done(AVIMClient client, AVException e) { if (null != e) { // 出错了,多是网络问题没法链接 LeanCloud 云端,请检查网络以后重试。 // 此时聊天服务不可用。 e.printStackTrace(); } else { // 成功登陆,能够开始进行聊天了(假设为 MainActivity)。 Intent intent = new Intent(currentActivity, MainActivity.class); currentActivity.startActivity(intent); }; } });
在本版本 IM SDK 中,开始聊天以前,须要先建立或者加入一个「对话」(AVIMConversation),全部消息都是由某个 client 发往一个「对话」,「对话」内的全部成员会实时收到新消息。
对话支持以下默认属性:
咱们能够经过 AVIMClient
来建立一个对话,其函数声明为:
//指定成员、自定义属性,建立对话 public void createConversation(final List<String> conversationMembers, final Map<String, Object> attributes, final AVIMConversationCreatedCallback callback); //指定成员、名字、自定义属性,建立对话 public void createConversation(final List<String> conversationMembers, String name, final Map<String, Object> attributes, final AVIMConversationCreatedCallback callback); //指定成员、名字、自定义属性和对话标志,建立对话 public void createConversation(final List<String> conversationMembers, String name, final Map<String, Object> attributes, final boolean isTransient, final AVIMConversationCreatedCallback callback);
各参数的含义以下:
接下来咱们看看实际如何建立一个对话。假定咱们要跟「Bob」这个用户进行聊天,咱们先建立一个对话,代码以下:
List<String> clientIds = new ArrayList<String>(); clientIds.add("Tom"); clientIds.add("Bob"); // 咱们给对话增长一个自定义属性 type,表示单聊仍是群聊 // 常量定义: // int ConversationType_OneOne = 0; // 两我的之间的单聊 // int ConversationType_Group = 1; // 多人之间的群聊 Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_OneOne); imClient.createConversation(clientIds, attr, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了,这时候能够显示对话的 Activity 页面(假定为 ChatActivity)了。 Intent intent = new Intent(this, ChatActivity.class); Intent.putExtra(“conversation”, conversation); startActivity(intent); } } });
创建的「对话」在控制台怎么查看
如你所见,咱们建立一个对话的时候,指定了成员(Tom 和 Bob)和一个额外的属性({type: 0})。这些数据保存到云端后,你在 控制台 -> 存储 -> 数据 里面会看到,_Conversation 表中增长了一条记录,新记录的
m
属性值为["Tom", "Bob"]
,attr
属性值为{"type":0}
。如你所料,m
属性就是对应着成员列表,attr
属性就是用户增长的额外属性值(以对象的形式存储)。
经过 AVIMConversation
的 sendMessage
族方法,能够将消息发往目标对话。方法声明以下:
// 直接发送一条消息,在大多数状况下,你该调用这个方法 public void sendMessage(AVIMMessage message, final AVIMConversationCallback callback); // 发送消息时,指定特殊的消息选项,用来发送特别的消息 public void sendMessage(final AVIMMessage message, final int messageFlag, final AVIMConversationCallback callback);
各参数的含义以下:
接下来咱们试着发送一条普通文本消息。示例代码以下:
AVIMMessage message = new AVIMMessage(); message.setContent("hello"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("发送成功,msgId=" + message.getMessageId()); } } });
好了,这样一条消息就发送过去了。可是问题来了,对于「Bob」而言,他怎么才能收到别人发给他的消息呢?
在 Bob 这一端,要能接收到消息,须要以下几步:
1,进行初始化;
2,准备好本身的 AVIMMessageHandler
,响应新消息到达通知。
在本版本 IM SDK 中,咱们设计的框架是将消息类型与具体的 handler 类绑定起来,这样开发者能够为不一样类型的消息设置不一样的 handler,处理起来更加灵活自由。这一绑定过程是经过 com.avos.avoscloud.im.v2.AVIMMessageManager
类的 void registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函数实现的。AVIMMessageManager
类中还有一个方法 void registerDefaultMessageHandler(AVIMMessageHandler handler)
则用来指定全局默认的消息处理 handler。
AVIMMessageHandler
的主要函数以下:
public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client);
对于 Tom 发过来的消息,要接收并显示出来,咱们只需实现 onMessage
方法便可,示例代码以下:
class CustomMessageHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 新消息到来了。在这里增长你本身的处理代码。 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一条新消息:" + msgContent); } }
3,进行登陆,代码也与发送端同样。
Bob 这边要接收到 Tom 发过来的消息,其完整流程以下:
// 自定义消息响应类 class CustomMessageHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 新消息到来了。在这里增长你本身的处理代码。 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一条新消息:" + msgContent); } } // application 的初始化部分 public void onCreate(){ ... AVOSCloud.initialize(this,"{{appId}}","{{appKey}}"); AVIMMessageManager.registerDefaultMessageHandler(new CustomMessageHandler()); ... } // 用户登陆部分 AVIMClient imClient = AVIMClient.getInstance("Bob"); imClient.open(new IMClientCallback(){ @Override public void done(AVIMClient client, AVException e) { if (null != e) { // 出错了,多是网络问题没法链接 LeanCloud 云端,请检查网络以后重试。 // 此时聊天服务不可用。 e.printStackTrace(); } else { // 成功登陆,能够开始进行聊天了。 }; } });
注意!
AVIMMessageManager.registerDefaultMessageHandler()
必定要在AVIMClient.open()
以前调用,不然可能致使服务器发回来的部分消息丢失。
在 app 退出的时候,或者切换用户的时候,咱们须要断开与 LeanCloud 实时通讯服务的长链接,这时候须要调用 AVIMClient.close(final AVIMClientCallback callback)
函数。通常状况下,这个函数都会关闭链接并马上返回,这时候 Leancloud 实时通讯服务端就会认为当前用户已经下线。
从上面的例子中能够看到,要接收到别人给你发送的消息,须要重载 AVIMMessageHandler 类。从 v2 版开始,LeanCloud IM SDK 大量采用回调来反馈操做结果,可是对于一些被动的消息通知,则仍是采用接口来实现的,包括:
LeanCloud IM SDK 内部使用了三种接口来响应这些事件。
主要用来处理网络变化事件,接口定义在 AVIMClientEventHandler
,主要函数为:
void onConnectionPaused(AVIMClient client)
指网络链接断开事件发生,此时聊天服务不可用。void onConnectionResume(AVIMClient client)
指网络链接恢复正常,此时聊天服务变得可用。在网络中断的状况下,全部的消息收发和对话操做都会出现问题。
经过 AVIMClient.setClientEventHandler(AVIMClientEventHandler handler)
能够设定全局的 ClientEventHandler。
主要用来处理对话中成员变化的事件,接口定义在 AVIMConversationEventHandler
,主要函数为:
onMemberLeft(AVIMClient client, AVIMConversation conversation, List<String> members, String kickedBy)
对话中有成员离开时全部剩余成员都会收到这一通知。参数意义说明以下:onMemberJoined(AVIMClient client, AVIMConversation conversation, List<String> members, String invitedBy)
对话中有新成员加入时全部成员都会收到这一通知。参数意义说明以下:onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy)
当前用户被踢出对话的通知,参数意义说明以下:onInvited(AVIMClient client, AVIMConversation conversation, String operator)
当前用户被邀请加入对话的通知。参数意义说明以下:经过 AVIMMessageManager.setConversationEventHandler(AVIMConversationEventHandler handler)
能够设置全局的 ConversationEventHandler。
主要用来处理新消息到达事件,接口定义在 MessageHandler
,AVIMMessageHandler
是一个空的实现类,咱们应该经过重载 AVIMMessageHandler 的相关方法来完成消息处理。主要的方法有:
onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
指接收到新的消息。参数意义说明以下:onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
本身发送的消息被对方接收时会收到此通知,各参数意义同上。经过 AVIMMessageManager.registerDefaultMessageHandler(handler)
能够设置全局的 MessageHandler。
咱们实现这三类接口,就能够处理全部的通知消息了。示例代码以下:
// 处理网络状态变化事件 class CustomNetworkHandler extends AVIMClientEventHandler { @Override public void onConnectionPaused(AVIMClient client) { // 请按本身需求改写 Logger.d("connect paused"); } @Override public void onConnectionResume(AVIMClient client) { // 请按本身需求改写 Logger.d("connect resume"); } } // 处理对话成员变化事件 class CustomConversationHandler extends AVIMConversationEventHandler { public private Context gContext = null; private void toast(String str) { Toast.makeText(gContext, str, Toast.LENGTH_SHORT).show(); } private void toast(Context context, String str) { Toast.makeText(context, str, Toast.LENGTH_SHORT).show(); } @Override public void onMemberLeft(AVIMClient client, AVIMConversation conversation, List<String> members, String kickedBy) { // 请按本身需求改写 toast(MsgUtils.nameByUserIds(members) + " left, kicked by " + MsgUtils.nameByUserId(kickedBy)); //注:MsgUtils 是一个辅助类,nameByUserIds 用来将 userId 转换成用户名 } @Override public void onMemberJoined(AVIMClient client, AVIMConversation conversation, List<String> members, String invitedBy) { // 请按本身需求改写 toast(MsgUtils.nameByUserIds(members) + " joined , invited by " + MsgUtils.nameByUserId(invitedBy)); //注:MsgUtils 是一个辅助类,nameByUserIds 用来将 userId 转换成用户名 } @Override public void onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy) { // 请按本身需求改写 toast("you are kicked by " + MsgUtils.nameByUserId(kickedBy)); } @Override public void onInvited(AVIMClient client, AVIMConversation conversation, String operator) { // 请按本身需求改写 toast("you are invited by " + MsgUtils.nameByUserId(operator)); } }; // 处理新消息到达事件 class CustomMsgHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 请按本身需求改写 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一条新消息:" + msgContent); } @Override public void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 请按本身需求改写 Logger.d("发往对话 " + conversation.getConversationid() + " 的消息 "+ message.getMessageId() +" 已被接收"); } } // 设置事件响应接口 AVIMClient.setClientEventHandler(new CustomNetworkHandler()); AVIMMessageManager.setConversationEventHandler(new CustomConversationHandler()); AVIMMessageManager.registerDefaultMessageHandler(new CustomMsgHandler());
上面的代码演示了如何发送简单文本信息,可是如今的交互方式已经愈来愈多样化,图像、语音、视频已经是很是广泛的消息类型。v2 版的 IM SDK 已经能够很好地支持这些富媒体消息,具体说明以下:
咱们默认支持文本、图像、语音、视频、文件、地理位置等富媒体消息,全部这些消息类型都有一个共同的基类:AVIMTypedMessage,其声明为
public abstract class AVIMTypedMessage extends AVIMMessage { public AVIMTypedMessage(); public int getMessageType(); @Override public final String getContent(); @Override public final void setContent(String content); }
这里咱们为每一种富媒体消息定义了一个消息类型,LeanCloud SDK 自身使用的类型是负数(以下面列表所示),全部正数留给开发者自定义扩展类型使用,0 做为「没有类型」被保留起来。
消息 | 对应的消息类型 |
---|---|
文本消息 | -1 |
图像消息 | -2 |
音频消息 | -3 |
视频消息 | -4 |
位置消息 | -5 |
文件消息 | -6 |
AVIMTypedMessage 子类,表示通常的文本消息,其声明为
public class AVIMTextMessage extends AVIMTypedMessage { public String getText(); public void setText(String text); public Map<String, Object> getAttrs(); public void setAttrs(Map<String, Object> attr); }
能够看到,对于文本消息,主要的属性有 text
和 attrs
两个,经过简单的 getter/setter 就能够访问到。要发送文本消息,示例代码为:
AVIMTextMessage message = new AVIMTextMessage(); message.setText("hello"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent."); } } });
AVIMTypedMessage 子类,用来发送带附件的消息,开发者能够用它来发送「离线文件」。对于此类消息,LeanCloud IM SDK 内部会先把文件上传到 LeanCloud 文件存储服务器(自带 CDN 功能),而后把文件元数据(url,文件大小等等)放在消息包内发送到 LeanCloud 实时通讯服务端。其构造函数声明为:
// 传入本地文件路径,构造消息对象 public AVIMFileessage(String localPath) throws FileNotFoundException, IOException; // 传入本地文件,构造消息对象 public AVIMFileMessage(File localFile) throws FileNotFoundException, IOException; // 传入 AVFile 实例,构造消息对象 public AVIMFileMessage(AVFile file);
与文本消息相似,文件消息也支持附带文本和其余自定义属性,能够经过以下方法添加 / 获取更多信息:
发送文件消息的示例代码为:
String localZipfilePath; try { AVIMFileMessage message = new AVIMFileMessage(localZipfilePath); message.setText("这是你要的文档"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到这样消息以后,开发者能够经过如下方法,获取到文件元数据(size 等)和一个包含二进制数据的 AVFile 对象:
AVFile getAVFile()
方法会返回一个二进制文件的 AVFile 实例,以后能够经过 AVFile 来完成数据下载或者其余操做,具体能够参见 AVFile 说明 String getFileUrl()
方法会返回二进制文件的 urllong getSize()
方法会返回二进制文件的实际大小(单位:byte)Map<String, Object> getFileMetaData()
能够获取二进制文件的其余元数据信息。String getText()
方法会返回随文件一块儿发送的文本信息。AVIMFileMessage 子类,专门用来发送图像和附带文本的混合消息,其构造函数声明为:
// 传入本地文件路径,构造消息对象 public AVIMImageMessage(String localPath) throws FileNotFoundException, IOException; // 传入本地文件,构造消息对象 public AVIMImageMessage(File localFile) throws FileNotFoundException, IOException; // 传入 AVFile 实例,构造消息对象 public AVIMImageMessage(AVFile file);
发送图像消息的示例代码为:
String localImagePath; try { AVIMImageMessage message = new AVIMImageMessage(localImagePath); message.setText("你说我好看不?"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到这样消息以后,开发者能够经过以下方法,获取到若干图像元数据(width,height,图像 size)和一个包含图像数据的 AVFile 对象:
int getWidth()
方法会返回图像的宽度(单位:pixel)int getHeight()
方法会返回图像的高度(单位:pixel)AVFile getAVFile()
(继承自 AVIMFileMessage)方法会返回一个图像文件的 AVFile 实例String getFileUrl()
(继承自 AVIMFileMessage)方法会返回图像文件的 urllong getSize()
(继承自 AVIMFileMessage)方法会返回图像文件的实际大小(单位:byte)String getText()
(继承自 AVIMFileMessage)方法会返回随图像一块儿发送的文本信息。Map<String, Object> getFileMetaData()
(继承自 AVIMFileMessage)能够获取图像的其余元数据信息。AVIMFileMessage 子类,专门用来发送语音和附带文本的混合消息,其构造函数声明为:
// 传入本地文件路径,构造消息对象 public AVIMAudioMessage(String localPath) throws FileNotFoundException, IOException; // 传入本地文件,构造消息对象 public AVIMAudioMessage(File localFile) throws FileNotFoundException, IOException; // 传入 AVFile 实例,构造消息对象 public AVIMAudioMessage(AVFile file);
发送音频消息的示例代码为:
String localAudioPath; try { AVIMAudioMessage message = new AVIMAudioMessage(localAudioPath); message.setText("听听我唱的小苹果:)"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到这样消息以后,开发者能够经过以下方法,获取到若干音频元数据(时长 duration、音频 size)和一个包含音频数据的 AVFile 对象:
double getDuration()
方法会返回音频的长度(单位:秒)AVFile getAVFile()
(继承自 AVIMFileMessage)方法会返回一个音频文件的 AVFile 实例String getFileUrl()
(继承自 AVIMFileMessage)方法会返回音频文件的 urllong getSize()
(继承自 AVIMFileMessage)方法会返回音频文件的实际大小(单位:byte)String getText()
(继承自 AVIMFileMessage)方法会返回随音频一块儿发送的文本信息。Map<String, Object> getFileMetaData()
(继承自 AVIMFileMessage)能够获取音频的其余元数据信息。AVIMFileMessage 子类,专门用来发送视频和附带文本的混合消息,其构造函数声明为:
// 传入本地文件路径,构造消息对象 public AVIMVideoMessage(String localPath) throws FileNotFoundException, IOException; // 传入本地文件,构造消息对象 public AVIMVideoMessage(File localFile) throws FileNotFoundException, IOException; // 传入 AVFile 文件,构造消息对象 public AVIMVideoMessage(AVFile file);
发送视频消息的示例代码为:
String localVideoPath; try { AVIMVideoMessage message = new AVIMVideoMessage(localVideoPath); message.setText("敢不敢跟我比一比"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到这样消息以后,开发者能够能够经过以下方法,获取到若干视频元数据(时长 duration、视频 size)和一个包含视频数据的 AVFile 对象:
double getDuration()
方法会返回视频的长度(单位:秒)AVFile getAVFile()
(继承自 AVIMFileMessage)方法会返回一个视频文件的 AVFile 实例String getFileUrl()
(继承自 AVIMFileMessage)方法会返回视频文件的 urllong getSize()
(继承自 AVIMFileMessage)方法会返回视频文件的实际大小(单位:byte)String getText()
(继承自 AVIMFileMessage)方法会返回随视频一块儿发送的文本信息。Map<String, Object> getFileMetaData()
(继承自 AVIMFileMessage)能够获取视频的其余元数据信息。AVIMTypedMessage 子类,支持发送地理位置信息和附带文本的混合消息,其声明为:
public class AVIMLocationMessage extends AVIMTypedMessage { public String getText(); public void setText(String text); public Map<String, Object> getAttrs(); public void setAttrs(Map<String, Object> attr); public AVGeoPoint getLocation(); public void setLocation(AVGeoPoint location); }
与文本消息相似,地理位置消息只是增长了一个 AVGeoPoint 的 Location 属性。要发送位置消息的示例代码为:
AVIMLocationMessage message = new AVIMLocationMessage(); message.setText("快点过来!"); message.setLocation(new AVGeoPoint(15.9, 56.4)); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } });
接收到这样的消息以后,开发者能够获取到具体的地理位置数据。
新版 LeanCloud IM SDK 内部封装了对富媒体消息的支持,全部富媒体消息都是从 AVIMTypedMessage 派生出来的。发送的时候能够直接调用 conversation.sendMessage()
函数。在接收端,咱们也专门增长了一类回调接口 AVIMTypedMessageHandler,其定义为:
public class AVIMTypedMessageHandler<T extends AVIMTypedMessage> extends MessageHandler<T> { @Override public void onMessage(T message, AVIMConversation conversation, AVIMClient client); @Override public void onMessageReceipt(T message, AVIMConversation conversation, AVIMClient client); }
开发者能够编写本身的消息处理 handler,而后调用 AVIMMessageManager.registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函数来注册目标 handler。
接收端对于富媒体消息的通知处理的示例代码以下:
class MsgHandler extends AVIMTypedMessageHandler<AVIMTypedMessage> { @Override public void onMessage(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) { // 请按本身需求改写 switch(message.getMessageType()) { case AVIMReservedMessageType.TextMessageType: AVIMTextMessage textMsg = (AVIMTextMessage)message; Logger.d("收到文本消息:" + textMsg.getText() + ", msgId:" + textMsg.getMessageId()); break; case AVIMReservedMessageType.FileMessageType: AVIMFileMessage fileMsg = (AVIMFileMessage)message; Logger.id("收到文件消息。msgId=" + fileMsg.getMessageId() + ", url=" + fileMsg.getFileUrl() + ", size=" + fileMsg.getSize()); break; case AVIMReservedMessageType.ImageMessageType: AVIMImageMessage imageMsg = (AVIMImageMessage)message; Logger.id("收到图片消息。msgId=" + imageMsg.getMessageId() + ", url=" + imageMsg.getFileUrl() + ", width=" + imageMsg.getWidth() + ", height=" + imageMsg.getHeight()); break; case AVIMReservedMessageType.AudioMessageType: AVIMAudioMessage audioMsg = (AVIMAudioMessage)message; Logger.id("收到音频消息。msgId=" + audioMsg.getMessageId() + ", url=" + audioMsg.getFileUrl() + ", duration=" + audioMsg.getDuration()); break; case AVIMReservedMessageType.VideoMessageType: AVIMVideoMessage videoMsg = (AVIMAudioMessage)message; Logger.id("收到视频消息。msgId=" + videoMsg.getMessageId() + ", url=" + videoMsg.getFileUrl() + ", duration=" + videoMsg.getDuration()); break; case AVIMReservedMessageType.LocationMessageType: AVIMLocationMessage locMsg = (AVIMLocationMessage)message; Logger.id("收到位置消息。msgId=" + locMsg.getMessageId() + ", latitude=" + locMsg.getLocation().getLatitude() + ", longitude=" + locMsg.getLocation().getLongitude()); break; } } @Override public void onMessageReceipt(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) { // 请加入你本身须要的逻辑... } } MsgHandler msgHandler = new MsgHandler(); AVIMMessageManager.registerMessageHandler(AVIMTypedMessage.class, msgHandler);
LeanCloud IM SDK 内部消息分发的逻辑是这样的:
这样一来,在开发者为 TypedMessage(及其子类) 指定了专门的 handler,也指定了全局的 defaultHandler 了的时候,若是发送端发送的是通用的 AVIMMessage 消息,那么接受端就是 AVIMMessageManager.registerDefaultMessageHandler()中指定的 handler 被调用;若是发送的是 AVIMTypedMessage(及其子类)的消息,那么接受端就是 AVIMMessageManager.registerMessageHandler()中指定的 handler 被调用。
继承于 AVIMTypedMessage,开发者也能够扩展本身的富媒体消息。其要求和步骤是:
123
)由开发者本身决定(LeanCloud 内建的消息类型使用负数,全部正数都预留给开发者扩展使用)。AVIMMessageManager.registerAVIMMessageType(Class<? extends AVIMTypedMessage> messageType)
函数进行类型注册AVIMMessageManager.registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函数进行消息处理 handler 注册。AVIMTextMessage 的源码以下,可供参考:
@AVIMMessageType(type = -1) public class AVIMTextMessage extends AVIMTypedMessage { @AVIMMessageField(name = "_lctext") String text; @AVIMMessageField(name = "_lcattrs") Map<String, Object> attrs; public String getText() { return this.text; } public void setText(String text) { this.text = text; } public Map<String, Object> getAttrs() { return this.attrs; } public void setAttrs(Map<String, Object> attr) { this.attrs = attr; } }
与前面的单聊相似,群组聊天也须要先创建一个对话(AVIMConversation),而后发送、接收新的消息。
和单聊相似,创建一个多人聊天的群组也是很简单的。例如:
Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_Group); imClient.createConversation(clientIds, attr, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了! Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(“conversation”, conversation); currentActivity.startActivity(intent); } } });
成功以后,咱们就能够进入聊天界面了。
若是是其余人,须要主动加入到并不是本身建立的群组里面,该怎么作到呢?
AVIMConversation 有一个 join 方法,能够用来主动加入一个群组,其声明为:
void join(AVIMConversationCallback callback)
这里参数的含义以下:
假定用户 Jade 但愿加入上面的群组,其示例代码为:
// 以前是 Jade 登陆的代码 conversation.join(new AVIMConversationCallback(){ @Override public void done(AVException e) { if (null != e) { // 出错了:( } else { // 成功,此时能够进入聊天界面了。。。 Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(“conversation”, conversation); currentActivity.startActivity(intent); } } });
发送消息很是简单,与前面单聊的场景同样。
咱们会注意到,AVIMConversation 还有一个发送消息的方法:
public void sendMessage(final AVIMMessage message, final int messageFlag, final AVIMConversationCallback callback)
而这里 flag 的定义有以下三种类型:
public void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
函数被调用的时机)。接收一个群组的消息,与接收单聊的消息也是同样的。
在查询到聊天室成员以后,可让用户邀请一些本身的朋友加入,做为管理员也能够剔除一些「可怕」的成员。
加入新成员的 API 以下:
void addMembers(final List<String> friendsList, final AVIMConversationCallback callback)
这里各参数的含义以下:
咱们试着在刚才的对话中邀请几我的:
// 假设须要邀请 Alex,Ben,Chad 三人加入对话 List<String> userIds = new ArrayList<String>(); userIds.add("Alex"); userIds.add("Ben"); userIds.add("Chad"); conversation.addMembers(userIds, new AVIMConversationCallback() { @Override public void done(AVException error) { if (null != error) { // 加入失败,报错. error.printStackTrace(); } else { // 发出邀请,此后新成员就能够看到这个对话中的全部消息了。 Logger.d("invited."); } } });
邀请成功之后,相关方收到通知的时序是这样的:
操做者(管理员) 被邀请者 其余人 1, 发出请求 addMembers 2, 收到 onInvited 通知 3, 收到 onMemberJoined 通知 收到 onMemberJoined 通知 收到 onMemberJoined 通知
与加人相似,踢人的 API 声明以下:
void kickMembers(final List<String> friendsList, final AVIMConversationCallback callback)
参数含义同上。咱们试着把 Alex 踢出去:
List<String> userIds = new ArrayList<String>(); userIds.add("Alex"); conversation.kickMembers(userIds, new AVIMConversationCallback() { @Override public void done(AVException error) { if (null != error) { // 失败,报错. error.printStackTrace(); } else { // 成功。 Logger.d("kicked."); } } });
踢人时,相关方收到通知的时序以下:
操做者(管理员) 被踢者 其余人 1, 发出请求 kickMembers 2, 收到 onKicked 通知 3, 收到 onMemberLeft 通知 收到 onMemberLeft 通知
注意!
若是邀请、踢人操做发生的时候,被邀请者/被踢者当前不在线,那么通知消息并不会被离线缓存,因此他们再上线的时候将不会收到通知。
任何成员,均可以主动退出一个群组。AVIMConversation 有一个 quit 方法,其声明为:
void quit(AVIMConversationCallback callback)
这里参数的含义以下:
退出群组以后,该群组内发生的任何事件或者消息,都不会再发到当前用户,当前用户也不能往群组内发送任何消息。
假设用户 Jade 又想退出上面的群组了,其示例代码为:
// 以前是 Jade 登陆的代码 conversation.quit(new AVIMConversationCallback(){ @Override public void done(AVException e) { if (null != e) { // 出错了:( } else { // 成功,这下清静了。。。 currentActivity.finish(); } } });
LeanMessage 会将非暂态消息自动保存在云端,以后开发者能够经过 AVIMConversation 来获取该对话的全部历史消息。获取历史消息的 API 以下:
// 查询当前对话的最新消息,默认返回 100 条 void queryMessages(final AVIMHistoryMessageCallback callback); // 查询当前对话的最新消息,返回 limit 指定的条数 void queryMessages(int limit, final AVIMHistoryMessageCallback callback); // 前向查询当前对话的历史消息,msgId/timestamp 指定消息的起点,limit 指定须要的结果条数 void queryMessages(String msgId, long timestamp, int limit, final AVIMHistoryMessageCallback callback);
各参数含义以下:
经过这一 API 拿到的消息就是 AVIMMessage 或者 AVIMTypedMessage 实例数组,开发者能够像以前收到新消息通知同样处理。示例代码以下:
String oldestMsgId; long oldestMsgTimestamp; conversation.queryMessages(oldestMsgId,oldestMsgTimestamp, limit, new AVIMHistoryMessageCallback(){ @Override public void done(List<AVIMMessage> messages, AVException e) { if (null != e) { // 出错了:( } else { // 成功,能够将消息加入缓存,同时更新 UI } } });
注意:
翻页加载获取历史消息的时候,LeanCloud 云端是从某条消息开始,往前查找开发者指定的 N 条消息,返回给客户端。为此,获取历史消息须要传入三个参数:起始消息的 msgId,起始消息的发送时间戳,须要获取的消息条数。
无论是单聊仍是群聊,当用户 A 发出消息后,若是目标对话中有部分用户当前不在线,LeanCloud 云端能够提供离线推送的方式来提醒用户。这一功能默认是关闭的,你能够在 LeanCloud 应用控制台中开启它。开启方法以下:
这样 iOS 平台上的用户就能够收到 Push Notification 了(固然,前提是应用自己申请到了 RemoteNotification 权限,也将正确的推送证书上传到了 LeanCloud 控制台)。
无论是单聊仍是群聊,对于发往普通的 Conversation 的普通消息,若是接收方当前不在线,LeanCloud 云端支持经过 Push Notification 的方式进行提醒。通常状况下这都是很好的,可是若是某个群组特别活跃,那离线用户就会收到过多的推送,会造成不小的干扰。
对此 LeanCloud IM 服务也容许单个用户来关闭/打开某个对话的离线推送功能。
无论是单聊,仍是群聊,在 LeanCloud IM SDK 里面都是对话(Conversation)。咱们给对话设置了以下几种属性:
咱们提供了专门的类,来搜索特定的群组:经过 imClient.getQuery()
获得一个 AVIMConversationQuery
实例,而后调用 AVIMConversationQuery.whereXXX
系列方法来增长约束条件。
AVIMConversationQuery
的使用方法与 AVQuery 同样,例如要搜索当前登陆用户参与的全部群聊对话,其代码为
// 搜索 Tom 参与的全部群组对话 List<String> clients = new ArrayList<String>(); clients.add("Tom"); AVIMConversationQuery conversationQuery = imClient.getQuery(); conversationQuery.containsMember(clients); // 以前有常量定义: // const int ConversationType_OneOne = 0; // const int ConversationType_Group = 1; conversationQuery.whereEqualTo("attr.type", ConversationType_Group); conversationQuery.findInBackground(new AVIMConversationQueryCallback(){ @Override public void done(List<AVIMConversation> conversations, AVException e) { if (null != e) { // 出错了。。。 e.printStackTrace(); } else { if (null != conversation) { Logger.d("找到了符合条件的 " + conversations.size() + " 个对话"); } else { Logger.d("没有找到符合条件的对话"); } } } });
注意:
这里 conversationQuery.containsMember()
表示对话的成员中至少包含这些人员,可用来根据部分红员查找对话;
与此相似的还有一个 conversationQuery.withMembers()
则表示有且仅有这些成员,用来根据全部成员查找目标对话;conversationQuery.whereXXX()
系列方法可用来限定对话名称和自定义属性,这里要强调的一点是,对于自定义属性的约束条件,属性名必定要以 attr
开头,如上例所示,限定额外的 type
条件的时候须要指定的属性名是 attr.type
。具体能够参看其头文件。
开放聊天室(也叫「暂态」对话)能够用于不少地方,譬如弹幕、直播等等。在 LeanCloud IM SDK 中,开放聊天室是一类特殊的群组,它也支持建立、加入/踢出成员等操做,消息记录会被保存并可供获取;与普通群组不同的地方具体体现为:
和普通的群组相似,创建一个开放聊天室也是很简单的,只是在 AVIMClient.createConversation(conversationMembers, name, attributes, isTransient, callback)
中咱们须要传入 isTransient=true
选项。例如:
Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_Group); imClient.createConversation(clientIds, name, attr, true, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了,进入聊天室 Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(“conversation”, conversation); currentActivity.startActivity(intent); } } });
建立成功以后,咱们就能够进入聊天界面了。开放聊天室的其余操做,都与普通群组操做同样。
只要应用层不作限制,任何终端用户均可以加入开放聊天室,这部分逻辑与以前的加入群组同样。
一样的,离开任何「对话」(不论普通仍是「暂态」),调用 AVIMConversation.quit(callback)
函数便可,这里再也不赘述。
对于开放聊天室来讲,与普通群组有很大一点不一样,就是没有了参与用户列表,取而代之的是能够查询实时在线人数。AVIMConversation.getMemberCount()
函数能够完成这一功能,其声明以下:
void getMemberCount(AVIMConversationMemberCountCallback callback)
参数含义说明以下:
这部分的示例代码以下:
conversation.getMemberCount(new AVIMConversationMemberCountCallback(){ @Override public void done(Integer memberCount, AVException e) { if (null != e) { // 出错了:( } else { // 成功,此时 memberCount 的数值就是实时在线人数 } } });
为了知足开发者对权限和认证的要求,LeanCloud 还设计了操做签名的机制。咱们能够在 LeanCloud 应用控制台中的「设置」->「应用选项」->「聊天推送」下面勾选「聊天服务签名认证」来启用签名(强烈推荐这样作)。启用后,全部的用户登陆、对话建立/加入、邀请成员、踢出成员等操做都须要验证签名,这样开发者就能够对消息进行充分的控制。
客户端这边究竟该如何使用呢?咱们只须要实现 SignatureFactory 接口,而后在用户登陆以前,把这个接口的实例赋值给 AVIMClient 便可(AVIMClient.setSignatureFactory(factory)
)。
设定了 signatureFactory 以后,对于须要鉴权的操做,LeanCloud IM SDK 与服务器端通信的时候都会带上应用本身生成的 Signature 信息,LeanCloud 云端会使用 app 的 masterKey 来验证信息的有效性,保证聊天渠道的安全。
对于 SignatureFactory 接口,咱们只须要实现这两个函数便可:
/** * 实现一个基础签名方法 其中的签名算法会在SessionManager和AVIMClient(V2)中被使用 */ public Signature createSignature(String peerId, List<String> watchIds) throws SignatureException; /** * 实现AVIMConversation相关的签名计算 */ public Signature createConversationSignature(String conversationId, String clientId, List<String> targetIds, String action) throws SignatureException;
createSignature
函数会在用户登陆的时候被调用,createConversationSignature
会在对话建立/加入、邀请成员、踢出成员等操做时被调用。
你须要作的就是按照前文所述的签名算法实现签名,其中 Signature
声明以下:
public class Signature { public List<String> getSignedPeerIds(); public void setSignedPeerIds(List<String> signedPeerIds); public String getSignature(); public void setSignature(String signature); public long getTimestamp(); public void setTimestamp(long timestamp); public String getNonce(); public void setNonce(String nonce); }
其中四个属性分别是:
下面的代码展现了基于 LeanCloud 云代码进行签名时,客户端的实现片断,你能够参考它来完成本身的逻辑实现:
public class KeepAliveSignatureFactory implements SignatureFactory { @Override public Signature createSignature(String peerId, List<String> watchIds) { Map<String,Object> params = new HashMap<String,Object>(); params.put("self_id",peerId); params.put("watch_ids",watchIds); try{ Object result = AVCloud.callFunction("sign",params); if(result instanceof Map){ Map<String,Object> serverSignature = (Map<String,Object>) result; Signature signature = new Signature(); signature.setSignature((String)serverSignature.get("signature")); signature.setTimestamp((Long)serverSignature.get("timestamp")); signature.setNonce((String)serverSignature.get("nonce")); return signature; } }catch(AVException e){ throw (SignatureFactory.SignatureException) e; } return null; } @Override public Signature createConversationSignature(String convId, String peerId, List<String> targetPeerIds,String action){ Map<String,Object> params = new HashMap<String,Object>(); params.put("self_id",peerId); params.put("group_id",convId); params.put("group_peer_ids",targetPeerIds); params.put("action",action); try{ Object result = AVCloud.callFunction("group_sign",params); if(result instanceof Map){ Map<String,Object> serverSignature = (Map<String,Object>) result; Signature signature = new Signature(); signature.setSignature((String)serverSignature.get("signature")); signature.setTimestamp((Long)serverSignature.get("timestamp")); signature.setNonce((String)serverSignature.get("nonce")); return signature; } }catch(AVException e){ throw (SignatureFactory.SignatureException) e; } return null; } }
LeanCloud IM SDK 专一作好底层的通信服务,有更多能够定制化的地方,譬如说:
由于缺乏 UI 组件,实事求是地讲在新用户接入成本可能稍高,可是在业务规模扩大、产品需求变多以后,相信你们会愈来愈喜欢 LeanCloud 这种自由灵活的使用体验,以及稳定迅捷的服务质量。