刚开始码项目的时候,我是非常排斥使用极光IM。因为在20115年(青葱少年)我就使用过,那时候极光IM刚开始做,还存在诸多的问题(当时把我虐的不要不要的0.0)。奈何胳膊拗不过大腿,领导让用就用吧。就这样再次开启了极光之旅......
实现单聊、群聊
实现基本的单聊、群聊还是比较简单的 ,毕竟我是只是调用下API。
首先看下JMessage
JMSGAbstractContent
JMSGGroup
JMSGMessage
JMSGUser
JMSGMessageDelegate
这几个.h文件里面的方法就能大概知道SDK的使用方法了。
首先发送消息使用的是JMSGMessage
里面的方法
//创建单聊的消息体
//单独调用此接口创建消息,SDK 不会本地保存消息,再调用发送接口时才会保、
//如果上层希望创建消息时就本地化保存,请使用 [JMSGConversation createMessageWithContent:]
+ (JMSGMessage *)createSingleMessageWithContent:(JMSGAbstractContent *)content
username:(NSString *)username;
//创建群聊的消息体
//单独使用此接口创建消息体,SDK 不会本地保存消息,再调用发送接口时才会保存;
//如果希望创建消息时就本地化保存,请使用 [JMSGConversation createMessageWithContent:]
+ (JMSGMessage *)createGroupMessageWithContent:(JMSGAbstractContent *)content
groupId:(NSString *)groupId;
//发送消息我使用的是这个方法 因为后面我要用消息体对界面进行更新设置 后面会说到
+ (void)sendMessage:(JMSGMessage *)message;
有了极光的用户ID,配合这三个接口就能实现发送消息了。
既然消息发了,那消息到底有没有发出去呢?
获取消息发送的回调在JMSGMessageDelegate
代理里面
我们只要添加代理就好了。在JMessage
文件下如下方法添加代理
/*!
* @abstract 增加回调(delegate protocol)监听
*
* @param delegate 需要监听的 Delegate Protocol
* @param conversation 允许为nil
* - 为 nil, 表示接收所有的通知, 不区分会话.
* - 不为 nil,表示只接收指定的 conversation 相关的通知.
*/
+ (void)addDelegate:(id <JMessageDelegate>)delegate withConversation:(JMSGConversation *)conversation;
我是监听了具体的一个会话
JMSGMessageDelegate
代理方法
/*!
* @abstract 发送消息结果返回回调
*
* @param message 原发出的消息对象
* @param error 不为nil表示发送消息出错
*
* @discussion 应检查 error 是否为空来判断是否出错. 如果未出错, 则成功.
*/
@optional
- (void)onSendMessageResponse:(JMSGMessage *)message
error:(NSError *)error;
/*!
* @abstract 接收消息(服务器端下发的)回调
*
* @param message 接收到下发的消息
* @param error 不为 nil 表示接收消息出错
*
* @discussion 应检查 error 是否为空来判断有没有出错. 如果未出错, 则成功.
* 留意的是, 这里的 error 不包含媒体消息下载文件错误. 这类错误有单独的回调 onReceiveMessageDownloadFailed:
*
* 收到的消息里, 也包含服务器端下发的各类消息事件, 比如有人被加入了群聊. 这类消息事件处理为特殊的 JMSGMessage 类型.
*
* 事件类的消息, 基于 JMSGMessage 类里的 contentType 属性来做判断,
* contentType = kJMSGContentTypeEventNotification.
*/
@optional
- (void)onReceiveMessage:(JMSGMessage *)message
error:(NSError *)error;
/*!
* @abstract 接收消息媒体文件下载失败的回调
*
* @param message 下载出错的消息
*
* @discussion 因为对于接收消息, 最主要需要特别做处理的就是媒体文件下载, 所以单列出来. 一定要处理.
*
* 通过的作法是: 如果是图片, 则 App 展示一张特别的表明未下载成功的图, 用户点击再次发起下载. 如果是语音,
* 则不必特别处理, 还是原来的图标展示. 用户点击时, SDK 发现语音文件在本地没有, 会再次发起下载.
*/
@optional
- (void)onReceiveMessageDownloadFailed:(JMSGMessage *)message;
实现onSendMessageResponse :
代理就能获取到当前发送的消失成功失败了
实现onReceiveMessage :
代理就能接收到别人(单聊、群组)发送的消息了 。
到这里简单的单聊、群聊就实现了。
实现收发 文字、图片、语音、自定义消息
在极光IM中文字、图片、语音、自定义消息。都有自己的类型定义 分别是JMSGTextContent
JMSGImageContent
JMSGVoiceContent
JMSGCustomContent
他们都间接或直接的继承与JMSGAbstractContent
发文字
发送文字用JMSGTextContent
进行实例化
- (instancetype)initWithText:(NSString *)text;
发图片
JMSGImageContent
- (nullable instancetype)initWithImageData:(NSData * JMSG_NONNULL)data;
发送图片是间接继承JMSGAbstractContent
的 它直接继承JMSGMediaAbstractContent
在JMSGMediaAbstractContent
中
@property(nonatomic, copy)JMSGMediaProgressHandler JMSG_NULLABLE uploadHandler;
用上属性获取到发送照片的进度 。后面的发送语音亦是用此方法获取到发送进度。
假如你需要UI展示发送图片、语音的进度可以将JMSGImageContent
JMSGVoiceContent
实例传递到cell
中获取进度来展示UI。
messageModel.VoiceContent.uploadHandler = ^(float percent, NSString *msgId) {
};
发语音
- (instancetype)initWithVoiceData:(NSData *)data
voiceDuration:(NSNumber *)duration;
发自定义消息
自定义消息在文字、图片、语音满足不了你的业务需求时就派上用场了。例如;发送名片、发送软件内的一些动态信息等。
注意:字典中的内容要和安卓兄弟协商好
- (instancetype)initWithCustomDictionary:(NSDictionary * JMSG_NULLABLE)customDict;
以上是各内容的实例化方法,鉴于各发送方法雷同。下面附上发送单聊文字的代码供理解。
以上方法得到实例后用`createSingleMessageWithContent `得到 `JMSGMessage `实例 就可以 `sendMessage :`即可发送。
JMSGTextContent *TextContent = [[JMSGTextContent alloc]initWithText:@"message"];
JMSGMessage * JMmessage = [JMSGMessage createSingleMessageWithContent:TextContent username:@"用户极光ID"];
[JMSGMessage sendMessage:JMmessage];//发送消息
接受消息
上面已经提到接受消息的方法 下面具体说明接受到消息后的处理。
当你用不同的Content
发送消息时极光内部自动区分内容类型
极光IM中消息类型定义如下:
typedef NS_ENUM(NSInteger, JMSGContentType) {
/// 不知道类型的消息
kJMSGContentTypeUnknown = 0,
/// 文本消息
kJMSGContentTypeText = 1,
/// 图片消息
kJMSGContentTypeImage = 2,
/// 语音消息
kJMSGContentTypeVoice = 3,
/// 自定义消息
kJMSGContentTypeCustom = 4,
/// 事件通知消息。服务器端下发的事件通知,本地展示为这个类型的消息展示出来
kJMSGContentTypeEventNotification = 5,
/// 文件消息
kJMSGContentTypeFile = 6,
/// 地理位置消息
kJMSGContentTypeLocation = 7,
/// 提示性消息
kJMSGContentTypePrompt = 8,
};
kJMSGContentTypeEventNotification 在下面聊天记录部分说明
我们拿到Message
后按要按类型操作就可以了。
以下是操作文本的代码示例 其他雷同
if (JIMmessage.contentType == kJMSGContentTypeText) {
//获取到类型然后对Message进行类型转换 获取到携带的内容进行展示即可
JMSGAbstractContent *content = JIMmessage.content;
JMSGTextContent *textContent = (JMSGTextContent *)content;
recMessage.text = textContent.text;
}
图片、语音是要进行下载操作的 获取到content
调用下载发法即可:
//下载语音
[weakmessageModel.VoiceContent voiceData:^(NSData *data, NSString *objectId, NSError *error) {
}];
//下载图片缩略图
[messageModel.ImgContent thumbImageData:^(NSData *data, NSString *objectId, NSError *error) {
}];
获取到的图片、语音内容 最好不要直接在VC中对其进行下载赋值,最好的是将图片、语音的content
传到Cell中在cell中对其操作
到这里收发各种消息就可以完成了。
上面说了一些功能的实现,下面开始说与UI相关的API方法、实现某些功能的操作
会话列表、聊天记录的展示
会话列表
会话是整个 IM 的核心. 所有的消息行为都是基于"会话"的.
当你创建单聊、群聊对话的时候,IM会建立起一个会话、并保存在本地。
会话最直白的体现就是 最近的聊天列表
获取全部会话的未读消息
注意:如果你的群组设置为免打扰,该群组的聊天未读数是不计入此统计的。
/*!
* @abstract 获取当前所有会话的未读消息的总数
*
* @discussion 获取所有会话未读消息总数
*/
+ (NSNumber *)getAllUnreadCount;
如果想获取已开启免打扰群组的未读数,要获取对应的群组会话,在其属性中就有
* @abstract 未读数
* @discussion 有新消息来时, SDK 会对未读数自动加 1
*/
@property(nonatomic, strong, readonly) NSNumber * JMSG_NULLABLE unreadCount;
会话还有一个 target
属性需要根据会话类型转型。单聊时转型为 JMSGUser,群聊时转型为 JMSGGroup。但是极光文档中不见建议我们在会话列表上用此属性
注意: 在会话列表上, 请不要使用此属性, 否则有性能问题. 只在进入聊天界面(单个会话) 时使用此属性,进入会话(聊天界面)后, 访问会话对象的各种信息, 包括群聊的群组成员, 都应使用此属性, 而没有必要再通过接口查询 UserInfo / GroupInfo.
但是会话列表是需要区别群聊、单聊来展示头像和一些信息的我们怎么办呢?
1、忽略极光IM的友情提示继续使用target属性
2、利用latestMessage
属性 区别、获取信息
利用 latestMessage 属性区别、获取信息
latestMessage
是JMSGMessage
类型。JMSGMessage
有如下几个属性
/*!
* @abstract 消息来源用户
* - 收到的消息, fromUser 是发出消息的对方.
* 单聊是聊天对象, 也与当前会话目标用户一致 [JMSGConversation target],
* 群聊是该条消息的发送用户.
* - 发出的消息: fromUser 是我自己.
*/
@property(nonatomic, strong, readonly) JMSGUser *fromUser;
/*!
* @abstract 消息的内容类型
*/
@property(nonatomic, assign, readonly) JMSGContentType contentType;
/*!
* @abstract 消息发送目标
* - 收到的消息,target 就是我自己。
* - 发送的消息,target 是我的聊天对象。
* 单聊是对方用户;
* 群聊是聊天群组, 也与当前会话的目标一致 [JMSGConversation target]
*/
@property(nonatomic, strong, readonly) id target;
利用这三个属性我们就可以区分、获取我们需要的数据了,这里就不做赘述了
聊天记录
聊天记只要调用API即可获取到消息(API获取的记录都是本地的)
/*!
* @abstract 同步分页获取最新的消息
*
* @param offset 开始的位置。nil 表示从最初开始。
* @param limit 获取的数量。nil 表示不限。
*
* @return 返回消息列表(数组)。数组成员的类型是 JMSGMessage*
*
* @discussion 排序规则是:最新
*
* 参数举例:
*
* - offset = nil, limit = nil,表示获取全部。相当于 allMessages。
* - offset = nil, limit = 100,表示从最新开始取 100 条记录。
* - offset = 100, limit = nil,表示从最新第 100 条开始,获取余下所有记录。
*/
- (NSArray JMSG_GENERIC(__kindof JMSGMessage *) *)messageArrayFromNewestWithOffset:(NSNumber *JMSG_NULLABLE)offset limit:(NSNumber *JMSG_NULLABLE)limit;
/*!
* @abstract 异步获取所有消息记录
*
* @param handler 结果回调。正常返回时 resultObject 类型为 NSArray,数据成员类型为 JMSGMessage。
*
* @discussion 排序规则:最新
*/
- (void)allMessages:(JMSGCompletionHandler)handler;
可能会有这样的需求 ,在群组聊天的时候、添加某个人进群、踢出某个人出群都要在聊天界面有一个提示。
其实这些群组的动态IM都有下发一个消息类型为kJMSGContentTypeEventNotification
的消息给我们给我们
。在JMSGNotificationEvent.h
文件中即可看到其属性值 获取展示即可。
消息发送失败在聊天界面展示红色的感叹号
刚接到这个需求的时候真的是有点懵!!!
因为极光IM的监听消息发送是全局性的,难道要在每一个Cell中都写一个监听代理嘛?这个想法听起来有点扯。但是极光IM没有针对JMSGMessage
获取到消息的发送回调。
然后我把极光IM,OC、Swift版本的Demo都看了一遍,极光Demo大致思路是:
创建一个数组把获取到的历史消息、后面发送的消息都放在数组里。一旦消息发送失败 ,就取到数组中对应消息的位置,利用这个位置再去获取到对应的Cell然后去更新UI。
以上是我对极光Demo的理解如有错误请纠正,勿喷!
不过我没有按照这种做法去做,因为我时间上来不及。
下面是我的做法:
//发送消息 成功、失败的回调代理
- (void)onSendMessageResponse:(JMSGMessage *)message error:(NSError *)error
{
}
上方法返回message
error
俩个属性 :
属性error:当error
为空的时候表示消息发送成功,反之则发送失败。发送失败对应也会有一个error.code
每个code对应的意思在极光官网就可查询。
属性message:message
是JMSGMessage
类型的,其有msgId
serverMessageId
/*!
* 消息ID:这个ID是本地存数据库生成的ID,不是服务器端下发时的ID。
*/
@property(nonatomic, strong, readonly) NSString *msgId;
/*!
* @abstract 服务器端下发的消息ID.
* @discussion 一般用于与服务器端跟踪消息.
*/
@property(nonatomic, strong, readonly) NSString * JMSG_NULLABLE serverMessageId;
俩个ID有个区别 msgId
在调用创建JMSGMessage
实例的时候就分配了,serverMessageId
只有消息发送到后台才会分配。
所以我在消息发送失败的时候发了一个通知携带msgId
,在Cell中接受通知。如果通知接送到的ID与我在外面传进来的Id相同则刷新界面UI展示红点。
- (void)onSendMessageResponse:(JMSGMessage *)message error:(NSError *)error
{
if (error) {
[[NSNotificationCenter defaultCenter]postNotificationName:@"MessageSendFaild" object:message.msgId];
}
}
//Cell中的放法
//添加接受通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(MessageSendFaild:) name:@"MessageSendFaild" object:nil];
//接受到通知的操作
- (void)MessageSendFaild:(NSNotification *)Notification
{
if ([[Utility strRelay:Notification.object] isEqualToString:[Utility strRelay:self.messageModel.MSGMessage.msgId]]) {
_faildBtn.hidden = NO;
}
}
以上处于聊天界面的时候是可行的。 但是如果退出重进获取历史记录就会有问题 ,下面将处理这部分问题。
每一条JMSGMessage
都包含一个发送状态status
只要判断发送状态来展示UI即可;
/*!
* 消息状态
*/
typedef NS_ENUM(NSInteger, JMSGMessageStatus) {
/// 发送息创建时的初始状态
kJMSGMessageStatusSendDraft = 0,
/// 消息正在发送过程中. UI 一般显示进度条
kJMSGMessageStatusSending = 1,
/// 媒体类消息文件上传失败
kJMSGMessageStatusSendUploadFailed = 2,
/// 媒体类消息文件上传成功
kJMSGMessageStatusSendUploadSucceed = 3,
/// 消息发送失败
kJMSGMessageStatusSendFailed = 4,
/// 消息发送成功
kJMSGMessageStatusSendSucceed = 5,
/// 接收中的消息(还在处理)
kJMSGMessageStatusReceiving = 6,
/// 接收消息时自动下载媒体失败
kJMSGMessageStatusReceiveDownloadFailed = 7,
/// 接收消息成功
kJMSGMessageStatusReceiveSucceed = 8,
};
以上就是消息消息失败展示红点的方法。如果想获取消息为什么发送失败可以获取error.code
与极光后台的错误码对比
803005
您不是群成员
803004
发送的目标不存在(如:群组已解散)
服务端错误码传送门