Android開發(fā)集成聊天環(huán)信SDK3.x簡單開始

首次發(fā)表 2017/1/4
更新日期 2018/5/9 升級 sdk 到3.4.0 及開發(fā)環(huán)境的更新

前言

環(huán)信已經(jīng)發(fā)部了SDK3.x版本,SDK3.x相對于SDK2.x來說是整個進行了重寫队寇,API變化還是比較大的门躯,已經(jīng)熟悉SDK2.x的開發(fā)者在使用新的SDK3.x還是會遇到不少問題的荷科,不過還好官方給出了SDK2.x升級SDK3.x指南笔链,已經(jīng)熟悉SDK2.x開發(fā)者可以根據(jù)文檔了解SDK3.x的變化舰蟆,新集成的開發(fā)者可以直接參考SDK3.x進行集成趣惠;
這里簡單的實現(xiàn)了sdk的初始化以及注冊登錄和收發(fā)消息,不過ui上沒有做很好的處理

如果你還是用的Eclipse身害,可以下載AndroidStudio嘗試下味悄,如果你上不了Android官網(wǎng),不懂怎么翻墻可以找下國內(nèi)開發(fā)提供的一些地址塌鸯,文末也會整理一些地址

看效果圖

ec-demo.gif

說下我當前開發(fā)環(huán)境

這里并不是一定要按照我的配置來侍瑟,只是說下當前項目開發(fā)運行的環(huán)境,如果你的開發(fā)環(huán)境不同可能需要自己修改下項目配置build.gradle文件

系統(tǒng) Mac
AndroidStudio 3.0.0
Gradle 4.1(跟隨AndroidStudio 一起更新)
Android compileSdkVersion 27
Android buildToolsVersion 27.0.3
Android Support 最新
環(huán)信 SDK 3.4.0

開始集成

這次要實現(xiàn) SDK的初始化SDK端的注冊登錄涨颜、消息的發(fā)送和監(jiān)聽這三步

SDK的初始化

這個初始化時在Application里進行的费韭,這里定義了一個方法去初始化環(huán)信的SDK,并在其中進行了一些設(shè)置

package net.melove.demo.easechat;

import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;

import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMOptions;

import java.util.Iterator;
import java.util.List;

/**
 * Created by lz on 2016/4/16.
 * 項目的 Application類庭瑰,做一些項目的初始化操作星持,比如sdk的初始化等
 */
public class ECApplication extends Application {

    // 上下文菜單
    private Context mContext;

    // 記錄是否已經(jīng)初始化
    private boolean isInit = false;

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

