前言
對于即時通訊來說,這是一個大的功能,一直以來也沒怎么實際開發(fā)接觸過,自研IM對一個非社交通訊平臺的公司而言,是不必要的,一般都采用第三方IM框架,正好咋公司最近需要一個在電商環(huán)境下用戶和商家之間需要一個簡單的聊天功能,考慮到APP推送采用的是極光推送,那么IM很自然的使用了JMessager了。今天就和大家分享一下使用JMessager的一些細節(jié)處理或者一些坑,給后續(xù)接入者一點預先了解吧。
需要了解的基礎
1.極光IM使用首先要注冊appkey ,這個在極光開發(fā)者網(wǎng)站上新建一個,就會自動生成一個。
需要注意的是:如果您的app之前使用過極光推送的話,那么直接使用極光推送appkey即可,不必要再為IM注冊一個新的key了,因為其實他們是一家的嘛,極光IM就是極光推送充話費送的~~
2.如果您需要集成到您公司旗下不同的2個APP之間聊天的話,就要使用垮APP發(fā)消息的api, 看它的JMessager API即可,就是使用帶appkey參數(shù),這個appkey就是極光平臺appkey, 注意:是要發(fā)送消息的對象所在的APP的key。
3.JMessager iOS的SDK 中設置方法已讀:setMessageHaveRead 此方法有bug, 安卓設備發(fā)消息給iOS設備上,iOS設備設置此方法將消息已讀無效,安卓設備收不到已讀回執(zhí)。安卓這邊確實設置了需要已讀回執(zhí)為true,但就是收不到回執(zhí)。不知道是不是我代碼問題,誰解決了,可以告訴我一下。
4.消息通知欄的內(nèi)容可以自定義,方法是在發(fā)送消息的時候可以設置一個options,這個option里面可以通知欄的style內(nèi)容,消息回執(zhí)也是在這個options里面開啟。
5.據(jù)安卓同事反應,安卓端自定義類型的消息,是不計入消息未讀個數(shù)里面的。這個有點坑!
集成和使用
1.集成:沒什么好說的,pod JMessager即可,然后appdelegate注冊一下。
2.使用:極光iOS開發(fā)文檔說的還是很清楚了,需要注意一點的就是JMGMessager類下面 haveRead這個屬性只針對接收方而言,而且只存在本地。
3.聊天UI代碼設計:
消息Message具體分很多種,文字、語音、定位、圖片、視頻、自定義消息等等,但有個基本的消息UI:發(fā)送者頭像、昵稱、時間、回執(zhí)消息提示等,這些都是跟具體消息無關(guān)的UI,所以,我是設計了一個BaseMessageCell , 然后具體TextMessageCell\VoiceMessageCell繼承BaseMessageCell,這樣每個具體消息cell專注內(nèi)容的布局顯示即可,其他通用的組件由baseMessageCell處理。

底部輸入控件單獨抽離出來:InputBar.跟業(yè)務無關(guān)的控件需要解耦出來。
4.聊天消息持有的數(shù)組輕量化:
整個聊天UITableView的數(shù)據(jù)源list 的元素只存msgId, message消息體用一個全局的字典NSDictionary.
為啥用字典+nsarray, 而不直接用一個message的大數(shù)組呢?
因為考慮到本地發(fā)出去的消息成功后要用新的服務器message替換本地message數(shù)據(jù)源,那么這里為了便于快速找到數(shù)組里這條message, 如果用message大數(shù)組需要循環(huán)遍歷,找到msgId一樣的很耗時,而dictionary的有點是通過key訪問的,有點是遍歷快速。對于聊天消息越來越多,就是要考慮性能了,看中了dictionary基于hash快速遍歷的特性,而采用了這種方式。
#pragma mark - 創(chuàng)建會話 -
- (void)createConversation {
[self showHUD];
[KWJMessager createConversationUsername:self.userName
loginIfNeed:YES completed:^(id resultObject, NSError *error) {
[self HidenHUD];
if (!error) {
//創(chuàng)建會話成功,準備聊天環(huán)境
self.conversation = resultObject;
self.navigationView.titleLabel.text = self.conversation.title;
//監(jiān)聽會話消息
[JMessage addDelegate:self withConversation:self.conversation];
//拉取最新消息
[self fectchLastedMessages];
}else {
//創(chuàng)建會話失敗
NSString *tip = [NSString stringWithFormat:@"創(chuàng)建會話失?。?@",@(error.code)];
makeToast(tip);
}
}];
}
#pragma mark - JMessage監(jiān)聽方法 -
//發(fā)送消息回調(diào)
- (void)onSendMessageResponse:(JMSGMessage *)message error:(NSError *)error {
if (message) {
self.offset ++;
//替換本地message數(shù)據(jù)源
[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 {
//發(fā)送失敗
NSString *tip = [NSString stringWithFormat:@"發(fā)送失?。?@",@(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];
}
}
}
//消息回執(zhí)
- (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];
}
}
關(guān)于UITableViewCell委托的代碼:(使用基類,結(jié)構(gòu)清晰,代碼簡潔)
- (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 - 發(fā)送服務鏈接
[weakself sendGoodsLinkMessage:NO];
};
return linkCell;
}
cell.message = message;
cell.actionBlock = ^(ActionType type){
if (type == ActionTypeLookDetail) {
[weakself playContentMessage:message indexPath:indexPath];
}else {
//消息重發(fā)
[weakself resendMessage:message];
}
};
return cell;
}
最后貼一下的使用截圖:



