极光IM-JMessager使用总结

前言

对于即时通讯来说,这是一个大的功能,一直以来也没怎么实际开发接触过,自研IM对一个非社交通讯平台的公司而言,是不必要的,一般都采用第三方IM框架,正好咋公司最近需要一个在电商环境下用户和商家之间需要一个简单的聊天功能,考虑到APP推送采用的是极光推送,那么IM很自然的使用了JMessager了。今天就和大家分享一下使用JMessager的一些细节处理或者一些坑,给后续接入者一点预先了解吧。

需要了解的基础

1.极光IM使用首先要注册appkey ,这个在极光开发者网站上新建一个,就会自动生成一个。
需要注意的是:如果您的app之前使用过极光推送的话,那么直接使用极光推送appkey即可,不必要再为IM注册一个新的key了,因为其实他们是一家的嘛,极光IM就是极光推送充话费送的~~
2.如果您需要集成到您公司旗下不同的2个APP之间聊天的话,就要使用垮APP发消息的api, 看它的JMessager API即可,就是使用带appkey参数,这个appkey就是极光平台appkey, 注意:是要发送消息的对象所在的APP的key
3.JMessager iOS的SDK 中设置方法已读:setMessageHaveRead 此方法有bug, 安卓设备发消息给iOS设备上,iOS设备设置此方法将消息已读无效,安卓设备收不到已读回执。安卓这边确实设置了需要已读回执为true,但就是收不到回执。不知道是不是我代码问题,谁解决了,可以告诉我一下。
4.消息通知栏的内容可以自定义,方法是在发送消息的时候可以设置一个options,这个option里面可以通知栏的style内容,消息回执也是在这个options里面开启。
5.据安卓同事反应,安卓端自定义类型的消息,是不计入消息未读个数里面的。这个有点坑!

集成和使用

1.集成:没什么好说的,pod JMessager即可,然后appdelegate注册一下。
2.使用:极光iOS开发文档说的还是很清楚了,需要注意一点的就是JMGMessager类下面 haveRead这个属性只针对接收方而言,而且只存在本地
3.聊天UI代码设计:
消息Message具体分很多种,文字、语音、定位、图片、视频、自定义消息等等,但有个基本的消息UI:发送者头像、昵称、时间、回执消息提示等,这些都是跟具体消息无关的UI,所以,我是设计了一个BaseMessageCell , 然后具体TextMessageCell\VoiceMessageCell继承BaseMessageCell,这样每个具体消息cell专注内容的布局显示即可,其他通用的组件由baseMessageCell处理。

UI布局代码结构

底部输入控件单独抽离出来:InputBar.跟业务无关的控件需要解耦出来。

4.聊天消息持有的数组轻量化:
整个聊天UITableView的数据源list 的元素只存msgId, message消息体用一个全局的字典NSDictionary.
为啥用字典+nsarray, 而不直接用一个message的大数组呢?
因为考虑到本地发出去的消息成功后要用新的服务器message替换本地message数据源,那么这里为了便于快速找到数组里这条message, 如果用message大数组需要循环遍历,找到msgId一样的很耗时,而dictionary的有点是通过key访问的,有点是遍历快速。对于聊天消息越来越多,就是要考虑性能了,看中了dictionary基于hash快速遍历的特性,而采用了这种方式。

