最近项目中集成即时聊天功能,挑来拣去,最终选择环信SDK来进行开发,选择环信的主要原因是接口方便、简洁,说明文档清晰易懂。文档有android、ios、和后台服务器端,还是非常全的。
环信官网:http://www.easemob.com/

本篇文章目的主要在于说明环信Demo如何实现即时通信的。我在集成环信SDK到我们自己开发的app之前,研究了一下环信demo的代码,看了两三天的样子,基本搞清楚来龙去脉,但是只是清楚来龙去脉,要说到里面的细节可能得深一步研究,但是这就够了,已经可以把demo里面的功能集成到我们自己的app中了。所以本篇文章就说明一下如何集成环信到自己的app中。

集成起来还是比较快的,最多一周时间集成就搞定了。我们是有自己的用户体系的,所以我们采用的是将环信与现有的APP用户体系集成。

集成之前,必然要到上面这个页面进行了解,如何集成,在这里说明了如何集成的方案,这个方案的选择就需要你自己根据已有的需求进行选择了。这个就不多说了,应该都明白。

登 录 原 理
我们的方案是将环信与现有的APP用户体系集成!也就是说我们的服务器需要把现有的用户在后台注册到环信服务器中,然后app登录的时候自动登录环信服务器,然后使用环信的即时通信功能。
这就意味着用户登录app的时候,需要登录两次,一次是我们的应用服务器,一次是环信服务器,只不过给用户的感觉是登录了一次,而环信服务器的登录是代码中控制的,用户看不到也感觉不到。

好友体系原理
登录之后,就是获取好友和群组了,环信增加了聊天室的功能,有点类似于松群组的功能,只不过聊天室更加随意些。群组大家都明白,不多说,聊天室呢不同,开放的公共的聊天室,成员可以随时进入聊天随时离开,离开之后自动不再收到聊天信息。
好友体系中环信是可以进行管理的,当然也可以不使用环信的好友管理体系,而使用应用服务器来进行好友的管理工作。我们项目中使用的是环信的好友管理体系,主要是方便,不过也不见得省了多少事儿,因为应用服务器用户体系的变更,都要由服务器把该用户体系的关系的变更通知环信服务器,然环信服务器也进行更改,从而保持应用服务器和环信服务器用户体系的一致性。所以大家集成过程中需要自己考虑代价。我们项目中使用环信管理好友体系主要在于app端方便,app端也不进行用户体系的变更,复杂的操作都在服务器端实现,所以app端方便实现、开发简单。

用户昵称、头像
环信服务器采用了低浸入的方式开发即时通信,也就是说它不保存用户的信息,也不访问用户的信息,这就意味着用户的昵称、头像等等信息环信是没有保存的,开发者无法通过环信获取用户信息。所以环信专门对与用户的昵称、头像信息给出了解决方案。

方法一 从APP服务器获取昵称和头像
方法二 从消息扩展中获取昵称和头像
昵称或头像处理的方法一和方法二区别:
方法一:在发送消息时不含有任何扩展,收消息时如果本地不存在发送人的用户信息则需要从APP服务器查询发送人的昵称和头像的URL。
方法二:在发送消息时带有包含昵称和头像URL的消息扩展,收到消息时即可从消息扩展中取出,不需要再去APP服务器获取, 方法二和方法一相比
优点:收到消息立即显示昵称不用等待APP服务器返回数据后显示。
缺点:每条消息都要带有扩展,增加消息体积,每次发消息都有一些不必要的数据。
上面是环信给出的用户昵称和头像的两种解决方案。这两种解决方案大家一看就应用明白了,不多说。主要说说我们项目中的解决方案,采用第一种方案,从应用服务器获取,保存本地数据库,之后,查询操作就是本地操作,那就会有问题了,用户关系更新或者信息更新呢?这个问题主要解决方法是用户好友体系的每次更新都会同时更新用户昵称和头像,然后更新本地数据库来解决这个问题。

到此,这三个问题明白之后,基本就可以开始进行开发了,你可能会说,还没有说明即时通信呢?最主要的就是即时通信怎么没有说明呢?这个问题大家勿急,后面会有!^_^

开 发
开发过程,首先就是要研究一下环信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 接口,就一个方法如下:

/*** 监听事件*/@Overridepublic 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());// 注册一个监听连接状态的listenerconnectionListener = new MyConnectionListener();EMChatManager.getInstance().addConnectionListener(connectionListener);groupChangeListener = new MyGroupChangeListener();// 注册群聊相关的listener}