        // 初始化環(huán)信SDK
        initEasemob();
    }

    /**
     *
     */
    private void initEasemob() {
        // 獲取當前進程 id 并取得進程名
        int pid = android.os.Process.myPid();
        String processAppName = getAppName(pid);
        /**
         * 如果app啟用了遠程的service,此application:onCreate會被調(diào)用2次
         * 為了防止環(huán)信SDK被初始化2次弹灭,加此判斷會保證SDK被初始化1次
         * 默認的app會在以包名為默認的process name下運行督暂,如果查到的process name不是app的process name就立即返回
         */
        if (processAppName == null || !processAppName.equalsIgnoreCase(mContext.getPackageName())) {
            // 則此application的onCreate 是被service 調(diào)用的,直接返回
            return;
        }
        if (isInit) {
            return;
        }
        /**
         * SDK初始化的一些配置
         * 關(guān)于 EMOptions 可以參考官方的 API 文檔
         * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1chat_1_1_e_m_options.html
         */
        EMOptions options = new EMOptions();
        // 設(shè)置Appkey鲤屡,如果配置文件已經(jīng)配置损痰,這里可以不用設(shè)置
        // options.setAppKey("lzan13#hxsdkdemo");
        // 設(shè)置自動登錄
        options.setAutoLogin(true);
        // 設(shè)置是否需要發(fā)送已讀回執(zhí)
        options.setRequireAck(true);
        // 設(shè)置是否需要發(fā)送回執(zhí),TODO 這個暫時有bug酒来,上層收不到發(fā)送回執(zhí)
        options.setRequireDeliveryAck(true);
        // 設(shè)置是否需要服務器收到消息確認
        options.setRequireServerAck(true);
        // 收到好友申請是否自動同意卢未,如果是自動同意就不會收到好友請求的回調(diào),因為sdk會自動處理堰汉,默認為true
        options.setAcceptInvitationAlways(false);
        // 設(shè)置是否自動接收加群邀請辽社,如果設(shè)置了當收到群邀請會自動同意加入
        options.setAutoAcceptGroupInvitation(false);
        // 設(shè)置(主動或被動)退出群組時,是否刪除群聊聊天記錄
        options.setDeleteMessagesAsExitGroup(false);
        // 設(shè)置是否允許聊天室的Owner 離開并刪除聊天室的會話
        options.allowChatroomOwnerLeave(true);
        // 設(shè)置google GCM推送id翘鸭,國內(nèi)可以不用設(shè)置
        // options.setGCMNumber(MLConstants.ML_GCM_NUMBER);
        // 設(shè)置集成小米推送的appid和appkey
        // options.setMipushConfig(MLConstants.ML_MI_APP_ID, MLConstants.ML_MI_APP_KEY);

        // 調(diào)用初始化方法初始化sdk
        EMClient.getInstance().init(mContext, options);

        // 設(shè)置開啟debug模式
        EMClient.getInstance().setDebugMode(true);

        // 設(shè)置初始化已經(jīng)完成
        isInit = true;
    }

    /**
     * 根據(jù)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) {
                    // 根據(jù)進程的信息獲取當前進程的名字
                    processName = info.processName;
                    // 返回當前進程名
                    return processName;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 沒有匹配的項,返回為null
        return null;
    }
}

主界面

app啟動后默認會進入到ECMainActivity就乓,不過在主界面會先判斷一下是否登錄成功過汉匙,如果沒有,就會跳轉(zhuǎn)到登錄幾面生蚁,然后我們調(diào)用登錄的時候噩翠,在登錄方法的onSuccess()回調(diào)中我們進行了界面的跳轉(zhuǎn),跳轉(zhuǎn)到主界面邦投,在主界面我們可以發(fā)起回話伤锚;
看下主界面的詳細代碼實現(xiàn):

package net.melove.demo.easechat;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.hyphenate.EMCallBack;
import com.hyphenate.chat.EMClient;

public class ECMainActivity extends AppCompatActivity {

    // 發(fā)起聊天 username 輸入框
    private EditText mChatIdEdit;
    // 發(fā)起聊天
    private Button mStartChatBtn;
    // 退出登錄
    private Button mSignOutBtn;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 判斷sdk是否登錄成功過,并沒有退出和被踢志衣,否則跳轉(zhuǎn)到登陸界面
        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) {
                // 獲取我們發(fā)起聊天的者的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;
                    }
                    // 跳轉(zhuǎn)到聊天界面屯援,開始聊天
                    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() {
        // 調(diào)用sdk的退出登錄方法,第一個參數(shù)表示是否解綁推送的token念脯,沒有使用推送或者被踢都要傳false
        EMClient.getInstance().logout(false, new EMCallBack() {
            @Override
            public void onSuccess() {
                Log.i("lzan13", "logout success");
                // 調(diào)用退出成功狞洋,結(jié)束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) {

            }
        });
    }
}

SDK端的注冊登錄

SDK初始化做完之后,就是需要進行環(huán)信的登錄了和二,登錄了才能使用環(huán)信的功能徘铝,才能收發(fā)消息,有不少人經(jīng)常問,不注冊賬戶能使用么惕它,這是聊天sdk怕午,不注冊賬戶你拿什么聊天呢!
登錄調(diào)用EMClient.getInstance().login(username, password, callback);此方法是一個異步方法淹魄,所以需要設(shè)置EMCallback回調(diào)來接收登錄結(jié)果郁惜;
注冊調(diào)用EMClient.getInstance().createAccount(username, password);此方法是同步方法,需要自己創(chuàng)建新線程去調(diào)用甲锡,不能放在UI線程直接調(diào)用兆蕉;
因為只是個簡單的demo,這邊把登錄和注冊都卸載了LoginActivity類里缤沦,這個方法中對調(diào)用環(huán)信sdk的方法返回錯誤值做了一些判斷虎韵,具體錯誤信息可以參考官方文檔:
環(huán)信SDK3.x EMError

package net.melove.demo.easechat;

import android.app.ProgressDialog;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.hyphenate.EMCallBack;
import com.hyphenate.EMError;
import com.hyphenate.chat.EMClient;
import com.hyphenate.exceptions.HyphenateException;

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();
                            }
                            /**
                             * 關(guān)于錯誤碼可以參考官方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) {
                            // 網(wǎng)絡錯誤
                            case EMError.NETWORK_ERROR:
                                Toast.makeText(ECLoginActivity.this, "網(wǎng)絡錯誤 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;
                            // 參數(shù)不合法包蓝,一般情況是username 使用了uuid導致,不能使用uuid注冊
                            case EMError.USER_ILLEGAL_ARGUMENT:
                                Toast.makeText(ECLoginActivity.this, "參數(shù)不合法企量,一般情況是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();
        EMClient.getInstance().login(username, password, new EMCallBack() {
            /**
             * 登陸成功的回調(diào)
             */
            @Override
            public void onSuccess() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mDialog.dismiss();

                        // 加載所有會話到內(nèi)存
                        EMClient.getInstance().chatManager().loadAllConversations();
                        // 加載所有群組到內(nèi)存届巩,如果使用了群組的話
                        // EMClient.getInstance().groupManager().loadAllGroups();

                        // 登錄成功跳轉(zhuǎn)界面
                        Intent intent = new Intent(ECLoginActivity.this, ECMainActivity.class);
                        startActivity(intent);
                        finish();
                    }
                });
            }

            /**
             * 登陸錯誤的回調(diào)
             * @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);
                        /**
                         * 關(guān)于錯誤碼可以參考官方api詳細說明
                         * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html
                         */
                        switch (i) {
                        // 網(wǎng)絡異常 2
                        case EMError.NETWORK_ERROR:
                            Toast.makeText(ECLoginActivity.this, "網(wǎng)絡錯誤 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 一般斷網(wǎng)會出現(xiàn)這個錯誤
                        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) {

            }
        });
    }
}