#pragma mark - 创建会话 -
- (void)createConversation {
    [self showHUD];
    [KWJMessager createConversationUsername:self.userName
                                loginIfNeed:YES completed:^(id resultObject, NSError *error) {
        [self HidenHUD];
        if (!error) {
            //创建会话成功,准备聊天环境
            self.conversation = resultObject;
            self.navigationView.titleLabel.text = self.conversation.title;
            //监听会话消息
            [JMessage addDelegate:self withConversation:self.conversation];
            //拉取最新消息
            [self fectchLastedMessages];
        }else {
            //创建会话失败
            NSString *tip = [NSString stringWithFormat:@"创建会话失败:%@",@(error.code)];
            makeToast(tip);
        }
    }];
}
#pragma mark - JMessage监听方法 -
//发送消息回调
- (void)onSendMessageResponse:(JMSGMessage *)message error:(NSError *)error {
    if (message) {
        self.offset ++;
        //替换本地message数据源
        [self.allMessagesDic setObject:message forKey:message.msgId];
        NSInteger row = [self.allMessagesIds indexOfObject:message.msgId];
        [self.messageTableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:row inSection:0]]
                                     withRowAnimation:UITableViewRowAnimationNone];
        [self scrollToEnd:YES];
    }else {
        //发送失败
        NSString *tip = [NSString stringWithFormat:@"发送失败:%@",@(error.code)];
        makeToast(tip);
    }
}
//接收到消息
- (void)onReceiveMessage:(JMSGMessage *)message error:(NSError *)error {
    if (message) {
        //往页面追加一条消息
        self.offset ++;
        [self appendOneMessage:message];
        [self addImageMessageIfNeed:message localImage:nil reverse:NO];
        if (message.contentType != kJMSGContentTypeVoice) {
            //非语音消息,立马设置已读
            [message setMessageHaveRead:^(id resultObject, NSError *error) {}];
            //清除会话角标
            [self.conversation clearUnreadCount];
        }
    }
}

//消息回执
- (void)onReceiveMessageReceiptStatusChangeEvent:(JMSGMessageReceiptStatusChangeEvent *)receiptEvent {
    NSMutableArray *paths = [NSMutableArray array];
    for (JMSGMessage *msg in receiptEvent.messages) {
        JMSGMessage *old = [self.allMessagesDic objectForKey:msg.msgId];
        [old updateFlag:@(YES)];//标记已读
        NSInteger i = -1;
        for (NSString *msgid in self.allMessagesIds) {
            if ([msgid isEqualToString:msg.msgId]) {
                i = [self.allMessagesIds indexOfObject:msgid];
                break;
            }
        }
        if (i >= 0 ) {
            NSIndexPath *indexPaths = [NSIndexPath indexPathForRow:i inSection:0];
            [paths addObject:indexPaths];
        }
    }
    [self.messageTableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationNone];
    
}
//滑至底部
- (void)scrollToEnd:(BOOL)animated {

    NSInteger rows = [self.messageTableView numberOfRowsInSection:0];
    rows = MIN(rows, self.allMessagesIds.count);
    if (rows > 0) {
        [self.messageTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:
                                                       rows-1 inSection:0]
                                     atScrollPosition:UITableViewScrollPositionBottom
                                             animated:animated];
    }
    
}

关于UITableViewCell委托的代码:(使用基类,结构清晰,代码简洁)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   NSString *msgId = [self.allMessagesIds safeObjectAtIndex:indexPath.row];
   JMSGMessage *message = [self.allMessagesDic objectForKey:msgId];
   KWChatBaseCell *cell = nil;
   Weakify(self);
   if ([message.content isKindOfClass:[JMSGTextContent class]]) {
       cell = [tableView dequeueReusableCellWithIdentifier:@"text"];
   }else if ([message.content isKindOfClass:[JMSGImageContent class]]) {
       cell = [tableView dequeueReusableCellWithIdentifier:@"image"];
   }else if ([message.content isKindOfClass:[JMSGLocationContent class]]) {
       cell = [tableView dequeueReusableCellWithIdentifier:@"location"];
   }else if ([message.content isKindOfClass:[JMSGVoiceContent class]]) {
       cell = [tableView dequeueReusableCellWithIdentifier:@"voice"];
   }else if ([message.content isKindOfClass:[JMSGCustomContent class]]) {
       KWChatGoodsLinkCell *linkCell = [tableView dequeueReusableCellWithIdentifier:@"link"];
       linkCell.message = message;
       linkCell.actionBlock = ^{
           #pragma mark - 发送服务链接
           [weakself sendGoodsLinkMessage:NO];
       };
       return linkCell;
   }
   cell.message = message;
   cell.actionBlock = ^(ActionType type){
       if (type == ActionTypeLookDetail) {
           [weakself playContentMessage:message indexPath:indexPath];
       }else {
           //消息重发
           [weakself resendMessage:message];
       }
   };
   return cell;
}

最后贴一下的使用截图:


image.png

image.png

image.png

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

推荐阅读更多精彩内容