1. 程式人生 > >環信實現基本聊天功能

環信實現基本聊天功能

用環信實現基本的聊天功能:

1、先到官網下載官方的demo:http://www.easemob.com/download/im

2、新建一個工程,然後將下面幾個檔案加到libs 裡面,沒有這個檔案的可以在app下新建一個。


3、配置資訊

在清單檔案 AndroidManifest.xml 里加入以下許可權,以及寫上你註冊的 AppKey。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.apple.huanxin">

    <!-- Required -->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <permission
        android:name="com.hyphenate.chatuidemo.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.hyphenate.chatuidemo.permission.C2D_MESSAGE" />

    <permission
        android:name="com.hyphenate.chatuidemo.permission.MIPUSH_RECEIVE"
        android:protectionLevel="signatureOrSystem" />

    <uses-permission android:name="com.hyphenate.chatuidemo.permission.MIPUSH_RECEIVE" />

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Login">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 設定環信應用的appKey -->
        <meta-data
            android:name="EASEMOB_APPKEY"
            android:value="lzan13#hxsdkdemo" />
        <!-- 宣告sdk所需的service -->
        <service
            android:name="com.hyphenate.chat.EMChatService"
            android:exported="true"
            tools:ignore="ExportedService" />

        <!-- 設定環信應用的 appkey 換成自己的 -->
        <meta-data
            android:name="EASEMOB_APPKEY"
            android:value="lzan13#hxsdkdemo" />
        <!-- 宣告sdk所需的service SDK核心功能 -->
        <service
            android:name="com.hyphenate.chat.EMChatService"
            android:exported="true" />
        <!-- 宣告sdk所需的receiver -->
        <receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_REMOVED" />

                <data android:scheme="package" />
            </intent-filter>
            <!-- 可選filter -->
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.USER_PRESENT" />
            </intent-filter>
        </receiver>

        <activity android:name=".MainActivity" />

        <!-- android:windowSoftInputMode="adjustResize|stateHidden"-->
        <activity android:name=".Chat"></activity>

    </application>

</manifest>

新建一個MyApplication檔案並繼承application 然後配置資訊初始化

public class MyApplication extends Application {

    // 上下文選單
    private Context mContext;

    // 記錄是否已經初始化
    private boolean isInit = false;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = this;

        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");
        // 設定自動登入
        /**
         * 自動登入在以下幾種情況下會被取消:

         使用者呼叫了 SDK 的登出動作;
         使用者在別的裝置上更改了密碼,導致此裝置上自動登入失敗;
         使用者的賬號被從伺服器端刪除;
         使用者從另一個裝置登入,把當前裝置上登入的使用者踢出。
         */
        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;
    }

}
4、實現註冊

註冊模式分兩種,開放註冊和授權註冊。可以登入環信管理後臺,在“應用概況”頁,切換註冊模式。

  • 只有開放註冊模式下,才可以客戶端註冊。開放註冊是為了測試使用,正式環境中不推薦使用該方式註冊環信 ID。
  • 授權註冊的流程應該是您伺服器通過環信提供的 REST API 註冊,之後儲存到您的伺服器或返回給客戶端。