消息的發(fā)送和監(jiān)聽

實現(xiàn)消息的接收需要添加EMMessageListener消息監(jiān)聽接口恕汇,我們在需要監(jiān)聽的地方要實現(xiàn)這個接口腕唧,并實現(xiàn)接口里邊的幾個回調(diào)方法:

onMessageReceived(List<EMMessage> list)新消息的回調(diào)
onCmdMessageReceived(List<EMMessage> list)新的透傳消息回調(diào)
onMessageReadAckReceived(List<EMMessage> list)消息已讀回調(diào)
onMessageDeliveryAckReceived(List<EMMessage> list)消息已發(fā)送回調(diào)
onMessageChanged(EMMessage message, Object object)消息狀態(tài)改變回調(diào)

下邊是聊天界面消息監(jiān)聽與發(fā)送的完整實現(xiàn),代碼注釋比較詳細瘾英,不再一一解釋

package net.melove.demo.easechat;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.hyphenate.EMCallBack;
import com.hyphenate.EMMessageListener;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMCmdMessageBody;
import com.hyphenate.chat.EMConversation;
import com.hyphenate.chat.EMMessage;
import com.hyphenate.chat.EMTextMessageBody;

import java.util.List;

public class ECChatActivity extends AppCompatActivity implements EMMessageListener {

    // 聊天信息輸入框
    private EditText mInputEdit;
    // 發(fā)送按鈕
    private Button mSendBtn;

    // 顯示內(nèi)容的 TextView
    private TextView mContentText;

