首次發(fā)表 2017/1/4
更新日期 2018/5/9 升級(jí) sdk 到3.4.0 及開(kāi)發(fā)環(huán)境的更新
前言
環(huán)信已經(jīng)發(fā)部了SDK3.x版本,SDK3.x相對(duì)于SDK2.x來(lái)說(shuō)是整個(gè)進(jìn)行了重寫(xiě),API變化還是比較大的,已經(jīng)熟悉SDK2.x的開(kāi)發(fā)者在使用新的SDK3.x還是會(huì)遇到不少問(wèn)題的,不過(guò)還好官方給出了SDK2.x升級(jí)SDK3.x指南,已經(jīng)熟悉SDK2.x開(kāi)發(fā)者可以根據(jù)文檔了解SDK3.x的變化,新集成的開(kāi)發(fā)者可以直接參考SDK3.x進(jìn)行集成;
這里簡(jiǎn)單的實(shí)現(xiàn)了sdk的初始化以及注冊(cè)登錄和收發(fā)消息,不過(guò)ui上沒(méi)有做很好的處理
如果你還是用的
Eclipse,可以下載AndroidStudio嘗試下,如果你上不了Android官網(wǎng),不懂怎么翻墻可以找下國(guó)內(nèi)開(kāi)發(fā)提供的一些地址,文末也會(huì)整理一些地址
看效果圖

說(shuō)下我當(dāng)前開(kāi)發(fā)環(huán)境
這里并不是一定要按照我的配置來(lái),只是說(shuō)下當(dāng)前項(xiàng)目開(kāi)發(fā)運(yùn)行的環(huán)境,如果你的開(kāi)發(fā)環(huán)境不同可能需要自己修改下項(xiàng)目配置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
開(kāi)始集成
這次要實(shí)現(xiàn) SDK的初始化、SDK端的注冊(cè)登錄、消息的發(fā)送和監(jiān)聽(tīng)這三步
SDK的初始化
這個(gè)初始化時(shí)在Application里進(jìn)行的,這里定義了一個(gè)方法去初始化環(huán)信的SDK,并在其中進(jìn)行了一些設(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.
* 項(xiàng)目的 Application類(lèi),做一些項(xiàng)目的初始化操作,比如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() {
// 獲取當(dāng)前進(jìn)程 id 并取得進(jìn)程名
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
/**
* 如果app啟用了遠(yuǎn)程的service,此application:onCreate會(huì)被調(diào)用2次
* 為了防止環(huán)信SDK被初始化2次,加此判斷會(huì)保證SDK被初始化1次
* 默認(rèn)的app會(huì)在以包名為默認(rèn)的process name下運(yùn)行,如果查到的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è)置自動(dòng)登錄
options.setAutoLogin(true);
// 設(shè)置是否需要發(fā)送已讀回執(zhí)
options.setRequireAck(true);
// 設(shè)置是否需要發(fā)送回執(zhí),TODO 這個(gè)暫時(shí)有bug,上層收不到發(fā)送回執(zhí)
options.setRequireDeliveryAck(true);
// 設(shè)置是否需要服務(wù)器收到消息確認(rèn)
options.setRequireServerAck(true);
// 收到好友申請(qǐng)是否自動(dòng)同意,如果是自動(dòng)同意就不會(huì)收到好友請(qǐng)求的回調(diào),因?yàn)閟dk會(huì)自動(dòng)處理,默認(rèn)為true
options.setAcceptInvitationAlways(false);
// 設(shè)置是否自動(dòng)接收加群邀請(qǐng),如果設(shè)置了當(dāng)收到群邀請(qǐng)會(huì)自動(dòng)同意加入
options.setAutoAcceptGroupInvitation(false);
// 設(shè)置(主動(dòng)或被動(dòng))退出群組時(shí),是否刪除群聊聊天記錄
options.setDeleteMessagesAsExitGroup(false);
// 設(shè)置是否允許聊天室的Owner 離開(kāi)并刪除聊天室的會(huì)話
options.allowChatroomOwnerLeave(true);
// 設(shè)置google GCM推送id,國(guó)內(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è)置開(kāi)啟debug模式
EMClient.getInstance().setDebugMode(true);
// 設(shè)置初始化已經(jīng)完成
isInit = true;
}
/**
* 根據(jù)Pid獲取當(dāng)前進(jìn)程的名字,一般就是當(dāng)前app的包名
*
* @param pid 進(jìn)程的id
* @return 返回進(jìn)程的名字
*/
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ù)進(jìn)程的信息獲取當(dāng)前進(jìn)程的名字
processName = info.processName;
// 返回當(dāng)前進(jìn)程名
return processName;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 沒(méi)有匹配的項(xiàng),返回為null
return null;
}
}
主界面
app啟動(dòng)后默認(rèn)會(huì)進(jìn)入到ECMainActivity,不過(guò)在主界面會(huì)先判斷一下是否登錄成功過(guò),如果沒(méi)有,就會(huì)跳轉(zhuǎn)到登錄幾面,然后我們調(diào)用登錄的時(shí)候,在登錄方法的onSuccess()回調(diào)中我們進(jìn)行了界面的跳轉(zhuǎn),跳轉(zhuǎn)到主界面,在主界面我們可以發(fā)起回話;
看下主界面的詳細(xì)代碼實(shí)現(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是否登錄成功過(guò),并沒(méi)有退出和被踢,否則跳轉(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)) {
// 獲取當(dāng)前登錄用戶(hù)的 username
String currUsername = EMClient.getInstance().getCurrentUser();
if (chatId.equals(currUsername)) {
Toast.makeText(ECMainActivity.this, "不能和自己聊天", Toast.LENGTH_SHORT).show();
return;
}
// 跳轉(zhuǎn)到聊天界面,開(kāi)始聊天
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的退出登錄方法,第一個(gè)參數(shù)表示是否解綁推送的token,沒(méi)有使用推送或者被踢都要傳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端的注冊(cè)登錄
SDK初始化做完之后,就是需要進(jìn)行環(huán)信的登錄了,登錄了才能使用環(huán)信的功能,才能收發(fā)消息,有不少人經(jīng)常問(wèn),不注冊(cè)賬戶(hù)能使用么,這是聊天sdk,不注冊(cè)賬戶(hù)你拿什么聊天呢!
登錄調(diào)用EMClient.getInstance().login(username, password, callback);此方法是一個(gè)異步方法,所以需要設(shè)置EMCallback回調(diào)來(lái)接收登錄結(jié)果;
注冊(cè)調(diào)用EMClient.getInstance().createAccount(username, password);此方法是同步方法,需要自己創(chuàng)建新線程去調(diào)用,不能放在UI線程直接調(diào)用;
因?yàn)橹皇莻€(gè)簡(jiǎn)單的demo,這邊把登錄和注冊(cè)都卸載了LoginActivity類(lèi)里,這個(gè)方法中對(duì)調(diào)用環(huán)信sdk的方法返回錯(cuò)誤值做了一些判斷,具體錯(cuò)誤信息可以參考官方文檔:
環(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;
// 注冊(cè)按鈕
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();
}
});
}
/**
* 注冊(cè)方法
*/
private void signUp() {
// 注冊(cè)是耗時(shí)過(guò)程,所以要顯示一個(gè)dialog來(lái)提示下用戶(hù)
mDialog = new ProgressDialog(this);
mDialog.setMessage("注冊(cè)中,請(qǐng)稍后...");
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, "注冊(cè)成功", 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)于錯(cuò)誤碼可以參考官方api詳細(xì)說(shuō)明
* 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)絡(luò)錯(cuò)誤
case EMError.NETWORK_ERROR:
Toast.makeText(ECLoginActivity.this, "網(wǎng)絡(luò)錯(cuò)誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
break;
// 用戶(hù)已存在
case EMError.USER_ALREADY_EXIST:
Toast.makeText(ECLoginActivity.this, "用戶(hù)已存在 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
break;
// 參數(shù)不合法,一般情況是username 使用了uuid導(dǎo)致,不能使用uuid注冊(cè)
case EMError.USER_ILLEGAL_ARGUMENT:
Toast.makeText(ECLoginActivity.this, "參數(shù)不合法,一般情況是username 使用了uuid導(dǎo)致,不能使用uuid注冊(cè) code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
break;
// 服務(wù)器未知錯(cuò)誤
case EMError.SERVER_UNKNOWN_ERROR:
Toast.makeText(ECLoginActivity.this, "服務(wù)器未知錯(cuò)誤 code: " + errorCode + ", message:" + message, Toast.LENGTH_LONG).show();
break;
case EMError.USER_REG_FAILED:
Toast.makeText(ECLoginActivity.this, "賬戶(hù)注冊(cè)失敗 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("正在登陸,請(qǐng)稍后...");
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();
// 加載所有會(huì)話到內(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();
}
});
}
/**
* 登陸錯(cuò)誤的回調(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)于錯(cuò)誤碼可以參考官方api詳細(xì)說(shuō)明
* http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html
*/
switch (i) {
// 網(wǎng)絡(luò)異常 2
case EMError.NETWORK_ERROR:
Toast.makeText(ECLoginActivity.this, "網(wǎng)絡(luò)錯(cuò)誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 無(wú)效的用戶(hù)名 101
case EMError.INVALID_USER_NAME:
Toast.makeText(ECLoginActivity.this, "無(wú)效的用戶(hù)名 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 無(wú)效的密碼 102
case EMError.INVALID_PASSWORD:
Toast.makeText(ECLoginActivity.this, "無(wú)效的密碼 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 用戶(hù)認(rèn)證失敗,用戶(hù)名或密碼錯(cuò)誤 202
case EMError.USER_AUTHENTICATION_FAILED:
Toast.makeText(ECLoginActivity.this, "用戶(hù)認(rèn)證失敗,用戶(hù)名或密碼錯(cuò)誤 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 用戶(hù)不存在 204
case EMError.USER_NOT_FOUND:
Toast.makeText(ECLoginActivity.this, "用戶(hù)不存在 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 無(wú)法訪問(wèn)到服務(wù)器 300
case EMError.SERVER_NOT_REACHABLE:
Toast.makeText(ECLoginActivity.this, "無(wú)法訪問(wèn)到服務(wù)器 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 等待服務(wù)器響應(yīng)超時(shí) 301
case EMError.SERVER_TIMEOUT:
Toast.makeText(ECLoginActivity.this, "等待服務(wù)器響應(yīng)超時(shí) code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 服務(wù)器繁忙 302
case EMError.SERVER_BUSY:
Toast.makeText(ECLoginActivity.this, "服務(wù)器繁忙 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show();
break;
// 未知 Server 異常 303 一般斷網(wǎng)會(huì)出現(xiàn)這個(gè)錯(cuò)誤
case EMError.SERVER_UNKNOWN_ERROR:
Toast.makeText(ECLoginActivity.this, "未知的服務(wù)器異常 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)聽(tīng)
實(shí)現(xiàn)消息的接收需要添加EMMessageListener消息監(jiān)聽(tīng)接口,我們?cè)谛枰O(jiān)聽(tīng)的地方要實(shí)現(xiàn)這個(gè)接口,并實(shí)現(xiàn)接口里邊的幾個(gè)回調(diào)方法:
onMessageReceived(List<EMMessage> list)新消息的回調(diào)
onCmdMessageReceived(List<EMMessage> list)新的透?jìng)飨⒒卣{(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)聽(tīng)與發(fā)送的完整實(shí)現(xiàn),代碼注釋比較詳細(xì),不再一一解釋
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)聽(tīng)器
private EMMessageListener mMessageListener;
// 當(dāng)前聊天的 ID
private String mChatId;
// 當(dāng)前會(huì)話對(duì)象
private EMConversation mConversation;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
// 獲取當(dāng)前會(huì)話的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可滾動(dòng),需配合x(chóng)ml布局設(shè)置
mContentText.setMovementMethod(new ScrollingMovementMethod());
// 設(shè)置發(fā)送按鈕的點(diǎn)擊事件
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)建一條新消息,第一個(gè)參數(shù)為消息內(nèi)容,第二個(gè)為接受者username
EMMessage message = EMMessage.createTxtSendMessage(content, mChatId);
// 將新的消息內(nèi)容和時(shí)間加入到下邊
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ā)送成功,打印下日志,正常操作應(yīng)該去刷新ui
Log.i("lzan13", "send message on success");
}
@Override public void onError(int i, String s) {
// 消息發(fā)送失敗,打印下失敗的信息,正常操作應(yīng)該去刷新ui
Log.i("lzan13", "send message on error " + i + " - " + s);
}
@Override public void onProgress(int i, String s) {
// 消息發(fā)送進(jìn)度,一般只有在發(fā)送圖片和文件等消息才會(huì)有回調(diào),txt不回調(diào)
}
});
}
}
});
}
/**
* 初始化會(huì)話對(duì)象,并且根據(jù)需要加載更多消息
*/
private void initConversation() {
/**
* 初始化會(huì)話對(duì)象,這里有三個(gè)參數(shù)么,
* 第一個(gè)表示會(huì)話的當(dāng)前聊天的 useranme 或者 groupid
* 第二個(gè)是繪畫(huà)類(lèi)型可以為空
* 第三個(gè)表示如果會(huì)話不存在是否創(chuàng)建
*/
mConversation = EMClient.getInstance().chatManager().getConversation(mChatId, null, true);
// 設(shè)置當(dāng)前會(huì)話未讀數(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();
// 分頁(yè)加載更多消息,需要傳遞已經(jīng)加載的消息的最上邊一條消息的id,以及需要加載的消息的條數(shù)
mConversation.loadMoreMsgFromDB(msgId, 20 - count);
}
// 打開(kāi)聊天界面獲取最后一條消息內(nèi)容并顯示
if (mConversation.getAllMessages().size() > 0) {
EMMessage messge = mConversation.getLastMessage();
EMTextMessageBody body = (EMTextMessageBody) messge.getBody();
// 將消息內(nèi)容和時(shí)間顯示出來(lái)
mContentText.setText(
"聊天記錄:" + body.getMessage() + " - time: " + mConversation.getLastMessage()
.getMsgTime());
}
}
/**
* 自定義實(shí)現(xiàn)Handler,主要用于刷新UI操作
*/
Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
EMMessage message = (EMMessage) msg.obj;
// 這里只是簡(jiǎn)單的demo,也只是測(cè)試文字消息的收發(fā),所以直接將body轉(zhuǎn)為EMTextMessageBody去獲取內(nèi)容
EMTextMessageBody body = (EMTextMessageBody) message.getBody();
// 將新的消息內(nèi)容和時(shí)間加入到下邊
mContentText.setText(mContentText.getText()
+ "\n接收:"
+ body.getMessage()
+ " - time: "
+ message.getMsgTime());
break;
}
}
};
@Override protected void onResume() {
super.onResume();
// 添加消息監(jiān)聽(tīng)
EMClient.getInstance().chatManager().addMessageListener(mMessageListener);
}
@Override protected void onStop() {
super.onStop();
// 移除消息監(jiān)聽(tīng)
EMClient.getInstance().chatManager().removeMessageListener(mMessageListener);
}
/**
* --------------------------------- Message Listener -------------------------------------
* 環(huán)信消息監(jiān)聽(tīng)主要方法
*/
/**
* 收到新消息
*
* @param list 收到的新消息集合
*/
@Override public void onMessageReceived(List<EMMessage> list) {
// 循環(huán)遍歷當(dāng)前收到的消息
for (EMMessage message : list) {
Log.i("lzan13", "收到新消息:" + message);
if (message.getFrom().equals(mChatId)) {
// 設(shè)置消息為已讀
mConversation.markMessageAsRead(message.getMsgId());
// 因?yàn)橄⒈O(jiān)聽(tīng)回調(diào)這里是非ui線程,所以要用handler去更新ui
Message msg = mHandler.obtainMessage();
msg.what = 0;
msg.obj = message;
mHandler.sendMessage(msg);
} else {
// TODO 如果消息不是當(dāng)前會(huì)話的消息發(fā)送通知欄通知
}
}
}
/**
* 收到新的 CMD 消息
*/
@Override public void onCmdMessageReceived(List<EMMessage> list) {
for (int i = 0; i < list.size(); i++) {
// 透?jìng)飨? EMMessage cmdMessage = list.get(i);
EMCmdMessageBody body = (EMCmdMessageBody) cmdMessage.getBody();
Log.i("lzan13", "收到 CMD 透?jìng)飨? + body.action());
}
}
/**
* 收到新的已讀回執(zhí)
*
* @param list 收到消息已讀回執(zhí)
*/
@Override public void onMessageRead(List<EMMessage> list) {}
/**
* 收到新的發(fā)送回執(zhí)
* TODO 無(wú)效 暫時(shí)有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) {}
}
界面布局
界面的實(shí)現(xiàn)也是非常簡(jiǎ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="對(duì)方的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="注冊(cè)"/>
<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">
<!-- 項(xiàng)目權(quán)限 -->
<!-- 相機(jī) -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 錄音 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- 震動(dòng) -->
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- 網(wǎng)絡(luò) -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 訪問(wèn)網(wǎng)絡(luò)狀態(tài) -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 訪問(wèn)WIFI狀態(tài) -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- 寫(xiě)入外部存儲(chǔ) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 訪問(wèn)精確定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 修改音頻設(shè)置 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<!-- 讀取手機(jī)狀態(tài) -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- 允許讀寫(xiě)系統(tǒng)設(shè)置項(xiàng) 使用設(shè)置時(shí)需要 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!-- 讀取啟動(dòng)設(shè)置 -->
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/>
<!--非必需權(quán)限-->
<!-- 開(kāi)機(jī)自啟動(dòng) -->
<!--<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ù)廣播(允許一個(gè)程序收到廣播后快速收到下一個(gè)廣播) -->
<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)信應(yīng)用的 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>
地址整理
AndroidStudio下載
Android官方下載
國(guó)內(nèi)提供 AndroidDevTools
模擬器 Genymotion下載
Genymotion 官網(wǎng)
環(huán)信官方文檔
SDK3.x 文檔
SDK3.x API 文檔
SDK2.x 升級(jí) SDK3.x 文檔
關(guān)于環(huán)信3.xSDK日志簡(jiǎn)單分析
使用環(huán)信3.xSDK集成小米推送實(shí)現(xiàn)消息以及通話時(shí)的離線通知
使用第三方庫(kù)出現(xiàn)找不到so庫(kù)UnsatisfiedLinkError錯(cuò)誤的原因以及解決方案
結(jié)語(yǔ)
代碼結(jié)束,Coding不止!Coding. Coding. Coding...
OK了,一個(gè)簡(jiǎn)單的注冊(cè)登錄以及收發(fā)消息的小demo就算完成了,可以用自己的環(huán)境編譯運(yùn)行下試試
延伸項(xiàng)目
這里還有一個(gè)針對(duì)音視頻的項(xiàng)目,集成了1V1以及多人音視頻的項(xiàng)目,還算比較完整,有興趣的可以看看
音視頻項(xiàng)目:VMChatDemoCall