环信官网:http://www.easemob.com/
开发文档:http://docs.easemob.com/im/start
一、前言
在自己做的第三个项目中,接触到了环信,原来没有对环信接入有些了解,导致在项目中使用遇到了很多的坑,最新环信V3.3.7版本也对iPhone X进行了适配,现在自己也有点空闲时间,对环信集成进行整理,方便相关功能快速开发,下面开始介绍环信集成与使用吧。
二、文章介绍内容目录
1、集成主要步骤简介
2、项目集成HyphenateSDK
3、项目集成EaseUI
4、项目具体使用
5、常见问题解决
1、集成主要步骤简介
集成环信服务主要有以下三个步骤
-
1.1 注册
注册开发者账号并创建应用
-
1.2 服务器端集成(REST API)
集成用户和好友体系
-
1.3 客户端集成
集成文档:
Android SDK 集成
iOS SDK 集成
Linux SDK 集成
Web IM SDK 集成
集成用户和好友体系
2、项目集成HyphenateSDK
-
2.1 集成 iOS SDK 前的准备工作
如果你需要用到环信推送功能,需要按照环信制作并上传推送证书申请配置证书,不需要则跳过此步骤。
-
2.2 SDK重要组成部分集成前需了解
SDK_Core: 为核心的消息同步协议实现,完成与服务器之间的信息交换。
SDK: 是基于核心协议实现的完整的 IM 功能,实现了不同类型消息的收发、会话管理、群组、好友、聊天室等功能。
EaseUI: 是一组 IM 相关的 UI 控件,旨在帮助开发者快速集成环信 SDK。
EMClient: 是 SDK 的入口,主要完成登录、退出、连接管理等功能。也是获取其他模块的入口。
EMChatManager: 管理消息的收发,完成会话管理等功能。
EMContactManager: 负责好友的添加删除,黑名单的管理。
EMGroupManager: 负责群组的管理,创建、删除群组,管理群组成员等功能。
EMChatroomManager: 负责聊天室的管理。
上面这些内容,可以对环信SDK组成有一个大致的理解,在最开始接入环信时候,自己没有看相关介绍文档,导致自己使用工程中遇到很多坑。
2.3 通过 Cocoapods导入
-
2.3.1 不包含实时语音版本 SDK(HyphenateLite),引用时 #import
pod 'HyphenateLite'
-
2.3.2 包含实时语音版本 SDK(Hyphenate),引用时 #import
pod 'Hyphenate'
2.4 手动集成
-
2.4.1 SDK下载
官方最新SDK+Demo
HyphenateV3.3.7
-
2.4.2 将ios_IM_sdk_V3.3.7文件下的SDK文件夹拖入到工程,如下图所示:
-
2.4.3 添加依赖库
- 不包含实时语音系统依赖库
CoreMedia.framework
AudioToolbox.framework
AVFoundation.framework
MobileCoreServices.framework
ImageIO.framework
ibc++.tbd
libz.tbd
libstdc++.6.0.9.tbd
libsqlite3.tbd
- 包含实时语音系统依赖库
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 中的用户信息存储在 Parse,这两个库是 Parse 所需要的库,开发者如果没用 Parse 存储,不要复制到自己项目中。
-
2.4.4 因为Hyphenate是动态库,需要在Build Phase中 Embedded Binaries添加Hyphenate.framework,如下图所示:
- 到这里,SDK已经导入到项目中了,
commad+R
运行下工程,没有报错说明已成功集成。- 这里不得不提醒下自己,在第一次接入环信的时候没有认真阅读环信集成文档,没有添加Embedded Binaries,导致能够正常初始化和登录,但是不能够正常接收消息,自己找了好久也没有发现问题所在。还是怪自己阅读文档不认真,有经验的同事很快帮我找到问题了。
3、项目集成EaseUI
具体使用详情,请戳:EaseUI使用指南
-
3.1 EaseUI支持pod导入
//Pod集成EaseUI时,会同时通过Pod集成SDK
//对应Hyphenate SDK(sdk包含实时音视频)
pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git'
//对应HyphenateLite SDK(sdk不包含实时音视频)
pod 'EaseUILite', :git =>'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git'
// 指定版本,可以在后面添加tag,例如:导入最新V3.3.7
pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git', :tag => ‘3.3.7’
项目中没有用pod方式导入,如果对UI没有太多自定义要求,可以用pod方式导入,方便版本管理,如果EaseUI不能满足项目需求,这时就要用手动导入方式了。不知道pod导入有坑没有,哈哈。
-
3.2 手动导入
-
3.2.1 EaseUI依赖三方库
MWPhotoBrowser:图片处理库,浏览显示
MJRefresh:用于页面刷新
MBProgressHUD:用于提示加载刷新
libopencore-amrnb.a、libopencore-amrwb.a:用于 amr 与 wav 之间的转换
EMSDWebImage(SDWebImage):图片下载
-
3.2.2 对EaseUI文件夹文件进行整理
- 如果不想使用v3.3.7中的三方依赖库(比如说MJRefresh库更新过后适配了iPhone X),我们想通过Cocoapods导入相关依赖库,方便项目对三方库管理。这时需要对先关文件进行修改,但改动的也并不是很多。整理过后EaseUI目录如下图所示:
-
3.2.3 这时依赖库通过Cocoapods导入,在引用到这些库头文件的地方改为
import <头文件名>
就好了,方法调用修改下:
pod 'MJRefresh','3.1.15.1'
pod 'MBProgressHUD','1.1.0'
pod 'SDWebImage','4.2.3'
-
3.2.4 创建一个PCH文件,导入EaseUI头文件:
#ifdef __OBJC__
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#import
#import
#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
- 最新SDWebImage下载图片方法名修改一下:
//旧版本
downloadImageWithURL:
//新版本V4.2.3
loadImageWithURL:
整理好的EaseUI最终文件请戳:
V3.3.7 EaseUI
-
3.2.5 对V3.3.7Demo中聊天控制器和会话列表控制器进行整理,注释不必要的文件引用和代码。我也将整理好的文件贴出来吧,请戳:
WBChat
4、项目具体使用
可以封装一个工具类来管理Hyphenate初始化、登录等相关操作。
-
4.1 Hyphenate初始化
- (void)initHyphenateSDK {
EMOptions *options = [EMOptions optionsWithAppkey:@""];
// options.apnsCertName = kEasemobSDKPushName;
EMError *error = nil;
error = [[EMClient sharedClient] initializeSDKWithOptions:options];
if (!error) {
NSLog(@"环信初始化成功");
}
/** < 注册通知 > */
[self registerNoti];
}
-
4.2 登录&退出登录
- (void)wb_hyphenateLoginSuccess:(void (^)(void))success failure:(void (^)(void))failure {
/** <<
用户调用了 SDK 的登出动作;
用户在别的设备上更改了密码,导致此设备上自动登录失败;
用户的账号被从服务器端删除;
用户从另一个设备登录,把当前设备上登录的用户踢出。
> */
BOOL isAutoLogin = [EMClient sharedClient].options.isAutoLogin;
if (!isAutoLogin) {
EMError *error = [[EMClient sharedClient] loginWithUsername:@"" password:@""];
if (!error) {
if (success) {
success();
}
/** < 设置是否自动登录 > */
[[EMClient sharedClient].options setIsAutoLogin:YES];
NSLog(@"环信登录成功");
}else {
if (failure) {
failure();
}
NSLog(@"环信登录失败");
}
}
}
- (void)wb_hyphenateLogoutSuccess:(void (^)(void))success failure:(void (^)(void))failure {
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"环信退出成功");
if (success) {
success();
}
}else {
if (failure) {
failure();
}
NSLog(@"环信退出失败");
}
}
-
4.3 自动登录
- (void)wb_applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)wb_applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
-
4.4 跳转聊天界面
/** <
单聊:EMConversationTypeChat
Chatter:聊天对象用户名
> */
WBChatViewController *vc = [[WBChatViewController alloc]initWithConversationChatter:@"" conversationType:EMConversationTypeChat];
[self.navigationController pushViewController:vc animated:YES];
-
4.5 用户信息解决方案
-
4.5.1 环信官方也有相应的解决方案:昵称和头像的显示与更新
主要有以下两种:
从APP服务器获取昵称和头像
从消息扩展中获取昵称和头像
-
4.5.2 我在项目中采用的是从消息扩展中获取昵称和头像,下面开始介绍我的实现方案吧。
会话列表处理,在EaseConversationListViewController.m中的refreshAndSortView加载会话列表方法,对发送方消息扩展字段进行缓存,可以用数据库或者是归档等方法,我采用的是归档,保存扩展消息模型。
//缓存发送方用户信息到本地
-(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) {
/** < 缓存发送消息者信息 > */
/** < 收到的对方发送的最后一条消息,也是会话里的最新消息 > */
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(@"更新聊天对象信息成功 ");
}else {
NSLog(@"更新聊天对象信息失败 ");
}
}
}
}
}
[self.tableView reloadData];
}
//在WBConversationListViewController.m读取缓存
pragma mark ------ < EaseConversationListViewControllerDataSource > ------
#pragma mark
- (id)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];
}
- 聊天界面处理
//添加用户信息扩展,在EaseMessageViewController.m->_sendMessage方法中添加扩展信息
- (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) {
/** <
发送消息最终执行的方法,在这里构造自己信息扩展,通常用户信息在本地都有保存,这里只是测试
> */
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];
}
}];
}
}
//扩展信息展示,在WBChatViewController.m->messageViewController:修改
#pragma mark ------ < EaseMessageViewControllerDataSource > ------
#pragma mark
/** << 设置头像、昵称 > */
- (id)messageViewController:(EaseMessageViewController *)viewController
modelForMessage:(EMMessage *)message
{
id 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;
}
代码我已托管到码云上了,详情请戳:ManageHyphenateSDK
使用demo注意
- 因为环信SDK超过了100M,我对其进行了忽略,可以将HyphenateFullSDK.zip文件解压缩重新导入一次。
- 导入三方库,
cd 工程路径
,执行
pod install
完成上述两个步骤,就能正常运行工程啦~,运行效果如下图所示:
5、常见问题解决
-
5.1 image not found
可参考标题目录 2.4.4解决,或者
-
5.2 集成动态库上传AppStore
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核
在SDK当前路径下执行以下命令删除i386 x86_64两个平台
bak文件是备份目录,上传appstore之后需要替换回bak目录下的SDK
- 实时音视频版本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/
- 不包含实时音视频版本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/
三、总结
终于整理完成了,自己在写Demo的过程中,由于
git reset
命令使用不当,导致整理的文件全部撤销了,顿时都无语了,不管怎样,最终还是整理完成了。在集成EaseUI的时候,建议还是手动集成吧,如果你需要对其中文件进行更改。Demo中导入的EaseUI,我并不是原样导入的,如果你不想修改,可以直接将EaseUI导入到工程。由于能力有限,有些地方整理的不是很好,也可以自行修改,希望自己以后集成环信能少遇到一些坑,希望这篇文章也能对你有所帮助。