Android 實現單聊功能_At_Swim
阿新 • • 發佈:2019-01-09
這個單聊特別特別好用
在這裡分享給大家
專案的 Application類,做一些專案的初始化操作,比如sdk的初始化等
public class ECApplication extends Application { // 上下文選單 private Context mContext; // 記錄是否已經初始化 private boolean isInit = false; @Override public void onCreate() { super.onCreate(); mContext = this; // 初始化環信SDK initEasemob(); } /** * */ private void initEasemob() { // 獲取當前程序 id 並取得程序名 int pid = android.os.Process.myPid(); String processAppName = getAppName(pid); /** * 如果app啟用了遠端的service,此application:onCreate會被呼叫2次 * 為了防止環信SDK被初始化2次,加此判斷會保證SDK被初始化1次* 預設的app會在以包名為預設的process name下執行,如果查到的process name不是app的process name就立即返回 */ if (processAppName == null || !processAppName.equalsIgnoreCase(mContext.getPackageName())) { // 則此application的onCreate 是被service 呼叫的,直接返回 return; } if (isInit) { return; } // 呼叫初始化方法初始化sdk EMClient.getInstance().init(mContext, initOptions()); // 設定開啟debug模式 EMClient.getInstance().setDebugMode(true); // 設定初始化已經完成 isInit = true; } /** * SDK初始化的一些配置 * 關於 EMOptions 可以參考官方的 API 文件 * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1chat_1_1_e_m_options.html */ private EMOptions initOptions() { EMOptions options = new EMOptions(); // 設定Appkey,如果配置檔案已經配置,這裡可以不用設定 // options.setAppKey("lzan13#hxsdkdemo"); // 設定自動登入 options.setAutoLogin(true); // 設定是否需要傳送已讀回執 options.setRequireAck(true); // 設定是否需要傳送回執, options.setRequireDeliveryAck(true); // 設定是否需要伺服器收到訊息確認 options.setRequireServerAck(true); // 設定是否根據伺服器時間排序,預設是true options.setSortMessageByServerTime(false); // 收到好友申請是否自動同意,如果是自動同意就不會收到好友請求的回撥,因為sdk會自動處理,預設為true options.setAcceptInvitationAlways(false); // 設定是否自動接收加群邀請,如果設定了當收到群邀請會自動同意加入 options.setAutoAcceptGroupInvitation(false); // 設定(主動或被動)退出群組時,是否刪除群聊聊天記錄 options.setDeleteMessagesAsExitGroup(false); // 設定是否允許聊天室的Owner 離開並刪除聊天室的會話 options.allowChatroomOwnerLeave(true); // 設定google GCM推送id,國內可以不用設定 // options.setGCMNumber(MLConstants.ML_GCM_NUMBER); // 設定整合小米推送的appid和appkey // options.setMipushConfig(MLConstants.ML_MI_APP_ID, MLConstants.ML_MI_APP_KEY); return options; } /** * 根據Pid獲取當前程序的名字,一般就是當前app的包名 * * @param pid 程序的id * @return 返回程序的名字 */ private String getAppName(int pid) { String processName = null; ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List list = activityManager.getRunningAppProcesses(); Iterator i = list.iterator(); while (i.hasNext()) { ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next()); try { if (info.pid == pid) { // 根據程序的資訊獲取當前程序的名字 processName = info.processName; // 返回當前程序名 return processName; } } catch (Exception e) { e.printStackTrace(); } } // 沒有匹配的項,返回為null return null; } }
public class ECChatActivity extends AppCompatActivity implements EMMessageListener { // 聊天資訊輸入框 private EditText mInputEdit; // 傳送按鈕 private Button mSendBtn; // 顯示內容的 TextView private TextView mContentText; // 訊息監聽器 private EMMessageListener mMessageListener; // 當前聊天的 ID private String mChatId; // 當前會話物件 private EMConversation mConversation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); // 獲取當前會話的username(如果是群聊就是群id) mChatId = getIntent().getStringExtra("ec_chat_id"); mMessageListener = this; initView(); initConversation(); } /** * 初始化介面 */ private void initView() { mInputEdit = (EditText) findViewById(R.id.ec_edit_message_input); mSendBtn = (Button) findViewById(R.id.ec_btn_send); mContentText = (TextView) findViewById(R.id.ec_text_content); // 設定textview可滾動,需配合xml佈局設定 mContentText.setMovementMethod(new ScrollingMovementMethod()); // 設定傳送按鈕的點選事件 mSendBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String content = mInputEdit.getText().toString().trim(); if (!TextUtils.isEmpty(content)) { mInputEdit.setText(""); // 建立一條新訊息,第一個引數為訊息內容,第二個為接受者username EMMessage message = EMMessage.createTxtSendMessage(content, mChatId); // 將新的訊息內容和時間加入到下邊 mContentText.setText(mContentText.getText() + "\n傳送:" + content + " - time: " + message.getMsgTime()); // 呼叫傳送訊息的方法 EMClient.getInstance().chatManager().sendMessage(message); // 為訊息設定回撥 message.setMessageStatusCallback(new EMCallBack() { @Override public void onSuccess() { // 訊息傳送成功,列印下日誌,正常操作應該去重新整理ui Log.i("lzan13", "send message on success"); } @Override public void onError(int i, String s) { // 訊息傳送失敗,列印下失敗的資訊,正常操作應該去重新整理ui Log.i("lzan13", "send message on error " + i + " - " + s); } @Override public void onProgress(int i, String s) { // 訊息傳送進度,一般只有在傳送圖片和檔案等訊息才會有回撥,txt不回撥 } }); } } }); } /** * 初始化會話物件,並且根據需要載入更多訊息 */ private void initConversation() { /** * 初始化會話物件,這裡有三個引數麼, * 第一個表示會話的當前聊天的 useranme 或者 groupid * 第二個是繪畫型別可以為空 * 第三個表示如果會話不存在是否建立 */ mConversation = EMClient.getInstance().chatManager().getConversation(mChatId, null, true); // 設定當前會話未讀數為 0 mConversation.markAllMessagesAsRead(); int count = mConversation.getAllMessages().size(); if (count < mConversation.getAllMsgCount() && count < 20) { // 獲取已經在列表中的最上邊的一條訊息id String msgId = mConversation.getAllMessages().get(0).getMsgId(); // 分頁載入更多訊息,需要傳遞已經載入的訊息的最上邊一條訊息的id,以及需要載入的訊息的條數 mConversation.loadMoreMsgFromDB(msgId, 20 - count); } // 開啟聊天介面獲取最後一條訊息內容並顯示 if (mConversation.getAllMessages().size() > 0) { EMMessage messge = mConversation.getLastMessage(); EMTextMessageBody body = (EMTextMessageBody) messge.getBody(); // 將訊息內容和時間顯示出來 mContentText.setText("聊天記錄:" + body.getMessage() + " - time: " + mConversation.getLastMessage().getMsgTime()); } } /** * 自定義實現Handler,主要用於重新整理UI操作 */ Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: EMMessage message = (EMMessage) msg.obj; // 這裡只是簡單的demo,也只是測試文字訊息的收發,所以直接將body轉為EMTextMessageBody去獲取內容 EMTextMessageBody body = (EMTextMessageBody) message.getBody(); // 將新的訊息內容和時間加入到下邊 mContentText.setText(mContentText.getText() + "\n接收:" + body.getMessage() + " - time: " + message.getMsgTime()); break; } } }; @Override protected void onResume() { super.onResume(); // 新增訊息監聽 EMClient.getInstance().chatManager().addMessageListener(mMessageListener); } @Override protected void onStop() { super.onStop(); // 移除訊息監聽 EMClient.getInstance().chatManager().removeMessageListener(mMessageListener); } /** * --------------------------------- Message Listener ------------------------------------- * 環信訊息監聽主要方法 */ /** * 收到新訊息 * * @param list 收到的新訊息集合 */ @Override public void onMessageReceived(List<EMMessage> list) { // 迴圈遍歷當前收到的訊息 for (EMMessage message : list) { if (message.getFrom().equals(mChatId)) { // 設定訊息為已讀 mConversation.markMessageAsRead(message.getMsgId()); // 因為訊息監聽回撥這裡是非ui執行緒,所以要用handler去更新ui Message msg = mHandler.obtainMessage(); msg.what = 0; msg.obj = message; mHandler.sendMessage(msg); } else { // 如果訊息不是當前會話的訊息傳送通知欄通知 } } } /** * 收到新的 CMD 訊息 * * @param list */ @Override public void onCmdMessageReceived(List<EMMessage> list) { for (int i = 0; i < list.size(); i++) { // 透傳訊息 EMMessage cmdMessage = list.get(i); EMCmdMessageBody body = (EMCmdMessageBody) cmdMessage.getBody(); Log.i("lzan13", body.action()); } } /** * 收到新的已讀回執 * * @param list 收到訊息已讀回執 */ @Override public void onMessageReadAckReceived(List<EMMessage> list) { } /** * 收到新的傳送回執 * TODO 無效 暫時有bug * * @param list 收到傳送回執的訊息集合 */ @Override public void onMessageDeliveryAckReceived(List<EMMessage> list) { } /** * 訊息的狀態改變 * * @param message 發生改變的訊息 * @param object 包含改變的訊息 */ @Override public void onMessageChanged(EMMessage message, Object object) { } }
對應的佈局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="net.melove.demo.easechat.ECChatActivity"> <!--輸入框--> <RelativeLayout android:id="@+id/ec_layout_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"> <Button android:id="@+id/ec_btn_send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Send"/> <EditText android:id="@+id/ec_edit_message_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_toLeftOf="@id/ec_btn_send"/> </RelativeLayout> <!--展示訊息內容--> <TextView android:id="@+id/ec_text_content" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/ec_layout_input" android:maxLines="15" android:scrollbars="vertical"/> </RelativeLayout>
public class ECLoginActivity extends AppCompatActivity { // 彈出框 private ProgressDialog mDialog; // username 輸入框 private EditText mUsernameEdit; // 密碼輸入框 private EditText mPasswordEdit; // 註冊按鈕 private Button mSignUpBtn; // 登入按鈕 private Button mSignInBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); initView(); } /** * 初始化介面控制元件 */ private void initView() { mUsernameEdit = (EditText) findViewById(R.id.ec_edit_username); mPasswordEdit = (EditText) findViewById(R.id.ec_edit_password); mSignUpBtn = (Button) findViewById(R.id.ec_btn_sign_up); mSignUpBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { signUp(); } }); mSignInBtn = (Button) findViewById(R.id.ec_btn_sign_in); mSignInBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { signIn(); } }); } /** * 註冊方法 */ private void signUp() { // 註冊是耗時過程,所以要顯示一個dialog來提示下使用者 mDialog = new ProgressDialog(this); mDialog.setMessage("註冊中,請稍後..."); mDialog.show(); new Thread(new Runnable() { @Override public void run() { try { String username = mUsernameEdit.getText().toString().trim(); String password = mPasswordEdit.getText().toString().trim(); EMClient.getInstance().createAccount(username, password); runOnUiThread(new Runnable() { @Override public void run() { if (!ECLoginActivity.this.isFinishing()) { mDialog.dismiss(); } Toast.makeText(ECLoginActivity.this, "註冊成功", Toast.LENGTH_LONG).show(); } }); } catch (final HyphenateException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { if (!ECLoginActivity.this.isFinishing()) { mDialog.dismiss(); } /** * 關於錯誤碼可以參考官方api詳細說明 * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html */ int errorCode = e.getErrorCode(); String message = e.getMessage(); Log.d("lzan13", String.format("sign up - errorCode:%d, errorMsg:%s", errorCode, e.getMessage())); switch (errorCode) { // 網路錯誤 case EMError.NETWORK_ERROR: Toast.makeText(ECLoginActivity.this, "網路錯誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; // 使用者已存在 case EMError.USER_ALREADY_EXIST: Toast.makeText(ECLoginActivity.this, "使用者已存在 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; // 引數不合法,一般情況是username 使用了uuid導致,不能使用uuid註冊 case EMError.USER_ILLEGAL_ARGUMENT: Toast.makeText(ECLoginActivity.this, "引數不合法,一般情況是username 使用了uuid導致,不能使用uuid註冊 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; // 伺服器未知錯誤 case EMError.SERVER_UNKNOWN_ERROR: Toast.makeText(ECLoginActivity.this, "伺服器未知錯誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; case EMError.USER_REG_FAILED: Toast.makeText(ECLoginActivity.this, "賬戶註冊失敗 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; default: Toast.makeText(ECLoginActivity.this, "ml_sign_up_failed code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show(); break; } } }); } catch (Exception e) { e.printStackTrace(); } } }).start(); } /** * 登入方法 */ private void signIn() { mDialog = new ProgressDialog(this); mDialog.setMessage("正在登陸,請稍後..."); mDialog.show(); String username = mUsernameEdit.getText().toString().trim(); String password = mPasswordEdit.getText().toString().trim(); if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { Toast.makeText(ECLoginActivity.this, "使用者名稱和密碼不能為空", Toast.LENGTH_LONG).show(); return; } EMClient.getInstance().login(username, password, new EMCallBack() { /** * 登陸成功的回撥 */ @Override public void onSuccess() { runOnUiThread(new Runnable() { @Override public void run() { mDialog.dismiss(); // 載入所有會話到記憶體 EMClient.getInstance().chatManager().loadAllConversations(); // 載入所有群組到記憶體,如果使用了群組的話 // EMClient.getInstance().groupManager().loadAllGroups(); // 登入成功跳轉介面 Intent intent = new Intent(ECLoginActivity.this, ECMainActivity.class); startActivity(intent); finish(); } }); } /** * 登陸錯誤的回撥 * @param i * @param s */ @Override public void onError(final int i, final String s) { runOnUiThread(new Runnable() { @Override public void run() { mDialog.dismiss(); Log.d("lzan13", "登入失敗 Error code:" + i + ", message:" + s); /** * 關於錯誤碼可以參考官方api詳細說明 * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html */ switch (i) { // 網路異常 2 case EMError.NETWORK_ERROR: Toast.makeText(ECLoginActivity.this, "網路錯誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 無效的使用者名稱 101 case EMError.INVALID_USER_NAME: Toast.makeText(ECLoginActivity.this, "無效的使用者名稱 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 無效的密碼 102 case EMError.INVALID_PASSWORD: Toast.makeText(ECLoginActivity.this, "無效的密碼 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 使用者認證失敗,使用者名稱或密碼錯誤 202 case EMError.USER_AUTHENTICATION_FAILED: Toast.makeText(ECLoginActivity.this, "使用者認證失敗,使用者名稱或密碼錯誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 使用者不存在 204 case EMError.USER_NOT_FOUND: Toast.makeText(ECLoginActivity.this, "使用者不存在 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 無法訪問到伺服器 300 case EMError.SERVER_NOT_REACHABLE: Toast.makeText(ECLoginActivity.this, "無法訪問到伺服器 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 等待伺服器響應超時 301 case EMError.SERVER_TIMEOUT: Toast.makeText(ECLoginActivity.this, "等待伺服器響應超時 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 伺服器繁忙 302 case EMError.SERVER_BUSY: Toast.makeText(ECLoginActivity.this, "伺服器繁忙 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; // 未知 Server 異常 303 一般斷網會出現這個錯誤 case EMError.SERVER_UNKNOWN_ERROR: Toast.makeText(ECLoginActivity.this, "未知的伺服器異常 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; default: Toast.makeText(ECLoginActivity.this, "ml_sign_in_failed code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); break; } } }); } @Override public void onProgress(int i, String s) { } }); } }
對應佈局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="net.melove.demo.easechat.ECLoginActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/ec_edit_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="username"/> <EditText android:id="@+id/ec_edit_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="password"/> <Button android:id="@+id/ec_btn_sign_up" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="註冊"/> <Button android:id="@+id/ec_btn_sign_in" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="登入"/> </LinearLayout> </RelativeLayout>
public class ECMainActivity extends AppCompatActivity { // 發起聊天 username 輸入框 private EditText mChatIdEdit; // 發起聊天 private Button mStartChatBtn; // 退出登入 private Button mSignOutBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 判斷sdk是否登入成功過,並沒有退出和被踢,否則跳轉到登陸介面 if (!EMClient.getInstance().isLoggedInBefore()) { Intent intent = new Intent(ECMainActivity.this, ECLoginActivity.class); startActivity(intent); finish(); return; } setContentView(R.layout.activity_main); initView(); } /** * 初始化介面 */ private void initView() { mChatIdEdit = (EditText) findViewById(R.id.ec_edit_chat_id); mStartChatBtn = (Button) findViewById(R.id.ec_btn_start_chat); mStartChatBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 獲取我們發起聊天的者的username String chatId = mChatIdEdit.getText().toString().trim(); if (!TextUtils.isEmpty(chatId)) { // 獲取當前登入使用者的 username String currUsername = EMClient.getInstance().getCurrentUser(); if (chatId.equals(currUsername)) { Toast.makeText(ECMainActivity.this, "不能和自己聊天", Toast.LENGTH_SHORT).show(); return; } // 跳轉到聊天介面,開始聊天 Intent intent = new Intent(ECMainActivity.this, ECChatActivity.class); intent.putExtra("ec_chat_id", chatId); startActivity(intent); } else { Toast.makeText(ECMainActivity.this, "Username 不能為空", Toast.LENGTH_LONG).show(); } } }); mSignOutBtn = (Button) findViewById(R.id.ec_btn_sign_out); mSignOutBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { signOut(); } }); } /** * 退出登入 */ private void signOut() { // 呼叫sdk的退出登入方法,第一個引數表示是否解綁推送的token,沒有使用推送或者被踢都要傳false EMClient.getInstance().logout(false, new EMCallBack() { @Override public void onSuccess() { Log.i("lzan13", "logout success"); // 呼叫退出成功,結束app finish(); } @Override public void onError(int i, String s) { Log.i("lzan13", "logout error " + i + " - " + s); } @Override public void onProgress(int i, String s) { } }); } }
對應佈局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="net.melove.demo.easechat.ECMainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/ec_edit_chat_id" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="對方的username"/> <Button android:id="@+id/ec_btn_start_chat" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="發起聊天"/> <Button android:id="@+id/ec_btn_sign_out" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="退出登入"/> </LinearLayout> </RelativeLayout>這是一個完整的單聊專案,感覺的話請評論