iOS 极光IM 集成之旅

刚开始码项目的时候,我是非常排斥使用极光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 属性区别、获取信息

latestMessageJMSGMessage类型。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:messageJMSGMessage类型的,其有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 发送的目标不存在(如:群组已解散)
服务端错误码传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354