    // 消息監(jiān)聽器
    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);
        // 設(shè)置textview可滾動四苇,需配合xml布局設(shè)置
        mContentText.setMovementMethod(new ScrollingMovementMethod());

        // 設(shè)置發(fā)送按鈕的點擊事件
        mSendBtn.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                String content = mInputEdit.getText().toString().trim();
                if (!TextUtils.isEmpty(content)) {
                    mInputEdit.setText("");
                    // 創(chuàng)建一條新消息,第一個參數(shù)為消息內(nèi)容方咆,第二個為接受者username
                    EMMessage message = EMMessage.createTxtSendMessage(content, mChatId);
                    // 將新的消息內(nèi)容和時間加入到下邊
                    mContentText.setText(mContentText.getText()
                        + "\n發(fā)送:"
                        + content
                        + " - time: "
                        + message.getMsgTime());
                    // 調(diào)用發(fā)送消息的方法
                    EMClient.getInstance().chatManager().sendMessage(message);
                    // 為消息設(shè)置回調(diào)
                    message.setMessageStatusCallback(new EMCallBack() {
                        @Override public void onSuccess() {
                            // 消息發(fā)送成功,打印下日志蟀架,正常操作應該去刷新ui
                            Log.i("lzan13", "send message on success");
                        }

                        @Override public void onError(int i, String s) {
                            // 消息發(fā)送失敗瓣赂,打印下失敗的信息,正常操作應該去刷新ui
                            Log.i("lzan13", "send message on error " + i + " - " + s);
                        }

                        @Override public void onProgress(int i, String s) {
                            // 消息發(fā)送進度片拍,一般只有在發(fā)送圖片和文件等消息才會有回調(diào)煌集,txt不回調(diào)
                        }
                    });
                }
            }
        });
    }

    /**
     * 初始化會話對象,并且根據(jù)需要加載更多消息
     */
    private void initConversation() {

        /**
         * 初始化會話對象捌省,這里有三個參數(shù)么苫纤,
         * 第一個表示會話的當前聊天的 useranme 或者 groupid
         * 第二個是繪畫類型可以為空
         * 第三個表示如果會話不存在是否創(chuàng)建
         */
        mConversation = EMClient.getInstance().chatManager().getConversation(mChatId, null, true);
        // 設(shè)置當前會話未讀數(shù)為 0
        mConversation.markAllMessagesAsRead();
        int count = mConversation.getAllMessages().size();
        if (count < mConversation.getAllMsgCount() && count < 20) {
            // 獲取已經(jīng)在列表中的最上邊的一條消息id
            String msgId = mConversation.getAllMessages().get(0).getMsgId();
            // 分頁加載更多消息,需要傳遞已經(jīng)加載的消息的最上邊一條消息的id,以及需要加載的消息的條數(shù)
            mConversation.loadMoreMsgFromDB(msgId, 20 - count);
        }
        // 打開聊天界面獲取最后一條消息內(nèi)容并顯示
        if (mConversation.getAllMessages().size() > 0) {
            EMMessage messge = mConversation.getLastMessage();
            EMTextMessageBody body = (EMTextMessageBody) messge.getBody();
            // 將消息內(nèi)容和時間顯示出來
            mContentText.setText(
                "聊天記錄:" + body.getMessage() + " - time: " + mConversation.getLastMessage()
                    .getMsgTime());
        }
    }

    /**
     * 自定義實現(xiàn)Handler卷拘,主要用于刷新UI操作
     */
    Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
            case 0:
                EMMessage message = (EMMessage) msg.obj;
                // 這里只是簡單的demo喊废,也只是測試文字消息的收發(fā),所以直接將body轉(zhuǎn)為EMTextMessageBody去獲取內(nèi)容
                EMTextMessageBody body = (EMTextMessageBody) message.getBody();
                // 將新的消息內(nèi)容和時間加入到下邊
                mContentText.setText(mContentText.getText()
                    + "\n接收:"
                    + body.getMessage()
                    + " - time: "
                    + message.getMsgTime());
                break;
            }
        }
    };

    @Override protected void onResume() {
        super.onResume();
        // 添加消息監(jiān)聽
        EMClient.getInstance().chatManager().addMessageListener(mMessageListener);
    }

    @Override protected void onStop() {
        super.onStop();
        // 移除消息監(jiān)聽
        EMClient.getInstance().chatManager().removeMessageListener(mMessageListener);
    }
    /**
     * --------------------------------- Message Listener -------------------------------------
     * 環(huán)信消息監(jiān)聽主要方法
     */
    /**
     * 收到新消息
     *
     * @param list 收到的新消息集合
     */
    @Override public void onMessageReceived(List<EMMessage> list) {
        // 循環(huán)遍歷當前收到的消息
        for (EMMessage message : list) {
            Log.i("lzan13", "收到新消息:" + message);
            if (message.getFrom().equals(mChatId)) {
                // 設(shè)置消息為已讀
                mConversation.markMessageAsRead(message.getMsgId());

                // 因為消息監(jiān)聽回調(diào)這里是非ui線程栗弟,所以要用handler去更新ui
                Message msg = mHandler.obtainMessage();
                msg.what = 0;
                msg.obj = message;
                mHandler.sendMessage(msg);
            } else {
                // TODO 如果消息不是當前會話的消息發(fā)送通知欄通知
            }
        }
    }

    /**
     * 收到新的 CMD 消息
     */
    @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", "收到 CMD 透傳消息" + body.action());
        }
    }

    /**
     * 收到新的已讀回執(zhí)
     *
     * @param list 收到消息已讀回執(zhí)
     */
    @Override public void onMessageRead(List<EMMessage> list) {}

    /**
     * 收到新的發(fā)送回執(zhí)
     * TODO 無效 暫時有bug
     *
     * @param list 收到發(fā)送回執(zhí)的消息集合
     */
    @Override public void onMessageDelivered(List<EMMessage> list) {}

    /**
     * 消息撤回回調(diào)
     *
     * @param list 撤回的消息列表
     */
    @Override public void onMessageRecalled(List<EMMessage> list) {}

    /**
     * 消息的狀態(tài)改變
     *
     * @param message 發(fā)生改變的消息
     * @param object 包含改變的消息
     */
    @Override public void onMessageChanged(EMMessage message, Object object) {}
}

界面布局

界面的實現(xiàn)也是非常簡單污筷,這里直接貼一下:
activity_main.xml

<?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="發(fā)起聊天"/>

        <Button
            android:id="@+id/ec_btn_sign_out"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="退出登錄"/>
    </LinearLayout>
