标签:界面 fail nes one 等等 ssd select long method
近期项目中集成即时聊天功能。挑来拣去,终于选择环信SDK来进行开发,选择环信的主要原因是接口方便、简洁。说明文档清楚易懂。文档有android、ios、和后台server端。还是非常全的。
本篇文章目的主要在于说明环信Demo怎样实现即时通信的。我在集成环信SDK到我们自己开发的app之前,研究了一下环信demo的代码。看了两三天的样子,基本搞清楚来龙去脉,可是仅仅是清楚来龙去脉。要说到里面的细节可能得深一步研究,可是这就够了,已经能够把demo里面的功能集成到我们自己的app中了。
所以本篇文章就说明一下怎样集成环信到自己的app中。
集成起来还是比較快的。最多一周时间集成就搞定了。我们是有自己的用户体系的。所以我们採用的是将环信与现有的APP用户体系集成。
集成之前。必定要到上面这个页面进行了解,怎样集成。在这里说明了怎样集成的方案。这个方法的选择就须要你自己依据已有的需求进行选择了。这个就不多说了。应该都明确。
登 录 原 理
我们的方案是将环信与现有的APP用户体系集成!
也就是说我们的server须要把现有的用户在后台注冊到环信server中,然后app登录的时候自己主动登录环信server,然后使用环信的即时通信功能。
这就意味着用户登录app的时候,须要登录两次,一次是我们的应用server。一次是环信server,仅仅只是给用户的感觉是登录了一次,而环信server的登录是代码中控制的。用户看不到也感觉不到。
好友体系原理
登录之后,就是获取好友和群组了。环信添加了聊天室的功能,有点相似于松群组的功能。仅仅只是聊天室更加任意些。群组大家都明确,不多说,聊天室呢不同,开放的公共的聊天室。成员能够随时进入聊天随时离开。离开之后自己主动不再收到聊天信息。
好友体系中环信是能够进行管理的,当然也能够不使用环信的好友管理体系,而使用应用server来进行好友的管理工作。我们项目中使用的是环信的好友管理体系。主要是方便,只是也不见得省了多少事儿,由于应用server用户体系的变更,都要由server把该用户体系的关系的变更通知环信server,然环信server也进行更改,从而保持应用server和环信server用户体系的一致性。所以大家集成过程中须要自己考虑代价。我们项目中使用环信管理好友体系主要在于app端方便,app端也不进行用户体系的变更,复杂的操作都在server端实现。所以app端方便实现、开发简单。
用户昵称、头像
环信server採用了低浸入的方式开发即时通信,也就是说它不保存用户的信息。也不訪问用户的信息。这就意味着用户的昵称、头像等等信息环信是没有保存的。开发人员无法通过环信获取用户信息。所以环信专门对与用户的昵称、头像信息给出了解决方式。
方法一 从APPserver获取昵称和头像
方法二 从消息扩展中获取昵称和头像
昵称或头像处理的方法一和方法二差别:
方法一:在发送消息时不含有不论什么扩展,收消息时假设本地不存在发送人的用户信息则须要从APPserver查询发送人的昵称和头像的URL。
方法二:在发送消息时带有包括昵称和头像URL的消息扩展,收到消息时就可以从消息扩展中取出,不须要再去APPserver获取。 方法二和方法一相比
长处:收到消息马上显示昵称不用等待APPserver返回数据后显示。
缺点:每条消息都要带有扩展,添加消息体积,每次发消息都有一些不必要的数据。
上面是环信给出的用户昵称和头像的两种解决方式。
这两种解决方式大家一看就应用明确了,不多说。主要说说我们项目中的解决方式,採用第一种方案。从应用server获取。保存本地数据库。之后,查询操作就是本地操作。那就会有问题了。用户关系更新或者信息更新呢?这个问题主要解决方法是用户好友体系的每次更新都会同一时候更新用户昵称和头像,然后更新本地数据库来解决问题。
到此,这三个问题明确之后,基本就能够開始进行开发了,你可能会说。还没有说明即时通信呢?最基本的就是即时通信怎么没有说明呢?这个问题大家勿急,后面会有!
^_^
开 发
开发过程。首先就是要研究一下环信demo的代码,里面已经进行了封装。所以把环信demo的代码看懂,利用的好的代码全然能够应用到现有的app中。
这个环信demo的代码,导入手机直接执行。注冊,用着非常好,代码执行正常,功能也正常。所以研究这个代码之后,再集成到自己的app中那就so easy!!
demo里面用到了几个jar包,主要是环信的sdk、百度地图、友盟数据分析、百度地图定位、图片载入等这几个jar包,百度地图这个应该没什么说的,之前我们app里面集成过,只是有点旧,这次顺带着把百度地图也更新成最新的了。眼下百度地图最新的挺好用的。也算是教训,就是实时更新所应用的第三方的jar!别的jar就没什么说的了。
以下就是demo里面的分包了。demo里面的分包比較多,只是从分包的名字能够看出每一个包以下的代码是什么作用了。我主要看的是activity包以下的每一个类,由于activity类就是一个个的界面,其它的都是为这个activity类服务的代码工具类。所以主要看这个就能够了。
activity包以下的类比較多,只是我们关心的类仅仅有几个而已。ChatActivity.java类就是即时聊天的界面。这个一定是要集成到自己的app其中的。
其它的三个ContactlistFragment.java、ChatAllHistoryFragment.java、GroupsActivity.java这三个类各自是联系人界面、回话历史界面、群组界面。这三个须要依据自己app的需求进行集成。
所以主要研究的工作就是放在这几个类上。
MainActivity.java就是主界面。主界面集成了上面三个界面,由主界面进行管理界面的显示。
剩下的工作没什么特别的了。搞不明确代码的能够给我留言,相互交流一下。
特别提一下以下的几个类
这个几个类有点绕。刚開始着实弄混了。
如今看来demo里面代码也是用心良苦呀!!
1、先看controller包以下的HXSDKHelper.java类,再看chatuidemo包以下的DemoHXSDKHelper.java类。明显是继承关系。后者才是demo中使用的对象类。
而且该父类在controller包下。明显是控制信息管理类,打开该类查看代码
从说明能够看出该类的作用了。
2、再看HXSDKModel.java类。这类名字就是模版类。还有DefaultHXSDKModel.java类和DemoSDKModel.java类。也非常明显存在继承关系。
完毕的功能主要是app其中即时通信的一些数据的保存和控制信息显示信息等。
这几个类搞清楚之后基本就没有什么打的问题了。
主要代码解说
1、主类MainActivity.java
public class MainActivity extends BaseActivity implements EMEventListener
该类实现了EMEventListener 接口,就一个方法例如以下:
/**
* 监听事件
*/
@Override
public void onEvent(EMNotifierEvent event) {
switch (event.getEvent()) {
case EventNewMessage: // 普通消息
{
EMMessage message = (EMMessage) event.getData();
// 提示新消息
HXSDKHelper.getInstance().getNotifier().onNewMsg(message);
refreshUI();
break;
}
case EventOfflineMessage: {
refreshUI();
break;
}
case EventConversationListChanged: {
refreshUI();
break;
}
default:
break;
}
}
主要就是监听新消息、离线消息、回话消息变化等。然后更新界面refreshUI(),更新界面就是刷新未读消息数、刷新联系人列表。回话列表等。
在主界面初始化中注冊了三个监听器,例如以下代码:
private void init() {
// setContactListener监听联系人的变化等
EMContactManager.getInstance().setContactListener(new MyContactListener());
// 注冊一个监听连接状态的listener
connectionListener = new MyConnectionListener();
EMChatManager.getInstance().addConnectionListener(connectionListener);
groupChangeListener = new MyGroupChangeListener();
// 注冊群聊相关的listener
}
这三个监听器就是监听联系人变化、群组变化、与环信server链接变化的监听器,这三者的变化都会回调这三个监听器里面的对应的方法,方便开发人员通过对应的方法採取对应的措施。
这三个监听器demo中代码比較具体,在此就不多说了。
2 联系人列表ContactlistFragment.java类
/**
* 联系人列表页
*/
public class ContactlistFragment extends Fragment {
public static final String TAG = "ContactlistFragment";
private ContactAdapter adapter;
private List<User> contactList;
private ListView listView;
private boolean hidden;
private Sidebar sidebar;
private InputMethodManager inputMethodManager;
private List<String> blackList;
ImageButton clearSearch;
EditText query;
HXContactSyncListener contactSyncListener;
HXBlackListSyncListener blackListSyncListener;
View progressBar;
Handler handler = new Handler();
private User toBeProcessUser;
private String toBeProcessUsername;
/**
* 这里注冊了两个监听器,目的在于同步联系人信息
* 当联系人发生变化、黑名单发生变化。通知这里注冊的监听器
* 进而刷新界面
*
*/
class HXContactSyncListener implements HXSDKHelper.HXSyncListener {
@Override
public void onSyncSucess(final boolean success) {
EMLog.d(TAG, "on contact list sync success:" + success);
ContactlistFragment.this.getActivity().runOnUiThread(new Runnable() {
public void run() {
getActivity().runOnUiThread(new Runnable(){
@Override
public void run() {
if(success){
progressBar.setVisibility(View.GONE);
refresh();
}else{
String s1 = getResources().getString(R.string.get_failed_please_check);
Toast.makeText(getActivity(), s1, 1).show();
progressBar.setVisibility(View.GONE);
}
}
});
}
});
}
}
class HXBlackListSyncListener implements HXSyncListener{
@Override
public void onSyncSucess(boolean success) {
getActivity().runOnUiThread(new Runnable(){
@Override
public void run() {
blackList = EMContactManager.getInstance().getBlackListUsernames();
refresh();
}
});
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_contact_list, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//防止被T后,没点确定button然后按了home键,长期在后台又进app导致的crash
if(savedInstanceState != null && savedInstanceState.getBoolean("isConflict", false))
return;
inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
listView = (ListView) getView().findViewById(R.id.list);
sidebar = (Sidebar) getView().findViewById(R.id.sidebar);
sidebar.setListView(listView);
//黑名单列表
blackList = EMContactManager.getInstance().getBlackListUsernames();
contactList = new ArrayList<User>();
// 获取设置contactlist
getContactList();
//搜索框
query = (EditText) getView().findViewById(R.id.query);
query.setHint(R.string.search);
clearSearch = (ImageButton) getView().findViewById(R.id.search_clear);
query.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
if (s.length() > 0) {
clearSearch.setVisibility(View.VISIBLE);
} else {
clearSearch.setVisibility(View.INVISIBLE);
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void afterTextChanged(Editable s) {
}
});
clearSearch.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
query.getText().clear();
hideSoftKeyboard();
}
});
// 设置adapter
adapter = new ContactAdapter(getActivity(), R.layout.row_contact, contactList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String username = adapter.getItem(position).getUsername();
if (Constant.NEW_FRIENDS_USERNAME.equals(username)) {
// 进入申请与通知页面
User user = DemoApplication.getInstance().getContactList().get(Constant.NEW_FRIENDS_USERNAME);
user.setUnreadMsgCount(0);
startActivity(new Intent(getActivity(), NewFriendsMsgActivity.class));
} else if (Constant.GROUP_USERNAME.equals(username)) {
// 进入群聊列表页面
startActivity(new Intent(getActivity(), GroupsActivity.class));
} else if(Constant.CHAT_ROOM.equals(username)){
//进入聊天室列表页面
startActivity(new Intent(getActivity(), PublicChatRoomsActivity.class));
}else {
// demo中直接进入聊天页面,实际通常是进入用户详情页
startActivity(new Intent(getActivity(), ChatActivity.class).putExtra("userId", adapter.getItem(position).getUsername()));
}
}
});
listView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 隐藏软键盘
if (getActivity().getWindow().getAttributes().softInputMode
!= WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
if (getActivity().getCurrentFocus() != null)
inputMethodManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
return false;
}
});
ImageView addContactView = (ImageView) getView().findViewById(R.id.iv_new_contact);
// 进入加入好友页
addContactView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), AddContactActivity.class));
}
});
registerForContextMenu(listView);
progressBar = (View) getView().findViewById(R.id.progress_bar);
contactSyncListener = new HXContactSyncListener();
HXSDKHelper.getInstance().addSyncContactListener(contactSyncListener);
blackListSyncListener = new HXBlackListSyncListener();
HXSDKHelper.getInstance().addSyncBlackListListener(blackListSyncListener);
if (!HXSDKHelper.getInstance().isContactsSyncedWithServer()) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (((AdapterContextMenuInfo) menuInfo).position > 2) {
toBeProcessUser = adapter.getItem(((AdapterContextMenuInfo) menuInfo).position);
toBeProcessUsername = toBeProcessUser.getUsername();
getActivity().getMenuInflater().inflate(R.menu.context_contact_list, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == R.id.delete_contact) {
try {
// 删除此联系人
deleteContact(toBeProcessUser);
// 删除相关的邀请消息
InviteMessgeDao dao = new InviteMessgeDao(getActivity());
dao.deleteMessage(toBeProcessUser.getUsername());
} catch (Exception e) {
e.printStackTrace();
}
return true;
}else if(item.getItemId() == R.id.add_to_blacklist){
moveToBlacklist(toBeProcessUsername);
return true;
}
return super.onContextItemSelected(item);
}
/**
* 当该Fragment对象改变了隐藏状态(由isHidden()方法返回)时,系统会调用这种方法。
* Fragment初始是不隐藏的,仅仅要Fragment对象改变了它的显示状态。就会调用该方法。
* 參数hidden 假设该Fragment对象如今是隐藏的,则该參数是true。否则是false。
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
this.hidden = hidden;
if (!hidden) {
refresh();
}
}
@Override
public void onResume() {
super.onResume();
if (!hidden) {
refresh();
}
}
/**
* 删除联系人
*
* @param toDeleteUser
*/
public void deleteContact(final User tobeDeleteUser) {
String st1 = getResources().getString(R.string.deleting);
final String st2 = getResources().getString(R.string.Delete_failed);
final ProgressDialog pd = new ProgressDialog(getActivity());
pd.setMessage(st1);
pd.setCanceledOnTouchOutside(false);
pd.show();
new Thread(new Runnable() {
public void run() {
try {
EMContactManager.getInstance().deleteContact(tobeDeleteUser.getUsername());
// 删除db和内存中此用户的数据
UserDao dao = new UserDao(getActivity());
dao.deleteContact(tobeDeleteUser.getUsername());
DemoApplication.getInstance().getContactList().remove(tobeDeleteUser.getUsername());
getActivity().runOnUiThread(new Runnable() {
public void run() {
pd.dismiss();
adapter.remove(tobeDeleteUser);
adapter.notifyDataSetChanged();
}
});
} catch (final Exception e) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
pd.dismiss();
Toast.makeText(getActivity(), st2 + e.getMessage(), 1).show();
}
});
}
}
}).start();
}
/**
* 把user移入到黑名单
*/
private void moveToBlacklist(final String username){
final ProgressDialog pd = new ProgressDialog(getActivity());
String st1 = getResources().getString(R.string.Is_moved_into_blacklist);
final String st2 = getResources().getString(R.string.Move_into_blacklist_success);
final String st3 = getResources().getString(R.string.Move_into_blacklist_failure);
pd.setMessage(st1);
pd.setCanceledOnTouchOutside(false);
pd.show();
new Thread(new Runnable() {
public void run() {
try {
//加入到黑名单
EMContactManager.getInstance().addUserToBlackList(username,false);
getActivity().runOnUiThread(new Runnable() {
public void run() {
pd.dismiss();
Toast.makeText(getActivity(), st2, 0).show();
refresh();
}
});
} catch (EaseMobException e) {
e.printStackTrace();
getActivity().runOnUiThread(new Runnable() {
public void run() {
pd.dismiss();
Toast.makeText(getActivity(), st3, 0).show();
}
});
}
}
}).start();
}
// 刷新ui
public void refresh() {
try {
// 可能会在子线程中调到这方法
getActivity().runOnUiThread(new Runnable() {
public void run() {
getContactList();
adapter.notifyDataSetChanged();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
if (contactSyncListener != null) {
HXSDKHelper.getInstance().removeSyncContactListener(contactSyncListener);
contactSyncListener = null;
}
if(blackListSyncListener != null){
HXSDKHelper.getInstance().removeSyncBlackListListener(blackListSyncListener);
}
super.onDestroy();
}
public void showProgressBar(boolean show) {
if (progressBar != null) {
if (show) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
}
}
/**
* 获取联系人列表。并过滤掉黑名单和排序
*/
private void getContactList() {
contactList.clear();
//获取本地好友列表
Map<String, User> users = DemoApplication.getInstance().getContactList();
Iterator<Entry<String, User>> iterator = users.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, User> entry = iterator.next();
if (!entry.getKey().equals(Constant.NEW_FRIENDS_USERNAME)
&& !entry.getKey().equals(Constant.GROUP_USERNAME)
&& !entry.getKey().equals(Constant.CHAT_ROOM)
&& !blackList.contains(entry.getKey())){
EMLog.i(TAG, "获取联系人="+entry.getValue());
contactList.add(entry.getValue());
}
}
// 排序
Collections.sort(contactList, new Comparator<User>() {
@Override
public int compare(User lhs, User rhs) {
return lhs.getUsername().compareTo(rhs.getUsername());
}
});
// 加入"群聊"和"聊天室"
if(users.get(Constant.CHAT_ROOM) != null)
contactList.add(0, users.get(Constant.CHAT_ROOM));
if(users.get(Constant.GROUP_USERNAME) != null)
contactList.add(0, users.get(Constant.GROUP_USERNAME));
// 把"申请与通知"加入到首位
if(users.get(Constant.NEW_FRIENDS_USERNAME) != null)
contactList.add(0, users.get(Constant.NEW_FRIENDS_USERNAME));
}
void hideSoftKeyboard() {
if (getActivity().getWindow().getAttributes().softInputMode !=
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
if (getActivity().getCurrentFocus() != null)
inputMethodManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(((MainActivity)getActivity()).isConflict){
outState.putBoolean("isConflict", true);
}else if(((MainActivity)getActivity()).getCurrentAccountRemoved()){
outState.putBoolean(Constant.ACCOUNT_REMOVED, true);
}
}
}
上面联系人类中的注冊的监听器使用的就是观察者模式,先看HXSDKHelper.java中的部分代码
public void addSyncGroupListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (!syncGroupsListeners.contains(listener)) {
syncGroupsListeners.add(listener);
}
}
public void removeSyncGroupListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (syncGroupsListeners.contains(listener)) {
syncGroupsListeners.remove(listener);
}
}
public void addSyncContactListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (!syncContactsListeners.contains(listener)) {
syncContactsListeners.add(listener);
}
}
public void removeSyncContactListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (syncContactsListeners.contains(listener)) {
syncContactsListeners.remove(listener);
}
}
public void addSyncBlackListListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (!syncBlackListListeners.contains(listener)) {
syncBlackListListeners.add(listener);
}
}
public void removeSyncBlackListListener(HXSyncListener listener) {
if (listener == null) {
return;
}
if (syncBlackListListeners.contains(listener)) {
syncBlackListListeners.remove(listener);
}
}
public void noitifyGroupSyncListeners(boolean success){
for (HXSyncListener listener : syncGroupsListeners) {
listener.onSyncSucess(success);
}
}
public void notifyContactsSyncListener(boolean success){
for (HXSyncListener listener : syncContactsListeners) {
listener.onSyncSucess(success);
}
}
public void notifyBlackListSyncListener(boolean success){
for (HXSyncListener listener : syncBlackListListeners) {
listener.onSyncSucess(success);
}
}
这部分代码控制着观察者,加入、删除、通知每一个观察者,当群组、好友、黑名单 通过环信server同步到client之后,notify每一个观察者,然后观察者接收到之后,刷新UI。这里就是观察者模式的经典应用!
!
!
联系人列表看懂之后,其它的群组界面和回话历史界面就不多说了。
3 聊天界面ChatActivity.java
这个类比較庞大,由于demo里面把单聊和群聊、聊天室都集成到这一个界面中完毕,代码非常庞大。可是不影响终于的集成,直接集成该类就能够实现功能。
不多说。
附上界面:
图一 回话历史界面
图二 通讯录界面好友
图三 设置界面
图四 聊天界面
最后附上源代码下载
其它的就没有什么特别须要指出的地方了,大家假设有什么问题或者疑问。都能够留言交流!
【握手】!
2016年5月17日更新
环信官方站点已经公布IM3.0版本号。
眼下开发的一个app採用的就是IM3.0版本号。
总体界面没发生大的变化,功能也都一样。可是在官方给的demo代码上优化非常多。方便非常多。只是得大概看懂里面的代码。假设是高手的话,半天就应该能集成好环信的即时通信功能。
本文给出的下载链接,是IM2.0版本号。所以假设想要使用IM3.0的版本号的。须要到官网下载。
对于新手来说,环信官网给出的demo是能够直接使用的。人家给出的是完整的app代码。新手就疑惑,不知道该怎样入手集成即时通信功能?
事实上非常easy!
首先,把环信官网给出的依赖包和动态库加入到自己的project中。
眼下官网给出的依赖包和动态库分为包括语音视频通话功能的和不包括语音视频通话功能的。
大家依据自己的APP的功能加入。
然后,把demo里面的聊天界面直接拷贝到自己的功能里面。此时复制进去以后。会出现大量的错误!由于聊天界面关联了非常多demo中的其它类,所以,要把其它类拷贝到自己的project中。
记得不要忘记布局文件、资源图片文件、字符串等等资源文件。 建议:在自己的project中。新建一个包专门放环信的类。
由于你要复制的类非常多。大概有二三十个!
最后,向即时通信代码填充数据。主要有几部分:
1)application类中环信helper类完毕初始化操作。
2)登录app界面做好登录环信server操作。须要登录环信的登录名和password。这里的环信登录与登录app
不同。APP登录是应用server的用户。用户名和password在应用server。
而登录环信是环信的登录名和password。
须要先注冊到环信server才行。注冊操作能够在应用server提前做好。APP登录应用server的时候顺带着
登录环信server就可以。
3)获取好友信息。这里要分为好友信息的维护是应用server维护还是环信帮助你维护。
这个我就不多说
了。
环信官网有说明。
4)本地维护好友列表和聊天信息里列表。聊天信息列表在环信中已经不让开发人员编辑和改变了。
该功能
已经集成在了依赖的环信的包中了。
好友列表在demo中给出了简单的数据表。
开发人员能够自己依据APP
须要开发和扩展。
5)退出APP。退出APP时务必调用helper类的logout方法。
这样以后,先前登录的用户就从APP上退
出了环信的server。开发人员要注意。我这里说的退出时指APP用户手动退出。不是用户按手机返回button或
者返回主界面button导致APP退出。而是APP中的退出button,当前登录用户退出APP。假设是用户按返回按
钮或回主界面button,返回到手机桌面的,不是必需调用helper类的logout方法。
先说这么多。大家还有什么问题,大家留言交流~~
标签:界面 fail nes one 等等 ssd select long method
原文地址:http://www.cnblogs.com/gavanwanggw/p/7352769.html