註冊使用者名稱會自動轉為小寫字母,所以建議使用者名稱均以小寫註冊(強烈建議開發者通過後臺呼叫 REST API 去註冊環信 ID,客戶端註冊方法不提倡使用)。
new Thread(new Runnable() {
    public void run() {
      try {
         // 呼叫sdk註冊方法
         EMChatManager.getInstance().createAccountOnServer(username, pwd);
      } catch (final EaseMobException e) {
      //註冊失敗
        int errorCode=e.getErrorCode();
        if(errorCode==EMError.NONETWORK_ERROR){
            Toast.makeText(getApplicationContext(), "網路異常,請檢查網路!", Toast.LENGTH_SHORT).show();
        }else if(errorCode==EMError.USER_ALREADY_EXISTS){
            Toast.makeText(getApplicationContext(), "使用者已存在!", Toast.LENGTH_SHORT).show();
        }else if(errorCode==EMError.UNAUTHORIZED){
            Toast.makeText(getApplicationContext(), "註冊失敗,無許可權!", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(getApplicationContext(), "註冊失敗: " + e.getMessage(), Toast.LENGTH_SHORT).show();
      }
   }
}).start();
5、登陸實現

登入成功後需要呼叫 EMGroupManager.getInstance().loadAllGroups(); 和 EMChatManager.getInstance().loadAllConversations();

以上兩個方法是為了保證進入主頁面後本地會話和群組都 load 完畢。

另外如果登入過,APP 長期在後臺再進的時候也可能會導致載入到記憶體的群組和會話為空,可以在主頁面的 oncreate 裡也加上這兩句程式碼,當然,更好的辦法應該是放在程式的開屏頁

EMChatManager.getInstance().login(userName,password,new EMCallBack() {//回撥
    @Override
    public void onSuccess() {
        runOnUiThread(new Runnable() {
            public void run() {
                EMGroupManager.getInstance().loadAllGroups();
                EMChatManager.getInstance().loadAllConversations();
                Log.d("main", "登入聊天伺服器成功!");        
            }
        });
    }
 
    @Override
    public void onProgress(int progress, String status) {
 
    }
 
    @Override
    public void onError(int code, String message) {
        Log.d("main", "登入聊天伺服器失敗!");
    }
});
完整程式碼:佈局主要就是加兩個輸入框和兩個按鈕,並詳細了錯誤的資訊,具體錯誤可以到下面網址檢視:http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html#a09893124e1fbcd100a9583a4293957af
public class Login 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 (!Login.this.isFinishing()) {
                                mDialog.dismiss();
                            }
                            Toast.makeText(Login.this, "註冊成功", Toast.LENGTH_LONG).show();
                        }
                    });
                } catch (final HyphenateException e) {
                    e.printStackTrace();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (!Login.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(Login.this, "網路錯誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
                                    break;
                                // 使用者已存在
                                case EMError.USER_ALREADY_EXIST:
                                    Toast.makeText(Login.this, "使用者已存在 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
                                    break;
                                // 引數不合法,一般情況是username 使用了uuid導致,不能使用uuid註冊
                                case EMError.USER_ILLEGAL_ARGUMENT:
                                    Toast.makeText(Login.this, "引數不合法,一般情況是username 使用了uuid導致,不能使用uuid註冊 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
                                    break;
                                // 伺服器未知錯誤
                                case EMError.SERVER_UNKNOWN_ERROR:
                                    Toast.makeText(Login.this, "伺服器未知錯誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
                                    break;
                                case EMError.USER_REG_FAILED:
                                    Toast.makeText(Login.this, "賬戶註冊失敗 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
                                    break;
                                default:
                                    Toast.makeText(Login.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(Login.this, "使用者名稱和密碼不能為空", Toast.LENGTH_LONG).show();
            return;
        }

//        Intent intent = new Intent(Login.this, MainActivity.class);
//        startActivity(intent);
//        finish();
        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(Login.this, MainActivity.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(Login.this, "網路錯誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 無效的使用者名稱 101
                            case EMError.INVALID_USER_NAME:
                                Toast.makeText(Login.this, "無效的使用者名稱 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 無效的密碼 102
                            case EMError.INVALID_PASSWORD:
                                Toast.makeText(Login.this, "無效的密碼 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 使用者認證失敗,使用者名稱或密碼錯誤 202
                            case EMError.USER_AUTHENTICATION_FAILED:
                                Toast.makeText(Login.this, "使用者認證失敗,使用者名稱或密碼錯誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 使用者不存在 204
                            case EMError.USER_NOT_FOUND:
                                Toast.makeText(Login.this, "使用者不存在 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 無法訪問到伺服器 300
                            case EMError.SERVER_NOT_REACHABLE:
                                Toast.makeText(Login.this, "無法訪問到伺服器 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 等待伺服器響應超時 301
                            case EMError.SERVER_TIMEOUT:
                                Toast.makeText(Login.this, "等待伺服器響應超時 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 伺服器繁忙 302
                            case EMError.SERVER_BUSY:
                                Toast.makeText(Login.this, "伺服器繁忙 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            // 未知 Server 異常 303 一般斷網會出現這個錯誤
                            case EMError.SERVER_UNKNOWN_ERROR:
                                Toast.makeText(Login.this, "未知的伺服器異常 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                            default:
                                Toast.makeText(Login.this, "ml_sign_in_failed code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
                                break;
                        }
                    }
                });
            }

            @Override
            public void onProgress(int i, String s) {

            }
        });
    }
6、自動登陸

即首次登入成功後,不需要再次呼叫登入方法,在下次 APP 啟動時,SDK 會自動為您登入。並且如果您自動登入失敗,也可以讀取到之前的會話資訊(以上情況是在未呼叫登出的情況下實現的)。

SDK 中自動登入屬性預設是 true 開啟的,如果不需要自動登入,在初始化 SDK 之前,呼叫 EMChat.getInstance().setAutoLogin(false); 設定為 false 關閉。

自動登入在以下幾種情況下會被取消:

  • 使用者呼叫了 SDK 的登出動作;
  • 使用者在別的裝置上更改了密碼,導致此裝置上自動登入失敗;
  • 使用者的賬號被從伺服器端刪除;
  • 使用者從另一個裝置登入,把當前裝置上登入的使用者踢出。
重連實現:當掉線時,Android SDK 會自動重連,無需進行任何操作。
//註冊一個監聽連線狀態的listener
EMChatManager.getInstance().addConnectionListener(new MyConnectionListener());
 
//實現ConnectionListener介面
private class MyConnectionListener implements EMConnectionListener {
    @Override
    public void onConnected() {
    //已連線到伺服器
    }
    @Override
    public void onDisconnected(final int error) {
        runOnUiThread(new Runnable() {
 
            @Override
            public void run() {
                if(error == EMError.USER_REMOVED){
                    // 顯示帳號已經被移除
                }else if (error == EMError.CONNECTION_CONFLICT) {
                    // 顯示帳號在其他裝置登入
                } else {
                if (NetUtils.hasNetwork(MainActivity.this))
                    //連線不到聊天伺服器
                else
                    //當前網路不可用,請檢查網路設定
                }
            }
        });
    }
}
退出登入實現
 /**
     * 退出登陸
     */
    private void setLogout(){
      //  EMClient.getInstance().logout(true);//同步方法

        //非同步方法
        // 呼叫sdk的退出登入方法,第一個引數表示是否解綁推送的token,沒有使用推送或者被踢都要傳false
        EMClient.getInstance().logout(false, new EMCallBack() {
            @Override
            public void onSuccess() {
                Log.e("lzan13", "logout success");
                // 呼叫退出成功,結束app
                finish();
            }

            @Override
            public void onError(int i, String s) {
                Log.e("lzan13", "logout error " + i + " - " + s);
            }

            @Override
            public void onProgress(int i, String s) {

            }
        });
    }
完整程式碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button button;
    private Button logout;
    private EditText userName;
    private final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 判斷sdk是否登入成功過,並沒有退出和被踢,否則跳轉到登陸介面
        if (!EMClient.getInstance().isLoggedInBefore()) {
            Intent intent = new Intent(MainActivity.this, Login.class);
            startActivity(intent);
            finish();
            return;
        }

        initView();
    }

    private void initView() {

        button = (Button)findViewById(R.id.btn);
        button.setOnClickListener(this);

        logout = (Button)findViewById(R.id.logout);
        logout.setOnClickListener(this);

        userName = (EditText)findViewById(R.id.userName);

        //註冊一個監聽連線狀態的listener
        EMClient.getInstance().addConnectionListener(new MyConnectionListener());

    }

    //實現ConnectionListener介面
    private class MyConnectionListener implements EMConnectionListener {
        @Override
        public void onConnected() {
        }
        @Override
        public void onDisconnected(final int error) {
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    if(error == EMError.USER_REMOVED){
                        // 顯示帳號已經被移除
                    }else if (error == EMError.USER_LOGIN_ANOTHER_DEVICE) {
                        // 顯示帳號在其他裝置登入
                    } else {
                        if (NetUtils.hasNetwork(MainActivity.this)){
                            Toast.makeText(getApplication(),"連線不到聊天伺服器",Toast.LENGTH_SHORT).show();
                            Log.e(TAG,"連線不到聊天伺服器");
                            //連線不到聊天伺服器
                        } else{
                            Toast.makeText(getApplication(),"當前網路不可用,請檢查網路設定",Toast.LENGTH_SHORT).show();
                            Log.e(TAG,"當前網路不可用,請檢查網路設定");
                            //當前網路不可用,請檢查網路設定
                        }

                    }
                }
            });
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.logout:
                setLogout();

                break;

            case R.id.btn:
                if (!userName.getText().toString().equals("")) {
                    Intent intent = new Intent(this, Chat.class);
                    intent.putExtra("userName", userName.getText().toString());
                    startActivity(intent);
                }else {
                    Toast.makeText(getApplication(),"使用者名稱不能為空!",Toast.LENGTH_SHORT).show();
                }

                break;
        }
    }

    /**
     * 退出登陸
     */
    private void setLogout(){
      //  EMClient.getInstance().logout(true);//同步方法

        //非同步方法
        // 呼叫sdk的退出登入方法,第一個引數表示是否解綁推送的token,沒有使用推送或者被踢都要傳false
        EMClient.getInstance().logout(false, new EMCallBack() {
            @Override
            public void onSuccess() {
                Log.e("lzan13", "logout success");
                // 呼叫退出成功,結束app
                finish();
            }

            @Override
            public void onError(int i, String s) {
                Log.e("lzan13", "logout error " + i + " - " + s);
            }

            @Override
            public void onProgress(int i, String s) {

            }
        });
    }




}
7、實現基本的聊天功能

佈局程式碼

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:id="@+id/ec_layout_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/input_text"
                android:layout_width="match_parent"
                android:layout_height="50dip"
                android:layout_weight="3" />

            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="傳送" />

        </LinearLayout>

        <TextView
            android:id="@+id/text_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:maxLines="15"
            android:layout_marginBottom="100dip"
            android:scrollbars="vertical" />
    </RelativeLayout>


</LinearLayout>

首先獲取到從上一介面傳遞過來的使用者id

 Intent intent = getIntent();
        userName = intent.getStringExtra("userName");
        mMessageListener = this;
實現傳送訊息功能
 private void sendMessage(){

        String content = input_text.getText().toString();

        //建立一條文字訊息,content為訊息文字內容,toChatUsername為對方使用者或者群聊的id,後文皆是如此
        EMMessage message = EMMessage.createTxtSendMessage(content, userName);
      //  text_content.setText(content);
        text_content.setText(text_content.getText() + "\n"+ mConversation.getAllMessages().get(0).getFrom()+":"+ content + " - time: " + message.getMsgTime());
        //如果是群聊,設定chattype,預設是單聊
       // if (chatType == CHATTYPE_GROUP)
        //message.setChatType(EMMessage.ChatType.GroupChat);
        //傳送訊息
        EMClient.getInstance().chatManager().sendMessage(message);

        // 為訊息設定回撥
        message.setMessageStatusCallback(new EMCallBack() {
            @Override
            public void onSuccess() {
                // 訊息傳送成功,列印下日誌,正常操作應該去重新整理ui
                Log.e(TAG, "send message on success");
            }

            @Override
            public void onError(int i, String s) {
                // 訊息傳送失敗,列印下失敗的資訊,正常操作應該去重新整理ui
                Log.e(TAG, "send message on error " + i + " - " + s);
            }

            @Override
            public void onProgress(int i, String s) {
                // 訊息傳送進度,一般只有在傳送圖片和檔案等訊息才會有回撥,txt不回撥
            }
        });
    }
接收訊息,主要通過一個執行緒來接收並顯示
 /**
     * 收到新訊息
     * @param messages
     */
    @Override
    public void onMessageReceived(List<EMMessage> messages) {
        //收到訊息
        // 迴圈遍歷當前收到的訊息
        for (EMMessage message : messages) {
            if (message.getFrom().equals(userName)) {
                // 設定訊息為已讀
                mConversation.markMessageAsRead(message.getMsgId());

                // 因為訊息監聽回撥這裡是非ui執行緒,所以要用handler去更新ui
                Message msg = mHandler.obtainMessage();
                msg.what = 0;
                msg.obj = message;
                mHandler.sendMessage(msg);
            } else {
                // 如果訊息不是當前會話的訊息傳送通知欄通知
            }
        }
    }
/**
     * 自定義實現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();
                    // 將新的訊息內容和時間加入到下邊
                    Log.e("nsc", body.getMessage());
                    //text_content.setText(body.getMessage());
                    text_content.setText(text_content.getText() + "\n"+mConversation.getAllMessages().get(0).getTo()+":" + body.getMessage() + " - time: " + message.getMsgTime());
                    break;
            }
        }
    };
資料格式,即返回的資料格式
{
    "callId":"",//每個回撥的ID都不一樣
    "eventType":"chat_offline",//用於以後的擴充套件,現在只推送聊天訊息(離線和所有),以後會加入更多,比如使用者加入了某個群組
    "timestamp":0,//環信接收到此訊息的時間
    "chat_type":"groupchat", // 群聊,如果是單聊則為"chat"
    "group_id":'',//群聊時才有此引數
    "from":"",//訊息的傳送方
    "to":"",//訊息的接收方
    "msg_id":"",// 訊息ID
    "payload":{//訊息,與通過REST API傳送過來的一致
    },
    "securityVersion":"",//安全校驗版本,目前為1.0.0。忽略此引數,以後會改成Console後臺做設定
    "security":""//簽名。格式如下: MD5(callId+約定的key+timestamp),約定的key為123456,以後會改成Console後臺做設定
}
完整程式碼
public class Chat extends AppCompatActivity implements View.OnClickListener,EMMessageListener{

    private EditText input_text;
    private Button send;
    private TextView text_content;

    private String userName;// 當前聊天的 ID

    // 當前會話物件
    private EMConversation mConversation;

    // 訊息監聽器
    private EMMessageListener mMessageListener;

    private final String TAG = "Chat";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        Intent intent = getIntent();
        userName = intent.getStringExtra("userName");
        mMessageListener = this;

        initView();
        initConversation();
    }

    private void initView() {


        input_text = (EditText)findViewById(R.id.input_text);
        send = (Button)findViewById(R.id.send);
        send.setOnClickListener(this);

        text_content = (TextView)findViewById(R.id.text_content);
        // 設定textview可滾動,需配合xml佈局設定
        text_content.setMovementMethod(new ScrollingMovementMethod());

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){

            case R.id.send:
                    sendMessage();
                break;

            default:

                break;

        }
    }

    /**
     * 初始化會話物件,並且根據需要載入更多訊息
     */
    private void initConversation() {

        /**
         * 初始化會話物件,這裡有三個引數麼,
         * 第一個表示會話的當前聊天的 useranme 或者 groupid
         * 第二個是繪畫型別可以為空
         * 第三個表示如果會話不存在是否建立
         */
        mConversation = EMClient.getInstance().chatManager().getConversation(userName, null, true);

        for (int i = 0; i<mConversation.getAllMessages().size();i++){
            Log.e(TAG,"mConversation="+mConversation.getAllMessages().get(i).toString()+"\n");
        }

        // 設定當前會話未讀數為 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();
            // 將訊息內容和時間顯示出來
            //text_content.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();
                    // 將新的訊息內容和時間加入到下邊
                    Log.e("nsc", body.getMessage());
                    //text_content.setText(body.getMessage());
                    text_content.setText(text_content.getText() + "\n"+mConversation.getAllMessages().get(0).getTo()+":" + body.getMessage() + " - time: " + message.getMsgTime());
                    break;
            }
        }
    };

    private void sendMessage(){

        String content = input_text.getText().toString();

        //建立一條文字訊息,content為訊息文字內容,toChatUsername為對方使用者或者群聊的id,後文皆是如此
        EMMessage message = EMMessage.createTxtSendMessage(content, userName);
      //  text_content.setText(content);
        text_content.setText(text_content.getText() + "\n"+ mConversation.getAllMessages().get(0).getFrom()+":"+ content + " - time: " + message.getMsgTime());
        //如果是群聊,設定chattype,預設是單聊
       // if (chatType == CHATTYPE_GROUP)
        //message.setChatType(EMMessage.ChatType.GroupChat);
        //傳送訊息
        EMClient.getInstance().chatManager().sendMessage(message);

        // 為訊息設定回撥
        message.setMessageStatusCallback(new EMCallBack() {
            @Override
            public void onSuccess() {
                // 訊息傳送成功,列印下日誌,正常操作應該去重新整理ui
                Log.e(TAG, "send message on success");
            }

            @Override
            public void onError(int i, String s) {
                // 訊息傳送失敗,列印下失敗的資訊,正常操作應該去重新整理ui
                Log.e(TAG, "send message on error " + i + " - " + s);
            }

            @Override
            public void onProgress(int i, String s) {
                // 訊息傳送進度,一般只有在傳送圖片和檔案等訊息才會有回撥,txt不回撥
            }
        });
    }



    @Override
    protected void onResume() {
        super.onResume();
        // 新增訊息監聽
        EMClient.getInstance().chatManager().addMessageListener(mMessageListener);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 移除訊息監聽
        EMClient.getInstance().chatManager().removeMessageListener(mMessageListener);
    }

    /**
     * 收到新訊息
     * @param messages
     */
    @Override
    public void onMessageReceived(List<EMMessage> messages) {
        //收到訊息
        // 迴圈遍歷當前收到的訊息
        for (EMMessage message : messages) {
            if (message.getFrom().equals(userName)) {
                // 設定訊息為已讀
                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.e("nsc", body.action());
        }
    }

    /**
     * 收到新的已讀回執
     * @param list
     */
    @Override
    public void onMessageReadAckReceived(List<EMMessage> list) {

    }

    /**
     * 收到新的傳送回執
     * @param list
     */
    @Override
    public void onMessageDeliveryAckReceived(List<EMMessage> list) {

    }

    /**
     * 訊息的狀態改變
     * @param emMessage
     * @param o
     */
    @Override
    public void onMessageChanged(EMMessage emMessage, Object o) {

    }
到此就可以實現基本的聊天了。程式碼下載地址:點選開啟連結