</RelativeLayout>

activity_login.xml

<?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>

activity_chat.xml

<?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>

    <!--展示消息內(nèi)容-->
    <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>

別忘了配置文件

最后我們需要配置一些權(quán)限,我們的代碼才能很好的工作

<?xml version="1.0" encoding="utf-8"?>
<manifest package="net.melove.demo.easechat"
          xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 項目權(quán)限 -->
    <!-- 相機 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!-- 錄音 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <!-- 震動 -->
    <uses-permission android:name="android.permission.VIBRATE"/>
    <!-- 網(wǎng)絡 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 訪問網(wǎng)絡狀態(tài) -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!-- 訪問WIFI狀態(tài) -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <!-- 寫入外部存儲 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 訪問精確定位 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!-- 修改音頻設(shè)置 -->
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <!-- 讀取手機狀態(tài) -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <!-- 允許讀寫系統(tǒng)設(shè)置項 使用設(shè)置時需要 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
    <!-- 讀取啟動設(shè)置 -->
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/>

    <!--非必需權(quán)限-->
    <!-- 開機自啟動 -->
    <!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>-->
    <!--<uses-permission android:name="android.permission.GET_TASKS"/>-->
    <!-- 安裝卸載文件系統(tǒng) -->
    <!--<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>-->
    <!-- 改變WIFI狀態(tài) -->
    <!--<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>-->
    <!-- 讀取描述文件 -->
    <!--<uses-permission android:name="android.permission.READ_PROFILE"/>-->
    <!-- 讀取聯(lián)系人 -->
    <!--<uses-permission android:name="android.permission.READ_CONTACTS"/>-->

    <!-- 連續(xù)廣播(允許一個程序收到廣播后快速收到下一個廣播) -->
    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>

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

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".ECLoginActivity">
        </activity>
        <activity
            android:name=".ECChatActivity"
            android:windowSoftInputMode="adjustResize|stateHidden">
        </activity>


        <!-- 設(shè)置環(huán)信應用的 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>
    </application>
</manifest>

地址整理

項目地址
lzan13 / EaseChat

AndroidStudio下載
Android官方下載
國內(nèi)提供 AndroidDevTools

模擬器 Genymotion下載
Genymotion 官網(wǎng)

環(huán)信官方文檔
SDK3.x 文檔
SDK3.x API 文檔
SDK2.x 升級 SDK3.x 文檔

關(guān)于環(huán)信3.xSDK日志簡單分析
使用環(huán)信3.xSDK集成小米推送實現(xiàn)消息以及通話時的離線通知
使用第三方庫出現(xiàn)找不到so庫UnsatisfiedLinkError錯誤的原因以及解決方案

結(jié)語

代碼結(jié)束乍赫,Coding不止瓣蛀!Coding. Coding. Coding...
OK了,一個簡單的注冊登錄以及收發(fā)消息的小demo就算完成了雷厂,可以用自己的環(huán)境編譯運行下試試

延伸項目

這里還有一個針對音視頻的項目惋增,集成了1V1以及多人音視頻的項目,還算比較完整改鲫,有興趣的可以看看

音視頻項目:VMChatDemoCall

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诈皿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钩杰,更是在濱河造成了極大的恐慌纫塌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讲弄,死亡現(xiàn)場離奇詭異措左,居然都是意外死亡,警方通過查閱死者的電腦和手機避除,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門怎披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓶摆,你說我怎么就攤上這事凉逛。” “怎么了群井?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵状飞,是天一觀的道長。 經(jīng)常有香客問我书斜,道長诬辈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任荐吉,我火速辦了婚禮焙糟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘样屠。我一直安慰自己穿撮,他們只是感情好缺脉,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悦穿,像睡著了一般攻礼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咧党,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天秘蛔,我揣著相機與錄音,去河邊找鬼傍衡。 笑死深员,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蛙埂。 我是一名探鬼主播倦畅,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绣的!你這毒婦竟也來了叠赐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屡江,失蹤者是張志新(化名)和其女友劉穎芭概,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惩嘉,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡罢洲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了文黎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惹苗。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耸峭,靈堂內(nèi)的尸體忽然破棺而出桩蓉,到底是詐尸還是另有隱情,我是刑警寧澤劳闹,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布院究,位于F島的核電站,受9級特大地震影響本涕,放射性物質(zhì)發(fā)生泄漏儡首。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一偏友、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧对供,春花似錦位他、人聲如沸氛濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽值纱。三九已至锅很,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骗奖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工醒串, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留执桌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓芜赌,卻偏偏與公主長得像仰挣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缠沈,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容