當然在集成環信之前需要一些准備操作:
1、首先注冊環信開發者賬號,直接進入環信官網注冊即可:http://www.easemob.com
2、按照文檔一步一步將需要的文件全部拖入工程中:http://docs.easemob.com/start/start
以下是我集成的文件:使用
EaseUI集成:http://docs.easemob.com/start/300iosclientintegration/140easeuiuseguide
libEaseMobClientSDK.a包
ChatDemo-UI3.0中的ChatView中的聊天控制器

我主要使用EaseMob中這個EaseSDKHelper單例類來注冊、登錄、獲取最新消息、推送等
在App啟動程序時:

進入EaseSDKHelper單例類中,添加一些自定義的方法
#pragma mark - init easemob 注冊環信 - (void)easemobApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions appkey:(NSString *)appkey apnsCertName:(NSString *)apnsCertName otherConfig:(NSDictionary *)otherConfig { //注冊登錄狀態監聽 // [[NSNotificationCenter defaultCenter] addObserver:self // selector:@selector(loginStateChange:) // name:KNOTIFICATION_LOGINCHANGE // object:nil]; //注冊AppDelegate默認回調監聽 [self _setupAppDelegateNotifications]; //注冊apns [self _registerRemoteNotification]; //注冊easemob sdk [[EaseMob sharedInstance] registerSDKWithAppKey:appkey apnsCertName:apnsCertName otherConfig:otherConfig]; // 注冊環信監聽 [self registerEaseMobLiteNotification]; //啟動easemob sdk [[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions]; [[EaseMob sharedInstance].chatManager setIsAutoFetchBuddyList:YES]; } #pragma mark - EMChatManagerLoginDelegate 自動登錄代理回調 // 自動登錄開始回調 -(void)willAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error { // UIAlertView *alertView = nil; if (error) { // alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"login.errorAutoLogin", @"Automatic logon failure") delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; //發送自動登陸狀態通知 [[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_LOGINCHANGE object:@NO]; } else{ // alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"login.beginAutoLogin", @"Start automatic login...") delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; //將舊版的coredata數據導入新的數據庫 EMError *error = [[EaseMob sharedInstance].chatManager importDataToNewDatabase]; if (!error) { [[EaseMob sharedInstance].chatManager loadDataFromDatabase]; //獲取數據 [self loadConversations]; } } // [alertView show]; } // 自動登錄結束回調 -(void)didAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error { // UIAlertView *alertView = nil; if (error) { // alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"login.errorAutoLogin", @"Automatic logon failure") delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; //發送自動登陸狀態通知 [[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_LOGINCHANGE object:@NO]; } else{ //獲取群組列表 [[EaseMob sharedInstance].chatManager asyncFetchMyGroupsList]; // alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"login.endAutoLogin", @"End automatic login...") delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; } // [alertView show]; } #pragma make - login easemob 用戶登錄 - (void)loginWithUsername:(NSString *)username password:(NSString *)password { [[EaseMob sharedInstance].chatManager asyncLoginWithUsername:username password:password completion:^(NSDictionary *loginInfo, EMError *error) { if (!error) { //設置自動登錄 [[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES]; //獲取數據 [self loadConversations]; //發送自動登陸狀態通知 [[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_LOGINCHANGE object:@YES]; } } onQueue:nil]; } #pragma mark - load conversations 加載會話列表 -(void)loadConversations{ // [[EaseMob sharedInstance].chatManager importDataToNewDatabase]; //獲取數據庫中數據 [[EaseMob sharedInstance].chatManager loadDataFromDatabase]; // 當前登錄用戶回話對象列表 NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations]; if (conversations.count == 0) { //從數據庫conversation表獲取 conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES]; } _conversations = conversations; }
在會話列表控制器中:
@interface KJAnswerQuestionController ()<UITableViewDataSource,UITableViewDelegate> /** 所有會話 */ @property (strong,nonatomic)NSArray *arrConversations; /** 提示視圖 */ @property (strong,nonatomic)UIView *showingView; @end @implementation KJAnswerQuestionController - (void)viewDidLoad { [super viewDidLoad]; //創建tableView self.view.backgroundColor = HMColor(250, 250, 250); self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds]; self.tableView.dataSource = self; self.tableView.delegate = self; self.tableView.tableFooterView = [[UIView alloc]initWithFrame:CGRectZero]; [self.view addSubview:self.tableView]; MoveUnderLine(self.tableView); //創建提示視圖 self.showingView = [UIView createViewWithShowingTitle:@"沒有聊天記錄喲!"]; [self.view addSubview:self.showingView]; //獲取數據庫中數據 self.arrConversations = [EaseSDKHelper shareHelper].conversations; [self.tableView reloadData]; //顯示隱藏 if (self.arrConversations.count == 0) { [self.showingView setHidden:NO]; [self.tableView setHidden:YES]; }else{ [self.showingView setHidden:YES]; [self.tableView setHidden:NO]; } } //消息刷新 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations]; if (conversations == 0) { conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES]; } if (conversations > self.arrConversations) { self.arrConversations = conversations; } [self.tableView reloadData]; } #pragma Mark- 數據源方法 //返回行數 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.arrConversations.count; } //返回cell -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ //取出單個會話 KJContactCell *cell = [KJContactCell createCellWithTableView:tableView]; EMConversation *conversation = [self.arrConversations objectAtIndex:indexPath.row]; cell.conversation = conversation; return cell; } #pragma mark - 代理方法 //設置cell的高度 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 60; } //選中cell時的處理 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ //取出單個會話 EMConversation *conversation = [self.arrConversations objectAtIndex:indexPath.row]; KJChatViewController *chatVC = [[KJChatViewController alloc]initWithConversationChatter:conversation.chatter conversationType:eConversationTypeChat]; chatVC.title = conversation.chatter; chatVC.conversation = conversation; chatVC.conversation.enableUnreadMessagesCountEvent = YES; [self.navigationController pushViewController:chatVC animated:YES]; } @end
在聊天控制器中,直接集成ChatViewController
#import "ChatViewController.h" @interface KJChatViewController : ChatViewController @end #import "KJChatViewController.h" @interface KJChatViewController () @end @implementation KJChatViewController - (void)viewDidLoad { [super viewDidLoad]; } //將未讀消息標記為已讀 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ // [self.conversation markMessageWithId:self.conversation.chatter asRead:YES]; [self.conversation markAllMessagesAsRead:YES]; } @end
在自定義的會話列表cell中,顯示會話聯系人、最后一條記錄、時間
#import <UIKit/UIKit.h> @class KJBadgeButton; @interface KJContactCell : UITableViewCell //創建cell +(instancetype)createCellWithTableView:(UITableView *)tableView; @property (strong,nonatomic)EMConversation *conversation; @property (strong,nonatomic)KJBadgeButton *badgeBtn; //提示數字 @end #import "KJContactCell.h" #import "KJContact.h" #import "KJBadgeButton.h" #define cellBorder 10 #define iconWidth 40 #define textHeight 30 @interface KJContactCell() @property (strong,nonatomic)UIImageView *iconView; //頭像 @property (strong,nonatomic)UILabel *MainTextLabel; //姓名 @property (strong,nonatomic)UILabel *subTextLabel; //消息 @end @implementation KJContactCell static NSString *reuseIdentifier = @"Cell"; +(instancetype)createCellWithTableView:(UITableView *)tableView{ KJContactCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; if (!cell) { cell = [[KJContactCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]; } cell.detailTextLabel.textColor = HMColor(153, 153, 153); cell.detailTextLabel.font = fontSize_13; return cell; } -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { //1.頭像 self.iconView = [[UIImageView alloc]init]; [self.contentView addSubview:self.iconView]; //2.提示badge self.badgeBtn = [[KJBadgeButton alloc]initWithFrame:CGRectMake(cellBorder+iconWidth-10, 5, 15, 15)]; [self.contentView addSubview:self.badgeBtn]; //3.姓名 self.MainTextLabel = [[UILabel alloc]init]; self.MainTextLabel.textColor = HMColor(51, 51, 51); self.MainTextLabel.font = fontSize_14; [self.contentView addSubview:self.MainTextLabel]; //4.消息 self.subTextLabel = [[UILabel alloc]init]; self.subTextLabel.textColor = HMColor(153, 153, 153); self.subTextLabel.font = fontSize_13; [self.contentView addSubview:self.subTextLabel]; } return self; } -(void)layoutSubviews{ [super layoutSubviews]; //1.頭像 self.iconView.frame = CGRectMake(cellBorder, cellBorder, iconWidth, iconWidth); //2.姓名 self.MainTextLabel.frame = CGRectMake(CGRectGetMaxX(self.iconView.frame)+2*cellBorder, 2, 100, textHeight); //3.消息 self.subTextLabel.frame =CGRectMake(CGRectGetMaxX(self.iconView.frame)+2*cellBorder, CGRectGetMaxY(self.MainTextLabel.frame), SCREEN_WIDTH-CGRectGetMaxX(self.iconView.frame)-cellBorder, textHeight); } //接收聯系人數據 -(void)setConversation:(EMConversation *)conversation{ _conversation = conversation; //設置頭像 self.iconView.image = [UIImage imageNamed:@"head"]; //設置姓名 self.MainTextLabel.text = conversation.chatter; //設置提示數字 [self.badgeBtn setBadgeValue:[NSString stringWithFormat:@"%ld",conversation.unreadMessagesCount]]; //設置歷史消息 self.subTextLabel.text = [self latestMessageTitleForConversation:conversation]; //設置歷史消息時間 self.detailTextLabel.text = [self latestMessageTimeForConversation:conversation]; } #pragma mark - 最后一條消息展示內容 -(NSString *)latestMessageTitleForConversation:(EMConversation *)conversation { //用戶獲取最后一條message,根據message的messageBodyType展示顯示最后一條message對應的文案 NSString *latestMessageTitle = @""; EMMessage *lastMessage = [conversation latestMessage]; if (lastMessage) { id<IEMMessageBody> messageBody = lastMessage.messageBodies.lastObject; switch (messageBody.messageBodyType) { case eMessageBodyType_Image:{ latestMessageTitle = NSLocalizedString(@"message.image1", @"[image]"); } break; case eMessageBodyType_Text:{ // 表情映射。 NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text]; latestMessageTitle = didReceiveText; } break; case eMessageBodyType_Voice:{ latestMessageTitle = NSLocalizedString(@"message.voice1", @"[voice]"); } break; case eMessageBodyType_Location: { latestMessageTitle = NSLocalizedString(@"message.location1", @"[location]"); } break; case eMessageBodyType_Video: { latestMessageTitle = NSLocalizedString(@"message.video1", @"[video]"); } break; case eMessageBodyType_File: { latestMessageTitle = NSLocalizedString(@"message.file1", @"[file]"); } break; default: { } break; } } return latestMessageTitle; } #pragma mark - 最后一條消息展示時間 - (NSString *)latestMessageTimeForConversation:(EMConversation *)conversation { //用戶獲取最后一條message,根據lastMessage中timestamp,自定義時間文案顯示(例如:"1分鍾前","14:20") NSString *latestMessageTime = @""; EMMessage *lastMessage = [conversation latestMessage];; if (lastMessage) { latestMessageTime = [NSDate formattedTimeFromTimeInterval:lastMessage.timestamp]; } return latestMessageTime; } @end
消息提醒按鈕
#import <UIKit/UIKit.h> @interface KJBadgeButton : UIButton @property (nonatomic, copy) NSString *badgeValue; @end #import "KJBadgeButton.h" @implementation KJBadgeButton - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.hidden = YES; self.userInteractionEnabled = NO; [self setBackgroundImage:[UIImage resizedImageWithName:@"main_badge"] forState:UIControlStateNormal]; self.titleLabel.font = [UIFont systemFontOfSize:11]; } return self; } - (void)setBadgeValue:(NSString *)badgeValue { #warning copy // _badgeValue = badgeValue; _badgeValue = [badgeValue copy]; if (badgeValue && [badgeValue intValue] != 0) { self.hidden = NO; // 設置文字 [self setTitle:badgeValue forState:UIControlStateNormal]; // 設置frame CGRect frame = self.frame; CGFloat badgeH = self.currentBackgroundImage.size.height; CGFloat badgeW = self.currentBackgroundImage.size.width; if (badgeValue.length > 1) { // 文字的尺寸 CGSize badgeSize = [badgeValue sizeWithFont:self.titleLabel.font]; badgeW = badgeSize.width + 10; } frame.size.width = badgeW; frame.size.height = badgeH; self.frame = frame; } else { self.hidden = YES; } } @end
最后在TabbarController中檢測未讀消息
//首先得注冊代理,監聽未讀消息數
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
#pragma mark - EMChatManagerChatDelegate /** * 歷史會話列表更新了會調用 */ - (void)didUpdateConversationList:(NSArray *)conversationList { // 給數據源重新賦值 self.arrConversations = conversationList; //刷新表格 [self.messageVc.tableView reloadData]; // 顯示總的未讀數 [self showTabBarBadge]; } /** * 未讀消息數改變了會調用 */ - (void)didUnreadMessagesCountChanged { //刷新表格 [self.messageVc.tableView reloadData]; // 顯示總的未讀數 [self showTabBarBadge]; } /** * 將總的未讀消息數顯示到tabBar上 */ - (void)showTabBarBadge { NSInteger totalUnreadCount = 0; for (EMConversation *conversation in self.arrConversations) { //獲取所有的聯系人發來的未讀消息 totalUnreadCount += [conversation unreadMessagesCount]; } if (totalUnreadCount > 0) { self.messageVc.tabBarItem.badgeValue = [NSString stringWithFormat:@"%ld",totalUnreadCount]; UIApplication *application = [UIApplication sharedApplication]; [application setApplicationIconBadgeNumber:totalUnreadCount]; }else{ self.messageVc.tabBarItem.badgeValue = nil; UIApplication *application = [UIApplication sharedApplication]; [application setApplicationIconBadgeNumber:0]; } }
//移除代理
-(void)dealloc{ [[EaseMob sharedInstance].chatManager removeDelegate:self]; }
測試后:

以上只是實現了單聊和群聊的功能,那么實時語音和視頻如何實現呢,下面這個就是干貨:
1.集成實時通話的前提是集成好單聊,並且使用的是libEaseMobClientSDK.a包,因為這個包 包含實時通話的功能
2.將demo3.0中的Call文件(實時通話的界面)以及Resources(通話界面的資源圖片)加到你自己的工程中
3.點擊實時通話或者視頻的按鈕,實際是發起的通知,在你工程中的主控制器中監聽這個通知,在通知的方法中實現發起實時通話的方法以及跳轉到通話界面
4.接收實時通話的回調是 - (void)callSessionStatusChanged:(EMCallSession *)callSession changeReason:(EMCallStatusChangedReason)reason error:(EMError *)error
5.實時通話用的協議是:EMCallManagerDelegate 代理:[[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil];
6.具體添加哪些方法看下上傳的ViewController文件,按照這個文件中的方法加到自己的主控制器中,demo中的實現在MainViewController.m類
代碼如下:
記得先導入call文件:這個是用來進行視頻和電話語音的類
ViewController.docx文件:http://i.cnblogs.com/Files.aspx

#import "ChatViewController.h" @interface KJChatViewController : ChatViewController @end #import "KJChatViewController.h" #import "CallViewController.h" @interface KJChatViewController ()<EMCallManagerDelegate> @end @implementation KJChatViewController - (void)viewDidLoad { [super viewDidLoad]; // 實時通話的代理 [self registerNotifications]; // 監聽發起實時通話的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(callOutWithChatter:) name:@"callOutWithChatter" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(callControllerClose:) name:@"callControllerClose" object:nil]; } //將未讀消息標記為已讀 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ // [self.conversation markMessageWithId:self.conversation.chatter asRead:YES]; [self.conversation markAllMessagesAsRead:YES]; } -(void)registerNotifications { [self unregisterNotifications]; [[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil]; } -(void)unregisterNotifications { [[EaseMob sharedInstance].callManager removeDelegate:self]; } - (void)dealloc { [self unregisterNotifications]; } // 發起實時通話 - (void)callOutWithChatter:(NSNotification *)notification { id object = notification.object; if ([object isKindOfClass:[NSDictionary class]]) { if (![self canRecord]) { return; } EMError *error = nil; NSString *chatter = [object objectForKey:@"chatter"]; EMCallSessionType type = [[object objectForKey:@"type"] intValue]; EMCallSession *callSession = nil; if (type == eCallSessionTypeAudio) { callSession = [[EaseMob sharedInstance].callManager asyncMakeVoiceCall:chatter timeout:50 error:&error]; } else if (type == eCallSessionTypeVideo){ if (![CallViewController canVideo]) { return; } callSession = [[EaseMob sharedInstance].callManager asyncMakeVideoCall:chatter timeout:50 error:&error]; } if (callSession && !error) { [[EaseMob sharedInstance].callManager removeDelegate:self]; CallViewController *callController = [[CallViewController alloc] initWithSession:callSession isIncoming:NO]; callController.modalPresentationStyle = UIModalPresentationOverFullScreen; [self presentViewController:callController animated:NO completion:nil]; } if (error) { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"error", @"error") message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil, nil]; [alertView show]; } } } #pragma mark - call - (BOOL)canRecord { __block BOOL bCanRecord = YES; AVAudioSession *audioSession = [AVAudioSession sharedInstance]; if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0"] != NSOrderedAscending) { if ([audioSession respondsToSelector:@selector(requestRecordPermission:)]) { [audioSession performSelector:@selector(requestRecordPermission:) withObject:^(BOOL granted) { bCanRecord = granted; }]; } } if (!bCanRecord) { UIAlertView * alt = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"setting.microphoneNoAuthority", @"No microphone permissions") message:NSLocalizedString(@"setting.microphoneAuthority", @"Please open in \"Setting\"-\"Privacy\"-\"Microphone\".") delegate:self cancelButtonTitle:nil otherButtonTitles:NSLocalizedString(@"ok", @"OK"), nil]; [alt show]; } return bCanRecord; } #pragma mark - ICallManagerDelegate // 接收實時通話的回調函數 - (void)callSessionStatusChanged:(EMCallSession *)callSession changeReason:(EMCallStatusChangedReason)reason error:(EMError *)error { if (callSession.status == eCallSessionStatusConnected) { EMError *error = nil; do { BOOL isShowPicker = [[[NSUserDefaults standardUserDefaults] objectForKey:@"isShowPicker"] boolValue]; if (isShowPicker) { error = [EMError errorWithCode:EMErrorInitFailure andDescription:NSLocalizedString(@"call.initFailed", @"Establish call failure")]; break; } if (![self canRecord]) { error = [EMError errorWithCode:EMErrorInitFailure andDescription:NSLocalizedString(@"call.initFailed", @"Establish call failure")]; break; } #warning 在后台不能進行視頻通話 if(callSession.type == eCallSessionTypeVideo && ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive || ![CallViewController canVideo])){ error = [EMError errorWithCode:EMErrorInitFailure andDescription:NSLocalizedString(@"call.initFailed", @"Establish call failure")]; break; } if (!isShowPicker){ [[EaseMob sharedInstance].callManager removeDelegate:self]; CallViewController *callController = [[CallViewController alloc] initWithSession:callSession isIncoming:YES]; callController.modalPresentationStyle = UIModalPresentationOverFullScreen; [self presentViewController:callController animated:NO completion:nil]; // EaseMessageViewController是聊天類,根據自己的聊天類寫 if ([self.navigationController.topViewController isKindOfClass:[EaseMessageViewController class]]) { EaseMessageViewController *chatVc = (EaseMessageViewController *)self.navigationController.topViewController; chatVc.isViewDidAppear = NO; } } } while (0); if (error) { [[EaseMob sharedInstance].callManager asyncEndCall:callSession.sessionId reason:eCallReasonHangup]; return; } } } - (void)callControllerClose:(NSNotification *)notification { // AVAudioSession *audioSession = [AVAudioSession sharedInstance]; // [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; // [audioSession setActive:YES error:nil]; [[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil]; } @end
演示結果如下:經本人測試,在真機上運行沒有問題,實時語音視頻聊天均能夠實現
左邊為電話通話 右邊為視頻通話

以下為參考資料:
1.基於環信Demo3.0,實現單聊功能:http://www.jianshu.com/p/f53be9664f14
2.集成環信的即時通訊:http://www.jianshu.com/p/b4618ef39274
3.環信聊天界面 - 顯示歷史會話記錄:http://blog.csdn.net/github_26672553/article/details/50719487
4.環信集成 - 加載會話列表:http://blog.csdn.net/u010545480/article/details/49660255
5.擴展表情包:http://apps.timwhitlock.info/emoji/tables/unicode
其他demo下載: https://github.com/zyprosoft/ZYChat
集成視頻:http://www.imgeek.org/video/
github:https://github.com/xiayuanquan/EaseMobChat
本人原創,轉載須注明出處,謝謝!