这三个监听器就是监听联系人变化、群组变化、与环信服务器链接变化的监听器,这三者的变化都会回调这三个监听器里面的相应的方法,方便开发者通过相应的方法采取相应的措施。
这三个监听器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 {@Overridepublic 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(){@Overridepublic 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{@Overridepublic void onSyncSucess(boolean success) {getActivity().runOnUiThread(new Runnable(){@Overridepublic void run() {blackList = EMContactManager.getInstance().getBlackListUsernames();refresh();}});}};@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_contact_list, container, false);}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);//防止被T后,没点确定按钮然后按了home键,长期在后台又进app导致的crashif(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>();// 获取设置contactlistgetContactList();//搜索框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() {@Overridepublic void onClick(View v) {query.getText().clear();hideSoftKeyboard();}});// 设置adapteradapter = new ContactAdapter(getActivity(), R.layout.row_contact, contactList);listView.setAdapter(adapter);listView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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。*/@Overridepublic void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);this.hidden = hidden;if (!hidden) {refresh();}}@Overridepublic 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();}// 刷新uipublic void refresh() {try {// 可能会在子线程中调到这方法getActivity().runOnUiThread(new Runnable() {public void run() {getContactList();adapter.notifyDataSetChanged();}});} catch (Exception e) {e.printStackTrace();}}@Overridepublic 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>() {@Overridepublic 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);}}@Overridepublic 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);}}

这部分代码控制着观察者,添加、删除、通知每一个观察者,当群组、好友、黑名单 通过环信服务器同步到客户端之后,notify每个观察者,然后观察者接收到之后,刷新UI。这里就是观察者模式的经典应用!!!
联系人列表看懂之后,其他的群组界面和回话历史界面就不多说了。

3 聊天界面ChatActivity.java
这个类比较庞大,因为demo里面把单聊和群聊、聊天室都集成到这一个界面中完成,代码很庞大,但是不影响最终的集成,直接集成该类就可以实现功能。不多说。

附上界面:

图一 回话历史界面


图二 通讯录界面好友


图三 设置界面


图四 聊天界面

最后附上源码下载
其他的就没有什么特别需要指出的地方了,大家如果有什么问题或者疑问,都可以留言交流!【握手】!


2016年5月17日更新
环信官方网站已经发布IM3.0版本。目前开发的一个app采用的就是IM3.0版本。
整体界面没发生大的变化,功能也都一样。但是在官方给的demo代码上优化很多,方便很多。不过得大概看懂里面的代码。如果是高手的话,半天就应该能集成好环信的即时通信功能。
本文给出的下载链接,是IM2.0版本。所以如果想要使用IM3.0的版本的,需要到官网下载。

对于新手来说,环信官网给出的demo是可以直接使用的。人家给出的是完整的app代码。新手就疑惑,不知道该如何入手集成即时通信功能?
其实很简单!
首先,把环信官网给出的依赖包和动态库添加到自己的工程中。目前官网给出的依赖包和动态库分为包含语音视频通话功能的和不包括语音视频通话功能的。大家根据自己的APP的功能添加。
然后,把demo里面的聊天界面直接复制到自己的功能里面,此时复制进去以后,会出现大量的错误!因为聊天界面关联了很多demo中的其他类,所以,要把其他类复制到自己的工程中。记得不要忘记布局文件、资源图片文件、字符串等等资源文件! 建议在自己的工程中,新建一个包专门放环信的类。因为你要复制的类非常多!大概有二三十个!
最后,向即时通信代码填充数据。主要有几部分:

    1)application类中环信helper类完成初始化操作。 2)登录app界面做好登录环信服务器操作。需要登录环信的登录名和密码。这里的环信登录与登录app 不同。APP登录是应用服务器的用户,用户名和密码在应用服务器。而登录环信是环信的登录名和密码, 需要先注册到环信服务器才行。注册操作可以在应用服务器提前做好。APP登录应用服务器的时候顺带着 登录环信服务器即可。 3)获取好友信息。这里要分为好友信息的维护是应用服务器维护还是环信帮助你维护。这个我就不多说 了。环信官网有说明。 4)本地维护好友列表和聊天信息里列表。聊天信息列表在环信中已经不让开发者编辑和改变了。该功能 已经集成在了依赖的环信的包中了。好友列表在demo中给出了简单的数据表。开发者可以自己根据APP 需要开发和扩展。 5)退出APP。退出APP时务必调用helper类的logout方法。这样以后,先前登录的用户就从APP上退 出了环信的服务器。开发者要注意,我这里说的退出时指APP用户手动退出,不是用户按手机返回按钮或 者返回主界面按钮导致APP退出。而是APP中的退出按钮,当前登录用户退出APP。如果是用户按返回按 钮或回主界面按钮,返回到手机桌面的,没必要调用helper类的logout方法。

先说这么多,大家还有什么问题,大家留言交流~~

