編輯:關於Android編程
最近項目中集成即時聊天功能,挑來揀去,最終選擇環信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 接口,就一個方法如下:
/** * 監聽事件 */ @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 }
這三個監聽器就是監聽聯系人變化、群組變化、與環信服務器鏈接變化的監聽器,這三者的變化都會回調這三個監聽器裡面的相應的方法,方便開發者通過相應的方法采取相應的措施。
這三個監聽器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後,沒點確定按鈕然後按了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); } }
這部分代碼控制著觀察者,添加、刪除、通知每一個觀察者,當群組、好友、黑名單 通過環信服務器同步到客戶端之後,notify每個觀察者,然後觀察者接收到之後,刷新UI。這裡就是觀察者模式的經典應用!!!
聯系人列表看懂之後,其他的群組界面和回話歷史界面就不多說了。
3 聊天界面ChatActivity.java
這個類比較龐大,因為demo裡面把單聊和群聊、聊天室都集成到這一個界面中完成,代碼很龐大,但是不影響最終的集成,直接集成該類就可以實現功能。不多說。
附上界面:
圖一 回話歷史界面
圖二 通訊錄界面好友
圖三 設置界面
圖四 聊天界面
最後附上源碼下載
補充:
環信官方網站已經發布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圓形頭像圖Circle ImageView》需要處理的原始圖(pic): 使用CircleImageView處理後的圖(作為頭像):
通常也稱作套接字,用於描述IP地址和端口,是一個通信鏈的句柄。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定
序言OkHttp 的強大算是毋庸置疑了;OkHttp 基本在網絡層能完成任何事情,適用任何情況;正因為如此 OkHttp 每次構建一個請求的時候不得不寫大量的代碼來完成相
主要介紹內容:BitmapDrawable ShapeDrawable LayerDrawable StateListDrawable LevelListDrawable