環(huán)信官網(wǎng):http://www.easemob.com/
開(kāi)發(fā)文檔:http://docs.easemob.com/im/start
一贮泞、前言
在自己做的第三個(gè)項(xiàng)目中畴栖,接觸到了環(huán)信邮丰,原來(lái)沒(méi)有對(duì)環(huán)信接入有些了解,導(dǎo)致在項(xiàng)目中使用遇到了很多的坑范嘱,最新環(huán)信V3.3.7版本也對(duì)iPhone X進(jìn)行了適配趣效,現(xiàn)在自己也有點(diǎn)空閑時(shí)間,對(duì)環(huán)信集成進(jìn)行整理艾帐,方便相關(guān)功能快速開(kāi)發(fā)乌叶,下面開(kāi)始介紹環(huán)信集成與使用吧。
二柒爸、文章介紹內(nèi)容目錄
1准浴、集成主要步驟簡(jiǎn)介
2、項(xiàng)目集成HyphenateSDK
3捎稚、項(xiàng)目集成EaseUI
4乐横、項(xiàng)目具體使用
5、常見(jiàn)問(wèn)題解決
1今野、集成主要步驟簡(jiǎn)介
集成環(huán)信服務(wù)主要有以下三個(gè)步驟
-
1.1 注冊(cè)
注冊(cè)開(kāi)發(fā)者賬號(hào)并創(chuàng)建應(yīng)用
-
1.2 服務(wù)器端集成(REST API)
-
1.3 客戶端集成
集成文檔:
Android SDK 集成
iOS SDK 集成
Linux SDK 集成
Web IM SDK 集成
集成用戶和好友體系
2葡公、項(xiàng)目集成HyphenateSDK
-
2.1 集成 iOS SDK 前的準(zhǔn)備工作
如果你需要用到環(huán)信推送功能,需要按照環(huán)信制作并上傳推送證書申請(qǐng)配置證書条霜,不需要?jiǎng)t跳過(guò)此步驟匾南。
-
2.2 SDK重要組成部分集成前需了解
SDK_Core: 為核心的消息同步協(xié)議實(shí)現(xiàn),完成與服務(wù)器之間的信息交換蛔外。
SDK: 是基于核心協(xié)議實(shí)現(xiàn)的完整的 IM 功能蛆楞,實(shí)現(xiàn)了不同類型消息的收發(fā)、會(huì)話管理夹厌、群組豹爹、好友、聊天室等功能矛纹。
EaseUI: 是一組 IM 相關(guān)的 UI 控件臂聋,旨在幫助開(kāi)發(fā)者快速集成環(huán)信 SDK。
EMClient: 是 SDK 的入口,主要完成登錄孩等、退出艾君、連接管理等功能。也是獲取其他模塊的入口肄方。
EMChatManager: 管理消息的收發(fā)冰垄,完成會(huì)話管理等功能。
EMContactManager: 負(fù)責(zé)好友的添加刪除权她,黑名單的管理虹茶。
EMGroupManager: 負(fù)責(zé)群組的管理,創(chuàng)建隅要、刪除群組蝴罪,管理群組成員等功能。
EMChatroomManager: 負(fù)責(zé)聊天室的管理步清。
上面這些內(nèi)容要门,可以對(duì)環(huán)信SDK組成有一個(gè)大致的理解,在最開(kāi)始接入環(huán)信時(shí)候廓啊,自己沒(méi)有看相關(guān)介紹文檔欢搜,導(dǎo)致自己使用工程中遇到很多坑。
2.3 通過(guò) Cocoapods導(dǎo)入
-
2.3.1 不包含實(shí)時(shí)語(yǔ)音版本 SDK(HyphenateLite)崖瞭,引用時(shí) #import <HyphenateLite/HyphenateLite.h>
pod 'HyphenateLite'
-
2.3.2 包含實(shí)時(shí)語(yǔ)音版本 SDK(Hyphenate),引用時(shí) #import <Hyphenate/Hyphenate.h>
pod 'Hyphenate'
2.4 手動(dòng)集成
-
2.4.1 SDK下載
-
2.4.2 將ios_IM_sdk_V3.3.7文件下的SDK文件夾拖入到工程撑毛,如下圖所示:
-
2.4.3 添加依賴庫(kù)
- 不包含實(shí)時(shí)語(yǔ)音系統(tǒng)依賴庫(kù)
CoreMedia.framework
AudioToolbox.framework
AVFoundation.framework
MobileCoreServices.framework
ImageIO.framework
ibc++.tbd
libz.tbd
libstdc++.6.0.9.tbd
libsqlite3.tbd
- 包含實(shí)時(shí)語(yǔ)音系統(tǒng)依賴庫(kù)
CoreMedia.framework
AudioToolbox.framework
AVFoundation.framework
MobileCoreServices.framework
ImageIO.framework
libc++.tbd
libz.tbd
libstdc++.6.0.9.tbd
ibsqlite3.tbd
libiconv.tbd
如果使用的是 xcode7以下书聚,后綴為dylib。
Parse.framework藻雌、Bolts.framework: Demo 中的用戶信息存儲(chǔ)在 Parse雌续,這兩個(gè)庫(kù)是 Parse 所需要的庫(kù),開(kāi)發(fā)者如果沒(méi)用 Parse 存儲(chǔ)胯杭,不要復(fù)制到自己項(xiàng)目中驯杜。
-
2.4.4 因?yàn)镠yphenate是動(dòng)態(tài)庫(kù),需要在Build Phase中 Embedded Binaries添加Hyphenate.framework做个,如下圖所示:
- 到這里鸽心,SDK已經(jīng)導(dǎo)入到項(xiàng)目中了,
commad+R
運(yùn)行下工程居暖,沒(méi)有報(bào)錯(cuò)說(shuō)明已成功集成顽频。- 這里不得不提醒下自己,在第一次接入環(huán)信的時(shí)候沒(méi)有認(rèn)真閱讀環(huán)信集成文檔太闺,沒(méi)有添加Embedded Binaries糯景,導(dǎo)致能夠正常初始化和登錄,但是不能夠正常接收消息,自己找了好久也沒(méi)有發(fā)現(xiàn)問(wèn)題所在蟀淮。還是怪自己閱讀文檔不認(rèn)真最住,有經(jīng)驗(yàn)的同事很快幫我找到問(wèn)題了。
3怠惶、項(xiàng)目集成EaseUI
具體使用詳情涨缚,請(qǐng)戳:EaseUI使用指南
-
3.1 EaseUI支持pod導(dǎo)入
//Pod集成EaseUI時(shí),會(huì)同時(shí)通過(guò)Pod集成SDK
//對(duì)應(yīng)Hyphenate SDK(sdk包含實(shí)時(shí)音視頻)
pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git'
//對(duì)應(yīng)HyphenateLite SDK(sdk不包含實(shí)時(shí)音視頻)
pod 'EaseUILite', :git =>'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git'
// 指定版本甚疟,可以在后面添加tag仗岖,例如:導(dǎo)入最新V3.3.7
pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git', :tag => ‘3.3.7’
項(xiàng)目中沒(méi)有用pod方式導(dǎo)入,如果對(duì)UI沒(méi)有太多自定義要求览妖,可以用pod方式導(dǎo)入轧拄,方便版本管理,如果EaseUI不能滿足項(xiàng)目需求讽膏,這時(shí)就要用手動(dòng)導(dǎo)入方式了檩电。不知道pod導(dǎo)入有坑沒(méi)有,哈哈??府树。
-
3.2 手動(dòng)導(dǎo)入
-
3.2.1 EaseUI依賴三方庫(kù)
MWPhotoBrowser:圖片處理庫(kù)俐末,瀏覽顯示
MJRefresh:用于頁(yè)面刷新
MBProgressHUD:用于提示加載刷新
libopencore-amrnb.a、libopencore-amrwb.a:用于 amr 與 wav 之間的轉(zhuǎn)換
EMSDWebImage(SDWebImage):圖片下載
-
3.2.2 對(duì)EaseUI文件夾文件進(jìn)行整理
- 如果不想使用v3.3.7中的三方依賴庫(kù)(比如說(shuō)MJRefresh庫(kù)更新過(guò)后適配了iPhone X)奄侠,我們想通過(guò)Cocoapods導(dǎo)入相關(guān)依賴庫(kù)卓箫,方便項(xiàng)目對(duì)三方庫(kù)管理。這時(shí)需要對(duì)先關(guān)文件進(jìn)行修改垄潮,但改動(dòng)的也并不是很多烹卒。整理過(guò)后EaseUI目錄如下圖所示:
屏幕快照 2018-01-11 下午9.54.04.png -
3.2.3 這時(shí)依賴庫(kù)通過(guò)Cocoapods導(dǎo)入,在引用到這些庫(kù)頭文件的地方改為
import <頭文件名>
就好了弯洗,方法調(diào)用修改下:
pod 'MJRefresh','3.1.15.1'
pod 'MBProgressHUD','1.1.0'
pod 'SDWebImage','4.2.3'
-
3.2.4 創(chuàng)建一個(gè)PCH文件旅急,導(dǎo)入EaseUI頭文件:
#ifdef __OBJC__
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "EaseUI.h"
#endif
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
#endif /* PrefixHeader_pch */
- 在EaseUI.h文件做如下修改:
//#import "UIImageView+EMWebCache.h"
#import <UIImageView+WebCache.h>
- 最新SDWebImage下載圖片方法名修改一下:
//舊版本
downloadImageWithURL:
//新版本V4.2.3
loadImageWithURL:
整理好的EaseUI最終文件請(qǐng)戳:
V3.3.7 EaseUI
-
3.2.5 對(duì)V3.3.7Demo中聊天控制器和會(huì)話列表控制器進(jìn)行整理,注釋不必要的文件引用和代碼牡整。我也將整理好的文件貼出來(lái)吧藐吮,請(qǐng)戳:
4、項(xiàng)目具體使用
可以封裝一個(gè)工具類來(lái)管理Hyphenate初始化逃贝、登錄等相關(guān)操作谣辞。
-
4.1 Hyphenate初始化
- (void)initHyphenateSDK {
EMOptions *options = [EMOptions optionsWithAppkey:@""];
// options.apnsCertName = kEasemobSDKPushName;
EMError *error = nil;
error = [[EMClient sharedClient] initializeSDKWithOptions:options];
if (!error) {
NSLog(@"環(huán)信初始化成功");
}
/** < 注冊(cè)通知 > */
[self registerNoti];
}
-
4.2 登錄&退出登錄
- (void)wb_hyphenateLoginSuccess:(void (^)(void))success failure:(void (^)(void))failure {
/** <<
用戶調(diào)用了 SDK 的登出動(dòng)作;
用戶在別的設(shè)備上更改了密碼沐扳,導(dǎo)致此設(shè)備上自動(dòng)登錄失斄氏小;
用戶的賬號(hào)被從服務(wù)器端刪除迫皱;
用戶從另一個(gè)設(shè)備登錄歉闰,把當(dāng)前設(shè)備上登錄的用戶踢出辖众。
> */
BOOL isAutoLogin = [EMClient sharedClient].options.isAutoLogin;
if (!isAutoLogin) {
EMError *error = [[EMClient sharedClient] loginWithUsername:@"" password:@""];
if (!error) {
if (success) {
success();
}
/** < 設(shè)置是否自動(dòng)登錄 > */
[[EMClient sharedClient].options setIsAutoLogin:YES];
NSLog(@"環(huán)信登錄成功");
}else {
if (failure) {
failure();
}
NSLog(@"環(huán)信登錄失敗");
}
}
}
- (void)wb_hyphenateLogoutSuccess:(void (^)(void))success failure:(void (^)(void))failure {
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"環(huán)信退出成功");
if (success) {
success();
}
}else {
if (failure) {
failure();
}
NSLog(@"環(huán)信退出失敗");
}
}
-
4.3 自動(dòng)登錄
- (void)wb_applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)wb_applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
-
4.4 跳轉(zhuǎn)聊天界面
/** <
單聊:EMConversationTypeChat
Chatter:聊天對(duì)象用戶名
> */
WBChatViewController *vc = [[WBChatViewController alloc]initWithConversationChatter:@"" conversationType:EMConversationTypeChat];
[self.navigationController pushViewController:vc animated:YES];
-
4.5 用戶信息解決方案
-
4.5.1 環(huán)信官方也有相應(yīng)的解決方案:昵稱和頭像的顯示與更新
主要有以下兩種:
從APP服務(wù)器獲取昵稱和頭像
從消息擴(kuò)展中獲取昵稱和頭像
-
4.5.2 我在項(xiàng)目中采用的是從消息擴(kuò)展中獲取昵稱和頭像,下面開(kāi)始介紹我的實(shí)現(xiàn)方案吧和敬。
會(huì)話列表處理凹炸,在EaseConversationListViewController.m中的refreshAndSortView加載會(huì)話列表方法,對(duì)發(fā)送方消息擴(kuò)展字段進(jìn)行緩存昼弟,可以用數(shù)據(jù)庫(kù)或者是歸檔等方法啤它,我采用的是歸檔,保存擴(kuò)展消息模型舱痘。
//緩存發(fā)送方用戶信息到本地
-(void)refreshAndSortView
{
if ([self.dataArray count] > 1) {
if ([[self.dataArray objectAtIndex:0] isKindOfClass:[EaseConversationModel class]]) {
NSArray* sorted = [self.dataArray sortedArrayUsingComparator:
^(EaseConversationModel *obj1, EaseConversationModel* obj2){
EMMessage *message1 = [obj1.conversation latestMessage];
EMMessage *message2 = [obj2.conversation latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];
[self.dataArray removeAllObjects];
[self.dataArray addObjectsFromArray:sorted];
for (EMConversation *conversation in self.dataArray) {
/** < 緩存發(fā)送消息者信息 > */
/** < 收到的對(duì)方發(fā)送的最后一條消息变骡,也是會(huì)話里的最新消息 > */
EMMessage *lastReceiveMessage = [conversation lastReceivedMessage];
if (lastReceiveMessage) {
NSDictionary *extDic = lastReceiveMessage.ext;
HyhenateUserModel *user = [HyhenateUserModel yy_modelWithDictionary:extDic];
BOOL res = [[WBConversationManager shareManager] wb_archiveObjectToFileWithConversation_ID:conversation.conversationId archiveData:user];
if (res) {
NSLog(@"更新聊天對(duì)象信息成功 ");
}else {
NSLog(@"更新聊天對(duì)象信息失敗 ");
}
}
}
}
}
[self.tableView reloadData];
}
//在WBConversationListViewController.m讀取緩存
pragma mark ------ < EaseConversationListViewControllerDataSource > ------
#pragma mark
- (id<IConversationModel>)conversationListViewController:(EaseConversationListViewController *)conversationListViewController
modelForConversation:(EMConversation *)conversation
{
EaseConversationModel *model = [[EaseConversationModel alloc] initWithConversation:conversation];
if (model.conversation.type == EMConversationTypeChat) {
// if ([[RobotManager sharedInstance] isRobotWithUsername:conversation.conversationId]) {
// model.title = [[RobotManager sharedInstance] getRobotNickWithUsername:conversation.conversationId];
// } else {
// UserProfileEntity *profileEntity = [[UserProfileManager sharedInstance] getUserProfileByUsername:conversation.conversationId];
// if (profileEntity) {
// model.title = profileEntity.nickname == nil ? profileEntity.username : profileEntity.nickname;
// model.avatarURLPath = profileEntity.imageUrl;
// }
// }
HyhenateUserModel *user = [[WBConversationManager shareManager] wb_unarchiveObjectWithConversation_ID:conversation.conversationId];
if (user) {
model.title = user.nick;
model.avatarURLPath = user.avatar;
}
} else if (model.conversation.type == EMConversationTypeGroupChat) {
NSString *imageName = @"groupPublicHeader";
if (![conversation.ext objectForKey:@"subject"])
{
NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups];
for (EMGroup *group in groupArray) {
if ([group.groupId isEqualToString:conversation.conversationId]) {
NSMutableDictionary *ext = [NSMutableDictionary dictionaryWithDictionary:conversation.ext];
[ext setObject:group.subject forKey:@"subject"];
[ext setObject:[NSNumber numberWithBool:group.isPublic] forKey:@"isPublic"];
conversation.ext = ext;
break;
}
}
}
NSDictionary *ext = conversation.ext;
model.title = [ext objectForKey:@"subject"];
imageName = [[ext objectForKey:@"isPublic"] boolValue] ? @"groupPublicHeader" : @"groupPrivateHeader";
model.avatarImage = [UIImage imageNamed:imageName];
}
return model;
}
//右滑刪除本地緩存:EaseConversationListViewController.m->deleteCellAction:
- (void)deleteCellAction:(NSIndexPath *)aIndexPath
{
EaseConversationModel *model = [self.dataArray objectAtIndex:aIndexPath.row];
[[EMClient sharedClient].chatManager deleteConversation:model.conversation.conversationId isDeleteMessages:YES completion:nil];
/** < 刪除用戶信息 > */
[[WBConversationManager shareManager] removeArchiveDataAtFilePath:model.conversation.conversationId];
[self.dataArray removeObjectAtIndex:aIndexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:aIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
- 聊天界面處理
//添加用戶信息擴(kuò)展,在EaseMessageViewController.m->_sendMessage方法中添加擴(kuò)展信息
- (void)_sendMessage:(EMMessage *)message
isNeedUploadFile:(BOOL)isUploadFile
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}
NSDictionary *ext = message.ext;
if (ext == nil) {
/** <
發(fā)送消息最終執(zhí)行的方法芭逝,在這里構(gòu)造自己信息擴(kuò)展塌碌,通常用戶信息在本地都有保存,這里只是測(cè)試
> */
NSDictionary *userDict = @{@"nick" : @"test",
@"avatar" : @"imageFile"
};
message.ext = ext;
}
__weak typeof(self) weakself = self;
if (!([EMClient sharedClient].options.isAutoTransferMessageAttachments) && isUploadFile) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:NSLocalizedString(@"message.autoTransfer", @"Please customize the transfer attachment method") delegate:nil cancelButtonTitle:NSLocalizedString(@"sure", @"OK") otherButtonTitles:nil, nil];
[alertView show];
} else {
[self addMessageToDataSource:message
progress:nil];
[[EMClient sharedClient].chatManager sendMessage:message progress:^(int progress) {
if (weakself.dataSource && [weakself.dataSource respondsToSelector:@selector(messageViewController:updateProgress:messageModel:messageBody:)]) {
[weakself.dataSource messageViewController:weakself updateProgress:progress messageModel:nil messageBody:message.body];
}
} completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}
}
//擴(kuò)展信息展示旬盯,在WBChatViewController.m->messageViewController:修改
#pragma mark ------ < EaseMessageViewControllerDataSource > ------
#pragma mark
/** << 設(shè)置頭像台妆、昵稱 > */
- (id<IMessageModel>)messageViewController:(EaseMessageViewController *)viewController
modelForMessage:(EMMessage *)message
{
id<IMessageModel> model = nil;
model = [[EaseMessageModel alloc] initWithMessage:message];
model.avatarImage = [UIImage imageNamed:@"EaseUIResource.bundle/user"];
// UserProfileEntity *profileEntity = [[UserProfileManager sharedInstance] getUserProfileByUsername:model.nickname];
// if (profileEntity) {
// model.avatarURLPath = profileEntity.imageUrl;
// model.nickname = profileEntity.nickname;
// }
if (model.isSender) {
//直接取出本地用戶信息
model.nickname = @"本地保存的用戶名";
model.avatarURLPath = @"本地用戶頭像地址";
}else {
NSDictionary *userDict = message.ext;
HyhenateUserModel *user = [HyhenateUserModel yy_modelWithDictionary:userDict];
model.nickname = user.nick;
model.avatarURLPath = user.avatar;
}
model.failImageName = @"imageDownloadFail";
return model;
}
代碼我已托管到碼云上了,詳情請(qǐng)戳:ManageHyphenateSDK
使用demo注意
- 因?yàn)榄h(huán)信SDK超過(guò)了100M胖翰,我對(duì)其進(jìn)行了忽略接剩,可以將HyphenateFullSDK.zip文件解壓縮重新導(dǎo)入一次。
- 導(dǎo)入三方庫(kù)萨咳,
cd 工程路徑
懊缺,執(zhí)行
pod install
完成上述兩個(gè)步驟,就能正常運(yùn)行工程啦~培他,運(yùn)行效果如下圖所示:
5鹃两、常見(jiàn)問(wèn)題解決
-
5.1 image not found
可參考標(biāo)題目錄2.4.4解決,或者
-
5.2 集成動(dòng)態(tài)庫(kù)上傳AppStore
由于 iOS 編譯的特殊性靶壮,為了方便開(kāi)發(fā)者使用怔毛,我們將 i386 x86_64 armv7 arm64 幾個(gè)平臺(tái)都合并到了一起员萍,所以使用動(dòng)態(tài)庫(kù)上傳appstore時(shí)需要將i386 x86_64兩個(gè)平臺(tái)刪除后腾降,才能正常提交審核
在SDK當(dāng)前路徑下執(zhí)行以下命令刪除i386 x86_64兩個(gè)平臺(tái)
bak文件是備份目錄,上傳appstore之后需要替換回bak目錄下的SDK
- 實(shí)時(shí)音視頻版本Hyphenate.framework
mkdir ./bak
cp -r Hyphenate.framework ./bak
lipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7
lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64
lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate
mv Hyphenate Hyphenate.framework/
- 不包含實(shí)時(shí)音視頻版本HyphenateLite.framework
mkdir ./bak
cp -r HyphenateLite.framework ./bak
lipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7
lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64
lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite
mv HyphenateLite HyphenateLite.framework/
三碎绎、總結(jié)
終于整理完成了螃壤,自己在寫Demo的過(guò)程中,由于
git reset
命令使用不當(dāng)筋帖,導(dǎo)致整理的文件全部撤銷了奸晴,頓時(shí)都無(wú)語(yǔ)了,不管怎樣日麸,最終還是整理完成了寄啼。在集成EaseUI的時(shí)候逮光,建議還是手動(dòng)集成吧,如果你需要對(duì)其中文件進(jìn)行更改墩划。Demo中導(dǎo)入的EaseUI涕刚,我并不是原樣導(dǎo)入的,如果你不想修改乙帮,可以直接將EaseUI導(dǎo)入到工程杜漠。由于能力有限,有些地方整理的不是很好察净,也可以自行修改驾茴,希望自己以后集成環(huán)信能少遇到一些坑,希望這篇文章也能對(duì)你有所幫助氢卡。