美文网首页iOS技术总结程序员环信集成
最新环信V3.3.7单聊集成与使用

最新环信V3.3.7单聊集成与使用

作者: WenBo丨星空灬 | 来源:发表于2018-01-12 16:38 被阅读362次
    architecture.jpg
    环信官网: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重要组成部分集成前需了解

    demo.png

    SDK_Core: 为核心的消息同步协议实现,完成与服务器之间的信息交换。
    SDK: 是基于核心协议实现的完整的 IM 功能,实现了不同类型消息的收发、会话管理、群组、好友、聊天室等功能。
    EaseUI: 是一组 IM 相关的 UI 控件,旨在帮助开发者快速集成环信 SDK。

    sdk.png

    EMClient: 是 SDK 的入口,主要完成登录、退出、连接管理等功能。也是获取其他模块的入口。
    EMChatManager: 管理消息的收发,完成会话管理等功能。
    EMContactManager: 负责好友的添加删除,黑名单的管理。
    EMGroupManager: 负责群组的管理,创建、删除群组,管理群组成员等功能。
    EMChatroomManager: 负责聊天室的管理。

    上面这些内容,可以对环信SDK组成有一个大致的理解,在最开始接入环信时候,自己没有看相关介绍文档,导致自己使用工程中遇到很多坑。

    2.3 通过 Cocoapods导入

    • 2.3.1 不包含实时语音版本 SDK(HyphenateLite),引用时 #import <HyphenateLite/HyphenateLite.h>
    pod 'HyphenateLite'
    
    • 2.3.2 包含实时语音版本 SDK(Hyphenate),引用时 #import <Hyphenate/Hyphenate.h>
    pod 'Hyphenate'
    

    2.4 手动集成

    • 2.4.1 SDK下载

    官方最新SDK+Demo
    HyphenateV3.3.7

    • 2.4.2 将ios_IM_sdk_V3.3.7文件下的SDK文件夹拖入到工程,如下图所示:
    屏幕快照 2018-01-11 下午2.42.24.png 屏幕快照 2018-01-11 下午2.49.43.png
    • 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.frameworkBolts.framework: Demo 中的用户信息存储在 Parse,这两个库是 Parse 所需要的库,开发者如果没用 Parse 存储,不要复制到自己项目中。

    • 2.4.4 因为Hyphenate是动态库,需要在Build Phase中 Embedded Binaries添加Hyphenate.framework,如下图所示:
    屏幕快照 2018-01-11 下午3.14.39.png
    • 到这里,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目录如下图所示:
      屏幕快照 2018-01-11 下午9.54.04.png
    • 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 <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最终文件请戳:
    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];
    

    主要有以下两种:

    • 从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<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];
    }
    
    • 聊天界面处理
      //添加用户信息扩展,在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<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;
    }
    

    代码我已托管到码云上了,详情请戳:ManageHyphenateSDK
    使用demo注意

    • 因为环信SDK超过了100M,我对其进行了忽略,可以将HyphenateFullSDK.zip文件解压缩重新导入一次。
    • 导入三方库,cd 工程路径,执行
    pod install
    

    完成上述两个步骤,就能正常运行工程啦~,运行效果如下图所示:


    Untitled.gif

    5、常见问题解决

    • 5.1 image not found

    屏幕快照 2018-01-11 下午3.03.57.png
    可参考标题目录2.4.4解决,或者
    屏幕快照 2018-01-11 下午3.26.40.png
    • 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导入到工程。由于能力有限,有些地方整理的不是很好,也可以自行修改,希望自己以后集成环信能少遇到一些坑,希望这篇文章也能对你有所帮助。

    相关文章

      网友评论

      • 师大小海腾:能整理下3.4.1的EaseUi吗
        WenBo丨星空灬:可以参考我V3.3.7整理的方式呢,最近项目有点忙,现在的项目也没用到环信
      • iosder:想问一下 如何上架后替换bak文件备份目录
        iosder:@WenBo丨星空灬 那备份bak不是多余了吗?
        WenBo丨星空灬:如果应用上架后,还需要用模拟器调试的话,重新在导入一遍HyphenateFullSDK替换掉就可以了。:grin:

      本文标题:最新环信V3.3.7单聊集成与使用

      本文链接:https://www.haomeiwen.com/subject/qomjnxtx.html