前言
对于即时通讯来说,这是一个大的功能,一直以来也没怎么实际开发接触过,自研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处理。
底部输入控件单独抽离出来: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
列表
网友评论