android-使用环信SDK开发即时通信功能及源码下载相关推荐

  1. Android基于环信SDK开发IM即时聊天(一)

    2016-09-02更新:可以看一下最新的这篇文章和源码,Android基于环信SDK开发IM即时聊天(二) 目前市面上我了解的做第三方即时聊天SDK的有两家:环信.融云,这里我使用环信SDK来完成即 ...

  2. Android基于环信SDK开发IM即时聊天(二)

    声明1:北京时间现在是2019/6/10,评论里的问题我看到了,这几天我找时间看看源代码问题出在哪,在此感谢大家的监督 声明2:此Demo我是在5.1测试机上测试通过,感谢WTQ_DOMIAN的评论, ...

  3. android 第三方 im,Android基于环信SDK开发IM即时聊天

    目前市面上我了解的做第三方即时聊天SDK的有两家:环信.融云,这里我使用环信SDK来完成即时聊天的初步开发工作. 下面先奉上1张效果图: 1.开发准备 首先要到环信官网注册开发者账号,目前只有企业账号 ...

  4. 环信sdk android 聊天,Android基于环信SDK开发IM即时聊天

    根据用户移动地图的位置,显示在视野范围内的建筑物,简单的思路是,添加地图监听,当地图移动结束之后,计算出当前屏幕四个角的GeoPoint,根据这4个点,通过mys 目前市面上我了解的做第三方即时聊天S ...

  5. cesium 入门开发系列地图鹰眼功能(附源码下载)

    前言 cesium 入门开发系列环境知识点了解: cesium api文档介绍,详细介绍 cesium 每个类的函数以及属性等等 cesium 在线例子 内容概览 cesium 结合 leaflet ...

  6. Android音频实时传输与播放(四):源码下载(问题更新)【转】

    Android音频实时传输与播放(四):源码下载(问题更新) 激动人心的时刻到了有木有 ^_^ 服务端下载请点击这里,客户端下载请点击这里! 最近有朋友在下载源码使用之后,说播放出来的声音噪声很大.其 ...

  7. Android(9) 环信sdk手动集成(非添加easeui依赖快速集成)

    @先看看效果是不是自己想要的吧@ 准备:参考官方文档,先注册并创建应用 (这里再分享一下利用easeui快速集成的方法吧   --->   https://blog.csdn.net/qq_38 ...

  8. iOS 基于环信SDK实现即时通讯-文字聊天

    这里介绍集成环信SDK3.0自定义聊天页面,后面有练习项目地址 首先到环信官网下载环信SDK.由于后续会实现语音.视频,我这里使用的是带有语音的SDK 下载完成后把HyphenateFullSDK文件 ...

  9. Android项目小结---闹铃备忘录小开发知识点(附有:源码下载)

    一.闹铃功能介绍: 1.为每条备忘添加闹铃 2.备忘内容和闹铃信息存在SQL中 3.可删除每天记录和闹铃或者删除所有 4.到点闹铃启动,包括锁屏时和重开机 5.在桌面的创建一个widget类似便签那样 ...

最新文章

  1. ***PHP中判断变量为空的几种方法
  2. 带有互感线圈的基本串并联问题
  3. Tomcat虚拟主机
  4. ANT不完全总结,包含各种命令,ant例子等,转自:http://lavasoft.blog.51cto.com/62575/87306
  5. 创建数据库连接字符串的快截方法
  6. Android Studio之导入别人的module后config.gradle配置文件没有生效
  7. secoclient隧道保活超时或协商超时_推荐:承德市隧道led大屏厂家电话【联丰智慧科技】...
  8. nodejs中的模块的理解
  9. eclipse data source explorer 编辑触发器
  10. 网上书店系统活动设计
  11. 常见几种校验方法(CS和校验、CRC16、CRC32、BCC异或校验)
  12. Python,折线图,手写数字,图像反色、二值化、28X28
  13. 中国烟草和水烟行业市场供需与战略研究报告
  14. Jenkins邮件通知模板(Git修改版)
  15. 原生js实现九宫格抽奖
  16. Pytest框架系列——配置文件Pytest.ini
  17. 双基地mimo雷达matlab仿真,双基地MIMO雷达的干扰研究
  18. Mysql数据库优化的目的和从那放几个方面进行优化
  19. 以五子棋为背景的二维数组和稀疏数组(节省空间)的转换、用io流实现本地磁盘的存储
  20. PMP常考知识点核对单

热门文章

  1. 三种主流无线同屏技术介绍(Miracast、DLNA与AirPlay技术)
  2. 2021-07-18 Pythan 和 JMP 连接, Python 调用运用程序。
  3. php时间戳转换成日期格式,php时间戳如何转换为日期格式
  4. 股票的区分: 什么是 A股,B股,H股,N股?
  5. 用RVIZ2显示毫米波雷达点云
  6. Matlab入门实践
  7. Matlab绘图——填充相交区域
  8. 基于FPGA的SRIO的相关介绍和实现
  9. maven命令下载jar包
  10. HTTP和FTP的区别