yun2win-Android im-SDK下载地址:https://github.com/yun2win/yun2win-sdk-android
yun2win官网:www.yun2win.com
简介
yun2win即时通讯云安卓客户端主要由聊天实例和推送服务组成,其中聊天服务组件由开发者从Github获得源码,自行修改定义,保证开发者对界面功能的绝对自主。
开发准备
-
注册并创建应用
-
到 github下载yun2winSDK及demo
-
下载源码详解
app为主体显示Module uikit为公共服务Module
app下libs的yun2win-0.1.jar为推送SDK
主体Module结构图
base文件夹:app初始相关类
Bridge文件夹:推送连接、接收和发送
common文件夹:公共配置等
db文件夹:数据库管理
entities文件夹:实体集合
manage文件夹:模型实体管理
model文件夹:模型集合
service文件夹:服务管理
ui文件夹:界面相关
-
配置信息
在清单文件AndroidManifest.xml里加入以下权限,以及写上你注册的appkey
权限配置(实际开发中可能需要更多的权限,可参考demo):
复制xml version="1.0" encoding="utf-8" ?>
xmlns:android="http://schemas.android.com/apk/res/android" package="com.yun2win.demo"> android:name="android.permission.INTERNET" /> android:name="android.permission.ACCESS_NETWORK_STATE" /> android:name="android.permission.ACCESS_WIFI_STATE" /> android:name="android.permission.CHANGE_WIFI_STATE" /> android:name="android.permission.ACCESS_FINE_LOCATION" /> android:name="android.permission.ACCESS_COARSE_LOCATION" /> android:name="android.permission.WRITE_SETTINGS" /> android:name="android.permission.GET_TASKS" /> android:name="android.permission.FLASHLIGHT" /> android:name="android.permission.VIBRATE" /> android:name="android.permission.WAKE_LOCK" /> android:name="android.permission.BLUETOOTH" /> android:name="android.permission.BLUETOOTH_ADMIN" /> android:name="android.permission.CHANGE_CONFIGURATION" /> android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> android:name="android.permission.READ_EXTERNAL_STORAGE" /> android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> android:name="android.permission.CAMERA" /> android:name="android.permission.RECORD_AUDIO" /> android:name="android.permission.READ_PHONE_STATE" /> android:name="y2w.base.AppContext" android:allowbackup="true" android:icon="@drawable/lyy_icon" android:label="@string/app_name" android:theme="@style/AppTheme"> android:name="YUN2WIN_APP_KEY" android:value="CerAgk970T8MlUmz" /> 关于YUN2WIN_APP_KEY对应的value获取,在创建应用后,申请APPKEY并进行相关配置。
-
app打包混淆
在proguard文件中加入以下keep
-keep class com.yun2win.** {*;}
-dontwarn com.yun2win.**
注册
发送注册请求
//account pwd username设置账户 密码 用户名
Users.getInstance().getRemote().register(account, pwd, username,new Back.Result<user>() { @override public void onsuccess(user user) { />/注册成功 返回当前用户信息 跳转界面 UserInfo.setUserInfo(user.getEntity().getAccount(),"","","","","",""); Intent intent = new Intent(context,LoginActivity.class); startActivity(intent); finish(); } @Override public void onError(int errorCode,String error) { //注册失败 } } });
登录
发送登陆请求获取当前用户数据
Users.getInstance().getRemote().login(account, password, new Back.Result<currentuser>() { @Override public void onSuccess(CurrentUser currentUser) { //同步会话联系人 Users.getInstance().getCurrentUser().getRemote().sync(new Back.Callback() { @Override public void onSuccess() { startActivity(new Intent(LoginActivity.this, MainActivity.class)); finish(); } @Override public void onError(int errorCode, String error) { //同步失败 } }); } @Override public void onError(int errorCode, String error) { //注册失败 } });
登陆成功,在跳转页面onCreate方法里面添加如下代码(此处为MainActivity)
//推送服务器连接
Users.getInstance().getCurrentUser().getImBridges().connect();
当前用户
用户属性
String id;//用户ID String name;//用户名字 String account;//用户账号 String createdAt;//创建时间 String updatedAt;//更新时间 String avatarUrl;//用户名字
当前用户属性
String appKey; String secret; String token; MToken imToken;//用于连接y2wIM消息推送服务器 Contacts contacts;//联系人对象 Sessions sessions;//会话对象 UserConversations userConversations;//用户会话对象 UserSessions userSessions;//群组对象 IMBridges imBridges;//消息推送代理对象 Remote remote;//远程方法封装对象 CurrentUser user;//当前会话对象
获取当前登陆的用户信息(yun2win支持多用户登陆)
//获取当前用户的名字
Users.getInstance().getCurrentUser().getEntity().getName(); //获取当前用户的头像 Users.getInstance().getCurrentUser().getEntity().getAvatarUrl(); //获取当前用户其它信息亦然
用户会话
用户会话实体UserConversation
属性
String id;//会话ID String targetId;//如果是群卫会话ID 个人对个人是对方用户ID String name;//会话名称 String type;//会话类型 boolean top;//是否置顶 boolean visiable;//是否可见 int unread;//未读消息 //最后一条消息发送者 唯一标识码 String lastSender; String lastType; //最后一条消息展示类容 String lastContent; boolean isDelete;//是否被删除 String createdAt; String updatedAt; String avatarUrl;//头像地址
为了保证消息的完整性,我们采取同步服务端数据到本地数据库,同时通过查询本地数据库更新界面,同步时拿本地最后一条更新数据的时间同步后面的数据(默认时间1990-01-01 00:00:00),联系人和聊天消息亦然
同步处理机制
通过CallBackUpdate管理界面和更新消息通知,当创建一个当前界面时添加CallBackUpdate实例,销毁当前界面时,移除当前CallBackUpdate实例
接收消息通知
Handler updatesessionHandler= new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what ==0){//更新界面 conversationAdapter.updateListView(); }else if(msg.what==1){//接收更新,本地服务器获取新的内容 conversations = Users.getInstance(). getCurrentUser().getUserConversations().getUserConversations(); conversationAdapter.setCOnversations(conversations); updatesessionHandler.sendEmptyMessage(0); } } };
添加CallBackUpdate
//CallBackUpdate.updateType.userConversation.toString() 当前页面标识
AppData.getInstance().getUpdateHashMap(). put(CallBackUpdate.updateType.userConversation.toString(),callBackUpdate);
移除CallBackUpdate
//CallBackUpdate.updateType.userConversation.toString() 当前页面标识
AppData.getInstance().getUpdateHashMap().remove(CallBackUpdate.updateType.userConversation.toString());
同步会话
Users.getInstance().getCurrentUser().getUserConversations().getRemote().sync(new Back.Result<list<userconversation>>() { @Override public void onSuccess(List<userconversation>userConversationList) { //同步到的消息添加到本地数据库 Users.getInstance().getCurrentUser().getUserConversations().add(userConversationList); //返回更新成功,通知界面更新 //AppData.getInstance().getUpdateHashMap(). get(CallBackUpdate.updateType.userConversation.toString()).updateUI(); callback.onSuccess(); } @Override public void onError(int errorCode,String error) { callback.onError(errorCode,error); } });
联系人
联系人Contacts
属性
//联系人唯一标识码
String id; //用户唯一标识码 String userId; String name;//名字 String email;//邮箱 String createdAt;//创建时间 String updatedAt;//更新时间 String avatarUrl;//头像地址 String role;//角色 String status;//状态 String jobTitle;//工作 boolean isDelete;//是否删除 String phone;//电话 String address;//地址
同步参考用户会话
联系人排序
contacts = Users.getInstance().getCurrentUser().getContacts().getContacts(); for (Contact data : contacts) { SortModel sm = new SortModel(); sm.setId(data.getEntity().getId()); sm.setUserId(data.getEntity().getUserId()); sm.setName(data.getEntity().getName()); sm.setPinyin(data.getEntity().getName()); sm.setEmail(data.getEntity().getEmail()); sm.setAvatarUrl(data.getEntity().getAvatarUrl()); sm.setStatus(data.getEntity().getStatus()); sm.setRole(data.getEntity().getRole()); sm.setSortLetters(StringUtil.getPinYinSortLetters(characterParser,sm.getPinyin())); SourceDataList.add(sm); } // 根据a-z进行排序源数据 Collections.sort(SourceDataList, pinyinComparator); contactAdapter.setListViewdate(SourceDataList);
同步联系人
//同步会话联系人
Users.getInstance().getCurrentUser().getRemote().sync(new Back.Callback() { @Override public void onSuccess() { Users.getInstance().getCurrentUser().getContacts().add(contacts); //返回更新成功,通知界面更新 //AppData.getInstance().getUpdateHashMap(). get(CallBackUpdate.updateType.contact.toString()).updateUI(); } @Override public void onError(int errorCode, String error) { } });
查找用户
Users.getInstance().getRemote().search(keyword, new Back.Result<list<contactentity>>() { @Override public void onSuccess(List<contactentity> entities) { if (entities==null||entities.size()<=0) { ToastUtil.ToastMessage(AddContactActivity.this, "抱歉,没有查询到任何用户"); return; } //更新界面 Message msg = new Message(); msg.what = 1; msg.obj = entities; updatelistHandler.sendMessage(msg); } @Override public void onError(int errorCode, String error) { ToastUtil.ToastMessage(AddContactActivity.this, "抱歉,没有查询到任何用户"); } });
获得个人用户信息
//_otheruserid 查找人的用户ID
Users.getInstance().getRemote().userGet(_otheruserid, new Back.Result<user>() { @Override public void onSuccess(User user) { account = user.getEntity().getAccount(); username = user.getEntity().getName(); avatarUrl =user.getEntity().getAvatarUrl(); updatefriendHandler.sendEmptyMessage(0); } @Override public void onError(int Code, String error) { } });
添加用户到联系人
添加好友,是把某人添加到自己联系人,不需要对方确认,也不会出现在对方联系人,除非对方也添加
//_otherId 联系人ID
Users.getInstance().getCurrentUser().getContacts().getRemote().contactDelete(_otherId, new Back.Callback() { @Override public void onSuccess() { ToastUtil.ToastMessage(context, "删除联系人成功"); //通知更新联系人界面 AppData.getInstance().getUpdateHashMap(). get(CallBackUpdate.updateType.contact.toString()).syncDate(); finish(); } @Override public void onError(int errorCode,String error) { ToastUtil.ToastMessage(context, "删除失败"); } });
提醒
请记下联系人ID不是用户ID,添加一个用户到我的联系人,就会生成一个相对这个用户的联系人ID
从联系人删除用户
//_otheruserid 对方用户ID 对方账户 名字 头像
Users.getInstance().getCurrentUser().getContacts().getRemote().contactAdd(_otheruserid, account, username, avatarUrl, new Back.Result<contact>() { @Override public void onSuccess(Contact contact) { ToastUtil.ToastMessage(context, "添加联系人成功"); //得到联系人ID _otherId = contact.getEntity().getId(); //通知更新联系人界面 AppData.getInstance().getUpdateHashMap(). get(CallBackUpdate.updateType.contact.toString()).syncDate(); isfriend = true; updatefriendHandler.sendEmptyMessage(2); } @Override public void onError(int errorCode, String error) { ToastUtil.ToastMessage(context, "添加失败"); } });
群组
群组UserSession
属性
//会话id
String id; //会话类型为p2p时,对方Id String sessionId; //会话名称 String name; //会话类型 boolean isDelete; //会话创建时间 String createdAt; //会话更新时间 String updatedAt; //会话头像 String avatarUrl;
添加群组
Users.getInstance().getCurrentUser().getUserSessions().getRemote().sessionStore( session.getEntity().getId(), session.getEntity().getName(), session.getEntity().getAvatarUrl(), new Back.Result<usersession>(){ @Override public void onSuccess(UserSession userSession) { } @Override public void onError(int Code, String error) { } });
删除群组
Users.getInstance().getCurrentUser().getUserSessions().getRemote().userSessionDelete( session.getEntity().getId(),new Back.Callback(){ @Override public void onSuccess() { } @Override public void onError(int Code, String error) { } });
同步群组
Users.getInstance().getCurrentUser().getUserSessions().getRemote().sync( new Back.Result<list<usersession>>(){ @Override public void onSuccess(List<usersessionentity> entities) { } @Override public void onError(int Code, String error) { } });
提醒
我的群会话只有收藏才能加入到我的群,发起群聊人默认把群会话加入我的群收藏
Session
Session
属性
SessionEntity entity;//session实体 SessionMembers members;//session成员 Messages messages;//session消息
SessionEntity
属性
//会话id
String id; //会话类型为p2p时,对方Id String otherSideId; //会话名称 String name; //会话类型 String type; //安全类型 String secureType; //会话简介 String description; //成员变更时间戳 String createMTS; //成员信息变更时间戳 String updateMTS; //会话创建时间 String createdAt; //会话更新时间 String updatedAt; //会话头像 String avatarUrl;
发起群聊,创建Session
/**
* 创建会话
* @param names session名字
* @param secureType 安全类型级别 public private
* @param SessionType 会话类型 p2p,group,single
* @param Urls.User_Avatar_Def 头像
*/
Users.getInstance().getCurrentUser().getSessions().getRemote().sessionCreate(names, secureType, EnumManage.sessionType, Urls.User_Avatar_Def, new Back.Result<session>() { @Override public void onSuccess(Session session) { addMytoGroupMembers(session); } @Override public void onError(int errorCode, String error) { ToastUtil.ToastMessage(SessionStartActivity.this, "创建失败"); pd.dismiss(); } });
获得Session详情
String targetId = EnumManage.SessionType.p2p.toString().equals(sessionType) ? otheruserID:sessionId; /** * 创建会话 * @param targetId 如何使一对一的聊天为对方用户ID否则为sessionID * @param SessionType 会话类型 p2p,group,single * @param Urls.User_Avatar_Def 头像 */ Users.getInstance().getCurrentUser().getSessions().getSessionByTargetId(targetId, sessionType, new Back.Result<session>() { @Override public void onSuccess(Session session) { _session = session; } @Override public void onError(int errorCode,String error) { } }); }
提醒
会话类型分为p2p
个人对个人group
群聊group
其它
SessionMember
添加会话成员
/**
* 创建会话
* @param userid 用户ID
* @param userName 用户名字
* @param groupRole 成员在群里的角色 master admin user
* @param userAvatarUrl 用户头像
* @param UserStatus 用户状态 active inactive
*/
session.getMembers().getRemote().sessionMemberAdd(userid, userName,groupRole, userAvatarUrl, UserStatus, new Back.Result<sessionmember>(){ @Override public void onSuccess(SessionMember sessionMember) { membersCount--; if(membersCount==0 && !iscreate){ pd.dismiss(); finish(); } } @Override public void onError(int errorCode,String error) { } });
提醒
发起群聊,先把自己添加进去,groupRole
设置为master
删除会话成员
session.getMembers().getRemote().sessionMemberDelete(sessionMember,new Back.Callback(){ @Override public void onSuccess() { } @Override public void onError(int errorCode, String error) { } });
同步会话成员
session.getMembers().getRemote().sync(new Back.Result<list<sessionmember>>(){ @Override public void onSuccess(List<sessionmember> sessionMembers) { } @Override public void onError(int Code, String error) { } })
消息
消息同步
MessageEntity
属性
String id;//消息ID String sender;//发送者账号 String content;//消息内容 String createdAt;//创建时间 String updatedAt;//更新时间 String type;//消息类型 String status;//发送状态 String sessionId;//会话ID
初次进入消息聊天界面
1.获得最后一条同步消息的更新时间
2.如果消息时间为默认时间,为了防止历史消息太多,加载时间长,加载最近20条直接显示(此数据不存储至数据库), 同时进行消息同步存到数据库,标记正在同步,如果正在同步中,接收到的新消息和发消息都不存到本地数据库,直接界面显示
int cout =Config.LOAD_MESSAGE_COUNT;//默认加载20条 //获得最近历史消息 _session.getMessages().getRemote().getLastMessages(cout, new Back.Result<list<messagemodel>>() { @Override public void onSuccess(List<messagemodel> messageModels) { //更新界面 messageList.clear(); messageList.addAll(messageModels); refreshMessageList(); scrollToBottom(); } @Override public void onError(int errorCode,String error) { } }); //开始同步 synMessagedate =new synMessagedate(); synMessagedate.start();
boolean issyndate =false;//是否正在同步 class synMessagedate extends Thread{//同步消息 @Override public void run() { super.run(); //标记正在同步 issyndate =true; boolean isstore= true;//是否存储到本地数据库 String updateAt =_session.getMessages().getMessageUpdateAt();获取本地数据最后一个消息的更新时间 _session.getMessages().getRemote().sync(isstore,updateAt, Config.LOAD_MESSAGE_COUNT, new Back.Result<list<messagemodel>>() { @Override public void onSuccess(List<messagemodel> models) { if (models.size() < Config.LOAD_MESSAGE_COUNT) {//同步完成 //每次取20条数据,如果取到的数据小于20条表示更新完成 issyndate=false; messageDisplay(); } else { //没有更新完成,继续更新 run(); } //标记消息已读 setMessagenum(models.size()); } @Override public void onError(int errorCode,String error) { } }); } }
如果消息时间不为默认时间,但是未读消息大于20条执行上面第2条操作,如果未读消息小于或者等于20条,直接同步更新
_session.getMessages().getRemote().sync(true,_session.getMessages().getMessageUpdateAt(), unread, new Back.Result<list<messagemodel> >() { @Override public void onSuccess(List<messagemodel> models) { setMessagenum(models.size()); //初始化listview加载本地消息更新 messageDisplay(); } @Override public void onError(int errorCode,String error) { messageDisplay(); } });
有新的消息,同步新消息
为了防止频繁同步消息,如果接收到消息通知,先判断messageLoading
是否正在同步消息,如果正在同步, 标记hasNewMessage
,表示有新的消息过来,等上次同步完成判断hasNewMessage
是否有新消息,如果有,继续同步
if(messageLoading){ hasNewMessage = true; }else{ loadNewMessage(); }
private void loadNewMessage(){ final int unread = 50;//默认一次加载消息条数 messageLoading = true;//标记正在同步消息 hasNewMessage = false;//标记没有新消息 //issyndate判断是否有初始同步消息如果有就不存储到数据库 _session.getMessages().getRemote().sync(!issyndate,_session.getMessages().getMessageUpdateAt(), unread, new Back.Result<list <messagemodel>>() { @Override public void onSuccess(List<messagemodel> models) { boolean istobottom =false; int lastnum =lv_message.getLastVisiblePosition(); if(lastnum >=(messageList.size()-1)) istobottom =true; if(models!=null&&models.size()>0){ boolean find =false; List<messagemodel> tempmodels = new ArrayList<messagemodel>(); //去掉重复的 for(int i =0;i<messagelist.size();i++){ if(!find) { if (models.get(0).getentity().getid().equals (messagelist.get(i).getentity().getid())) { find=true; tempmodels.add(messagelist.get(i)); } }else{ tempmodels.add(messagelist.get(i)); } } if(find){ messagelist.removeall(tempmodels); } messagelist