├── .gitignore ├── EaseChat.pptx ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── melove │ │ └── demo │ │ └── easechat │ │ ├── ECApplication.java │ │ ├── ECChatActivity.java │ │ ├── ECLoginActivity.java │ │ └── ECMainActivity.java │ └── res │ ├── layout │ ├── activity_chat.xml │ ├── activity_login.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ppt.md ├── screenshot ├── ec-demo.gif └── ec-easeui-demo.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | 5 | .idea 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | /.idea/misc.xml 9 | .DS_Store 10 | /build 11 | /captures 12 | /easeui 13 | -------------------------------------------------------------------------------- /EaseChat.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzan13/EaseChat/c017369e4302488a9c09ab02e8a789f8ddfa4675/EaseChat.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EaseChat 2 | ======== 3 | 4 | ### 前言 5 | 环信已经发部了`SDK3.x`版本,`SDK3.x`相对于`SDK2.x`来说是整个进行了重写,`API`变化还是比较大的,已经熟悉`SDK2.x`的开发者在使用新的`SDK3.x`还是会遇到不少问题的,不过还好官方给出了`SDK2.x`升级`SDK3.x`指南,已经熟悉`SDK2.x`开发者可以根据文档了解`SDK3.x`的变化,新集成的开发者可以直接参考`SDK3.x`进行集成; 6 | 这里简单的实现了sdk的初始化以及注册登录和收发消息,不过ui上没有没有去做很好的处理 7 | 8 | > 如果你还是用的`Eclipse`,可以下载`AndroidStudio`尝试下,如果你上不了`Android`官网,不懂怎么翻墙可以找下国内开发提供的一些地址 9 | 10 | ### 先看效果图 11 | ![ec-demo](./screenshot/ec-demo.gif) 12 | 13 | ### 开发环境 14 | 这里并不是一定要按照我的配置来,只是说下当前项目开发运行的环境,如果你的开发环境不同可能需要自己修改下项目配置`build.gradle`文件 15 | 16 | >系统 Mac 17 | AndroidStudio 3.0.0 18 | Gradle 4.1(跟随AndroidStudio 一起更新) 19 | Android compileSdkVersion 27 20 | Android buildToolsVersion 27.0.3 21 | Android Support 最新 22 | 环信 SDK 3.4.0 23 | 24 | ### 地址整理 25 | >项目地址 26 | [lzan13](https://github.com/lzan13) / [EaseChat](https://github.com/lzan13/EaseChat) 27 | 28 | >AndroidStudio下载 29 | [Android官方下载](http://tools.android.com/download/studio/builds/2-0) 30 | [国内提供 AndroidDevTools](http://androiddevtools.cn/) 31 | 32 | >模拟器 Genymotion下载 33 | [Genymotion 官网](http://genymotion.com/) 34 | 35 | >环信官方文档 36 | [SDK3.x 文档](http://docs.easemob.com/im/start) 37 | [SDK3.x API 文档](http://www.easemob.com/apidoc/android/chat3.0/annotated.html) 38 | [SDK2.x 升级 SDK3.x 文档](http://docs.easemob.com/im/200androidcleintintegration/140upgradetov30) 39 | 40 | >[关于环信3.xSDK日志简单分析](https://www.jianshu.com/p/a194fa19bd6a) 41 | >[使用环信3.xSDK集成小米推送实现消息以及通话时的离线通知](https://www.jianshu.com/p/df892008ca5e) 42 | >[使用第三方库出现找不到so库UnsatisfiedLinkError错误的原因以及解决方案](https://www.jianshu.com/p/b9a524f24b7e) 43 | 44 | >项目详细介绍 45 | [项目文章详细介绍](http://www.jianshu.com/p/3e732f45d376) 46 | 47 | ### 延伸项目 48 | 这里还有一个针对音视频的项目,集成了1V1以及多人音视频的项目,还算比较完整,有兴趣的可以看看 49 | > 音视频项目:[VMChatDemoCall](https://github.com/lzan13/VMChatDemoCall) 50 | 51 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion "27.0.3" 6 | 7 | defaultConfig { 8 | applicationId "net.melove.demo.easechat" 9 | minSdkVersion 15 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | ndk { 14 | abiFilters('armeabi-v7a', 'x86') 15 | } 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | sourceSets { 24 | main { 25 | // 设置Jni so文件路径 如果有jniLibs目录就不需要设置,好像手动创建的 jniLibs目录无效 26 | jniLibs.srcDirs = ['libs'] 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | compile fileTree(include: ['*.jar'], dir: 'libs') 33 | compile 'com.android.support:appcompat-v7:27.1.1' 34 | 35 | compile 'com.hyphenate:hyphenate-sdk:3.4.0' 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\develop\android\android_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/java/net/melove/demo/easechat/ECApplication.java: -------------------------------------------------------------------------------- 1 | package net.melove.demo.easechat; 2 | 3 | import android.app.ActivityManager; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | 8 | import com.hyphenate.chat.EMClient; 9 | import com.hyphenate.chat.EMOptions; 10 | 11 | import java.util.Iterator; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by lz on 2016/4/16. 16 | * 项目的 Application类,做一些项目的初始化操作,比如sdk的初始化等 17 | */ 18 | public class ECApplication extends Application { 19 | 20 | // 上下文菜单 21 | private Context mContext; 22 | 23 | // 记录是否已经初始化 24 | private boolean isInit = false; 25 | 26 | @Override public void onCreate() { 27 | super.onCreate(); 28 | mContext = this; 29 | 30 | // 初始化环信SDK 31 | initEasemob(); 32 | } 33 | 34 | /** 35 | * 36 | */ 37 | private void initEasemob() { 38 | // 获取当前进程 id 并取得进程名 39 | int pid = android.os.Process.myPid(); 40 | String processAppName = getAppName(pid); 41 | /** 42 | * 如果app启用了远程的service,此application:onCreate会被调用2次 43 | * 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次 44 | * 默认的app会在以包名为默认的process name下运行,如果查到的process name不是app的process name就立即返回 45 | */ 46 | if (processAppName == null || !processAppName.equalsIgnoreCase(mContext.getPackageName())) { 47 | // 则此application的onCreate 是被service 调用的,直接返回 48 | return; 49 | } 50 | if (isInit) { 51 | return; 52 | } 53 | 54 | // 调用初始化方法初始化sdk 55 | EMClient.getInstance().init(mContext, initOptions()); 56 | // 设置开启debug模式 57 | EMClient.getInstance().setDebugMode(true); 58 | 59 | // 设置初始化已经完成 60 | isInit = true; 61 | } 62 | 63 | /** 64 | * SDK初始化的一些配置 65 | * 关于 EMOptions 可以参考官方的 API 文档 66 | * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1chat_1_1_e_m_options.html 67 | */ 68 | private EMOptions initOptions() { 69 | 70 | EMOptions options = new EMOptions(); 71 | // 设置Appkey,如果配置文件已经配置,这里可以不用设置 72 | // options.setAppKey("lzan13#hxsdkdemo"); 73 | // 设置自动登录 74 | options.setAutoLogin(true); 75 | // 设置是否需要发送已读回执 76 | options.setRequireAck(true); 77 | // 设置是否需要发送回执, 78 | options.setRequireDeliveryAck(true); 79 | // 设置是否根据服务器时间排序,默认是true 80 | options.setSortMessageByServerTime(false); 81 | // 收到好友申请是否自动同意,如果是自动同意就不会收到好友请求的回调,因为sdk会自动处理,默认为true 82 | options.setAcceptInvitationAlways(false); 83 | // 设置是否自动接收加群邀请,如果设置了当收到群邀请会自动同意加入 84 | options.setAutoAcceptGroupInvitation(false); 85 | // 设置(主动或被动)退出群组时,是否删除群聊聊天记录 86 | options.setDeleteMessagesAsExitGroup(false); 87 | // 设置是否允许聊天室的Owner 离开并删除聊天室的会话 88 | options.allowChatroomOwnerLeave(true); 89 | // 设置google GCM推送id,国内可以不用设置 90 | // options.setGCMNumber(MLConstants.ML_GCM_NUMBER); 91 | // 设置集成小米推送的appid和appkey 92 | // options.setMipushConfig(MLConstants.ML_MI_APP_ID, MLConstants.ML_MI_APP_KEY); 93 | 94 | return options; 95 | } 96 | 97 | /** 98 | * 根据Pid获取当前进程的名字,一般就是当前app的包名 99 | * 100 | * @param pid 进程的id 101 | * @return 返回进程的名字 102 | */ 103 | private String getAppName(int pid) { 104 | String processName = null; 105 | ActivityManager activityManager = 106 | (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 107 | List list = activityManager.getRunningAppProcesses(); 108 | Iterator i = list.iterator(); 109 | while (i.hasNext()) { 110 | ActivityManager.RunningAppProcessInfo info = 111 | (ActivityManager.RunningAppProcessInfo) (i.next()); 112 | try { 113 | if (info.pid == pid) { 114 | // 根据进程的信息获取当前进程的名字 115 | processName = info.processName; 116 | // 返回当前进程名 117 | return processName; 118 | } 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | // 没有匹配的项,返回为null 124 | return null; 125 | } 126 | } -------------------------------------------------------------------------------- /app/src/main/java/net/melove/demo/easechat/ECChatActivity.java: -------------------------------------------------------------------------------- 1 | package net.melove.demo.easechat; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.text.method.ScrollingMovementMethod; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.widget.Button; 12 | import android.widget.EditText; 13 | import android.widget.TextView; 14 | 15 | import com.hyphenate.EMCallBack; 16 | import com.hyphenate.EMMessageListener; 17 | import com.hyphenate.chat.EMClient; 18 | import com.hyphenate.chat.EMCmdMessageBody; 19 | import com.hyphenate.chat.EMConversation; 20 | import com.hyphenate.chat.EMMessage; 21 | import com.hyphenate.chat.EMTextMessageBody; 22 | 23 | import java.util.List; 24 | 25 | public class ECChatActivity extends AppCompatActivity implements EMMessageListener { 26 | 27 | // 聊天信息输入框 28 | private EditText mInputEdit; 29 | // 发送按钮 30 | private Button mSendBtn; 31 | 32 | // 显示内容的 TextView 33 | private TextView mContentText; 34 | 35 | // 消息监听器 36 | private EMMessageListener mMessageListener; 37 | // 当前聊天的 ID 38 | private String mChatId; 39 | // 当前会话对象 40 | private EMConversation mConversation; 41 | 42 | @Override protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_chat); 45 | 46 | // 获取当前会话的username(如果是群聊就是群id) 47 | mChatId = getIntent().getStringExtra("ec_chat_id"); 48 | mMessageListener = this; 49 | 50 | initView(); 51 | initConversation(); 52 | } 53 | 54 | /** 55 | * 初始化界面 56 | */ 57 | private void initView() { 58 | mInputEdit = (EditText) findViewById(R.id.ec_edit_message_input); 59 | mSendBtn = (Button) findViewById(R.id.ec_btn_send); 60 | mContentText = (TextView) findViewById(R.id.ec_text_content); 61 | // 设置textview可滚动,需配合xml布局设置 62 | mContentText.setMovementMethod(new ScrollingMovementMethod()); 63 | 64 | // 设置发送按钮的点击事件 65 | mSendBtn.setOnClickListener(new View.OnClickListener() { 66 | @Override public void onClick(View v) { 67 | String content = mInputEdit.getText().toString().trim(); 68 | if (!TextUtils.isEmpty(content)) { 69 | mInputEdit.setText(""); 70 | // 创建一条新消息,第一个参数为消息内容,第二个为接受者username 71 | EMMessage message = EMMessage.createTxtSendMessage(content, mChatId); 72 | // 将新的消息内容和时间加入到下边 73 | mContentText.setText(mContentText.getText() 74 | + "\n发送:" 75 | + content 76 | + " - time: " 77 | + message.getMsgTime()); 78 | // 调用发送消息的方法 79 | EMClient.getInstance().chatManager().sendMessage(message); 80 | // 为消息设置回调 81 | message.setMessageStatusCallback(new EMCallBack() { 82 | @Override public void onSuccess() { 83 | // 消息发送成功,打印下日志,正常操作应该去刷新ui 84 | Log.i("lzan13", "send message on success"); 85 | } 86 | 87 | @Override public void onError(int i, String s) { 88 | // 消息发送失败,打印下失败的信息,正常操作应该去刷新ui 89 | Log.i("lzan13", "send message on error " + i + " - " + s); 90 | } 91 | 92 | @Override public void onProgress(int i, String s) { 93 | // 消息发送进度,一般只有在发送图片和文件等消息才会有回调,txt不回调 94 | } 95 | }); 96 | } 97 | } 98 | }); 99 | } 100 | 101 | /** 102 | * 初始化会话对象,并且根据需要加载更多消息 103 | */ 104 | private void initConversation() { 105 | 106 | /** 107 | * 初始化会话对象,这里有三个参数么, 108 | * 第一个表示会话的当前聊天的 useranme 或者 groupid 109 | * 第二个是绘画类型可以为空 110 | * 第三个表示如果会话不存在是否创建 111 | */ 112 | mConversation = EMClient.getInstance().chatManager().getConversation(mChatId, null, true); 113 | // 设置当前会话未读数为 0 114 | mConversation.markAllMessagesAsRead(); 115 | int count = mConversation.getAllMessages().size(); 116 | if (count < mConversation.getAllMsgCount() && count < 20) { 117 | // 获取已经在列表中的最上边的一条消息id 118 | String msgId = mConversation.getAllMessages().get(0).getMsgId(); 119 | // 分页加载更多消息,需要传递已经加载的消息的最上边一条消息的id,以及需要加载的消息的条数 120 | mConversation.loadMoreMsgFromDB(msgId, 20 - count); 121 | } 122 | // 打开聊天界面获取最后一条消息内容并显示 123 | if (mConversation.getAllMessages().size() > 0) { 124 | EMMessage messge = mConversation.getLastMessage(); 125 | EMTextMessageBody body = (EMTextMessageBody) messge.getBody(); 126 | // 将消息内容和时间显示出来 127 | mContentText.setText( 128 | "聊天记录:" + body.getMessage() + " - time: " + mConversation.getLastMessage() 129 | .getMsgTime()); 130 | } 131 | } 132 | 133 | /** 134 | * 自定义实现Handler,主要用于刷新UI操作 135 | */ 136 | Handler mHandler = new Handler() { 137 | @Override public void handleMessage(Message msg) { 138 | switch (msg.what) { 139 | case 0: 140 | EMMessage message = (EMMessage) msg.obj; 141 | // 这里只是简单的demo,也只是测试文字消息的收发,所以直接将body转为EMTextMessageBody去获取内容 142 | EMTextMessageBody body = (EMTextMessageBody) message.getBody(); 143 | // 将新的消息内容和时间加入到下边 144 | mContentText.setText(mContentText.getText() 145 | + "\n接收:" 146 | + body.getMessage() 147 | + " - time: " 148 | + message.getMsgTime()); 149 | break; 150 | } 151 | } 152 | }; 153 | 154 | @Override protected void onResume() { 155 | super.onResume(); 156 | // 添加消息监听 157 | EMClient.getInstance().chatManager().addMessageListener(mMessageListener); 158 | } 159 | 160 | @Override protected void onStop() { 161 | super.onStop(); 162 | // 移除消息监听 163 | EMClient.getInstance().chatManager().removeMessageListener(mMessageListener); 164 | } 165 | /** 166 | * --------------------------------- Message Listener ------------------------------------- 167 | * 环信消息监听主要方法 168 | */ 169 | /** 170 | * 收到新消息 171 | * 172 | * @param list 收到的新消息集合 173 | */ 174 | @Override public void onMessageReceived(List list) { 175 | // 循环遍历当前收到的消息 176 | for (EMMessage message : list) { 177 | Log.i("lzan13", "收到新消息:" + message); 178 | if (message.getFrom().equals(mChatId)) { 179 | // 设置消息为已读 180 | mConversation.markMessageAsRead(message.getMsgId()); 181 | 182 | // 因为消息监听回调这里是非ui线程,所以要用handler去更新ui 183 | Message msg = mHandler.obtainMessage(); 184 | msg.what = 0; 185 | msg.obj = message; 186 | mHandler.sendMessage(msg); 187 | } else { 188 | // TODO 如果消息不是当前会话的消息发送通知栏通知 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * 收到新的 CMD 消息 195 | */ 196 | @Override public void onCmdMessageReceived(List list) { 197 | for (int i = 0; i < list.size(); i++) { 198 | // 透传消息 199 | EMMessage cmdMessage = list.get(i); 200 | EMCmdMessageBody body = (EMCmdMessageBody) cmdMessage.getBody(); 201 | Log.i("lzan13", "收到 CMD 透传消息" + body.action()); 202 | } 203 | } 204 | 205 | /** 206 | * 收到新的已读回执 207 | * 208 | * @param list 收到消息已读回执 209 | */ 210 | @Override public void onMessageRead(List list) {} 211 | 212 | /** 213 | * 收到新的发送回执 214 | * TODO 无效 暂时有bug 215 | * 216 | * @param list 收到发送回执的消息集合 217 | */ 218 | @Override public void onMessageDelivered(List list) {} 219 | 220 | /** 221 | * 消息撤回回调 222 | * 223 | * @param list 撤回的消息列表 224 | */ 225 | @Override public void onMessageRecalled(List list) {} 226 | 227 | /** 228 | * 消息的状态改变 229 | * 230 | * @param message 发生改变的消息 231 | * @param object 包含改变的消息 232 | */ 233 | @Override public void onMessageChanged(EMMessage message, Object object) {} 234 | } 235 | -------------------------------------------------------------------------------- /app/src/main/java/net/melove/demo/easechat/ECLoginActivity.java: -------------------------------------------------------------------------------- 1 | package net.melove.demo.easechat; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.Toast; 13 | 14 | import com.hyphenate.EMCallBack; 15 | import com.hyphenate.EMError; 16 | import com.hyphenate.chat.EMClient; 17 | import com.hyphenate.exceptions.HyphenateException; 18 | 19 | public class ECLoginActivity extends AppCompatActivity { 20 | // 弹出框 21 | private ProgressDialog mDialog; 22 | // username 输入框 23 | private EditText mUsernameEdit; 24 | // 密码输入框 25 | private EditText mPasswordEdit; 26 | // 注册按钮 27 | private Button mSignUpBtn; 28 | // 登录按钮 29 | private Button mSignInBtn; 30 | 31 | @Override protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_login); 34 | 35 | initView(); 36 | } 37 | 38 | /** 39 | * 初始化界面控件 40 | */ 41 | private void initView() { 42 | mUsernameEdit = findViewById(R.id.ec_edit_username); 43 | mPasswordEdit = findViewById(R.id.ec_edit_password); 44 | 45 | mSignUpBtn = findViewById(R.id.ec_btn_sign_up); 46 | mSignUpBtn.setOnClickListener(new View.OnClickListener() { 47 | @Override public void onClick(View v) { 48 | signUp(); 49 | } 50 | }); 51 | 52 | mSignInBtn = findViewById(R.id.ec_btn_sign_in); 53 | mSignInBtn.setOnClickListener(new View.OnClickListener() { 54 | @Override public void onClick(View v) { 55 | signIn(); 56 | } 57 | }); 58 | } 59 | 60 | /** 61 | * 注册方法 62 | */ 63 | private void signUp() { 64 | // 注册是耗时过程,所以要显示一个dialog来提示下用户 65 | mDialog = new ProgressDialog(this); 66 | mDialog.setMessage("注册中,请稍后..."); 67 | mDialog.show(); 68 | 69 | new Thread(new Runnable() { 70 | @Override public void run() { 71 | try { 72 | String username = mUsernameEdit.getText().toString().trim(); 73 | String password = mPasswordEdit.getText().toString().trim(); 74 | EMClient.getInstance().createAccount(username, password); 75 | runOnUiThread(new Runnable() { 76 | @Override public void run() { 77 | if (!ECLoginActivity.this.isFinishing()) { 78 | mDialog.dismiss(); 79 | } 80 | Toast.makeText(ECLoginActivity.this, "注册成功", Toast.LENGTH_LONG).show(); 81 | } 82 | }); 83 | } catch (final HyphenateException e) { 84 | e.printStackTrace(); 85 | runOnUiThread(new Runnable() { 86 | @Override public void run() { 87 | if (!ECLoginActivity.this.isFinishing()) { 88 | mDialog.dismiss(); 89 | } 90 | /** 91 | * 关于错误码可以参考官方api详细说明 92 | * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html 93 | */ 94 | int errorCode = e.getErrorCode(); 95 | String message = e.getMessage(); 96 | Log.d("lzan13", 97 | String.format("sign up - errorCode:%d, errorMsg:%s", errorCode, 98 | e.getMessage())); 99 | switch (errorCode) { 100 | // 网络错误 101 | case EMError.NETWORK_ERROR: 102 | Toast.makeText(ECLoginActivity.this, 103 | "网络错误 code: " + errorCode + ", message:" + message, 104 | Toast.LENGTH_LONG).show(); 105 | break; 106 | // 用户已存在 107 | case EMError.USER_ALREADY_EXIST: 108 | Toast.makeText(ECLoginActivity.this, 109 | "用户已存在 code: " + errorCode + ", message:" + message, 110 | Toast.LENGTH_LONG).show(); 111 | break; 112 | // 参数不合法,一般情况是username 使用了uuid导致,不能使用uuid注册 113 | case EMError.USER_ILLEGAL_ARGUMENT: 114 | Toast.makeText(ECLoginActivity.this, 115 | "参数不合法,一般情况是username 使用了uuid导致,不能使用uuid注册 code: " 116 | + errorCode 117 | + ", message:" 118 | + message, Toast.LENGTH_LONG).show(); 119 | break; 120 | // 服务器未知错误 121 | case EMError.SERVER_UNKNOWN_ERROR: 122 | Toast.makeText(ECLoginActivity.this, 123 | "服务器未知错误 code: " + errorCode + ", message:" + message, 124 | Toast.LENGTH_LONG).show(); 125 | break; 126 | case EMError.USER_REG_FAILED: 127 | Toast.makeText(ECLoginActivity.this, 128 | "账户注册失败 code: " + errorCode + ", message:" + message, 129 | Toast.LENGTH_LONG).show(); 130 | break; 131 | default: 132 | Toast.makeText(ECLoginActivity.this, 133 | "ml_sign_up_failed code: " + errorCode + ", message:" + message, 134 | Toast.LENGTH_LONG).show(); 135 | break; 136 | } 137 | } 138 | }); 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | }).start(); 144 | } 145 | 146 | /** 147 | * 登录方法 148 | */ 149 | private void signIn() { 150 | mDialog = new ProgressDialog(this); 151 | mDialog.setMessage("正在登陆,请稍后..."); 152 | mDialog.show(); 153 | String username = mUsernameEdit.getText().toString().trim(); 154 | String password = mPasswordEdit.getText().toString().trim(); 155 | if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { 156 | Toast.makeText(ECLoginActivity.this, "用户名和密码不能为空", Toast.LENGTH_LONG).show(); 157 | return; 158 | } 159 | EMClient.getInstance().login(username, password, new EMCallBack() { 160 | /** 161 | * 登陆成功的回调 162 | */ 163 | @Override public void onSuccess() { 164 | runOnUiThread(new Runnable() { 165 | @Override public void run() { 166 | mDialog.dismiss(); 167 | // 加载所有会话到内存 168 | EMClient.getInstance().chatManager().loadAllConversations(); 169 | // 加载所有群组到内存,如果使用了群组的话 170 | // EMClient.getInstance().groupManager().loadAllGroups(); 171 | // 登录成功跳转界面 172 | Intent intent = new Intent(ECLoginActivity.this, ECMainActivity.class); 173 | startActivity(intent); 174 | finish(); 175 | } 176 | }); 177 | } 178 | 179 | /** 180 | * 登陆错误的回调 181 | * @param i 182 | * @param s 183 | */ 184 | @Override public void onError(final int i, final String s) { 185 | runOnUiThread(new Runnable() { 186 | @Override public void run() { 187 | mDialog.dismiss(); 188 | Log.d("lzan13", "登录失败 Error code:" + i + ", message:" + s); 189 | /** 190 | * 关于错误码可以参考官方api详细说明 191 | * http://www.easemob.com/apidoc/android/chat3.0/classcom_1_1hyphenate_1_1_e_m_error.html 192 | */ 193 | switch (i) { 194 | // 网络异常 2 195 | case EMError.NETWORK_ERROR: 196 | Toast.makeText(ECLoginActivity.this, 197 | "网络错误 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 198 | break; 199 | // 无效的用户名 101 200 | case EMError.INVALID_USER_NAME: 201 | Toast.makeText(ECLoginActivity.this, 202 | "无效的用户名 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 203 | break; 204 | // 无效的密码 102 205 | case EMError.INVALID_PASSWORD: 206 | Toast.makeText(ECLoginActivity.this, 207 | "无效的密码 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 208 | break; 209 | // 用户认证失败,用户名或密码错误 202 210 | case EMError.USER_AUTHENTICATION_FAILED: 211 | Toast.makeText(ECLoginActivity.this, 212 | "用户认证失败,用户名或密码错误 code: " + i + ", message:" + s, Toast.LENGTH_LONG) 213 | .show(); 214 | break; 215 | // 用户不存在 204 216 | case EMError.USER_NOT_FOUND: 217 | Toast.makeText(ECLoginActivity.this, 218 | "用户不存在 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 219 | break; 220 | // 无法访问到服务器 300 221 | case EMError.SERVER_NOT_REACHABLE: 222 | Toast.makeText(ECLoginActivity.this, 223 | "无法访问到服务器 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 224 | break; 225 | // 等待服务器响应超时 301 226 | case EMError.SERVER_TIMEOUT: 227 | Toast.makeText(ECLoginActivity.this, 228 | "等待服务器响应超时 code: " + i + ", message:" + s, Toast.LENGTH_LONG) 229 | .show(); 230 | break; 231 | // 服务器繁忙 302 232 | case EMError.SERVER_BUSY: 233 | Toast.makeText(ECLoginActivity.this, 234 | "服务器繁忙 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 235 | break; 236 | // 未知 Server 异常 303 一般断网会出现这个错误 237 | case EMError.SERVER_UNKNOWN_ERROR: 238 | Toast.makeText(ECLoginActivity.this, 239 | "未知的服务器异常 code: " + i + ", message:" + s, Toast.LENGTH_LONG).show(); 240 | break; 241 | default: 242 | Toast.makeText(ECLoginActivity.this, 243 | "ml_sign_in_failed code: " + i + ", message:" + s, 244 | Toast.LENGTH_LONG).show(); 245 | break; 246 | } 247 | } 248 | }); 249 | } 250 | 251 | @Override public void onProgress(int i, String s) { 252 | 253 | } 254 | }); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/net/melove/demo/easechat/ECMainActivity.java: -------------------------------------------------------------------------------- 1 | package net.melove.demo.easechat; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.Toast; 12 | 13 | import com.hyphenate.EMCallBack; 14 | import com.hyphenate.chat.EMClient; 15 | 16 | public class ECMainActivity extends AppCompatActivity { 17 | 18 | // 发起聊天 username 输入框 19 | private EditText mChatIdEdit; 20 | // 发起聊天 21 | private Button mStartChatBtn; 22 | // 退出登录 23 | private Button mSignOutBtn; 24 | 25 | @Override protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | // 判断sdk是否登录成功过,并没有退出和被踢,否则跳转到登陆界面 29 | if (!EMClient.getInstance().isLoggedInBefore()) { 30 | Intent intent = new Intent(ECMainActivity.this, ECLoginActivity.class); 31 | startActivity(intent); 32 | finish(); 33 | return; 34 | } 35 | 36 | setContentView(R.layout.activity_main); 37 | 38 | initView(); 39 | } 40 | 41 | /** 42 | * 初始化界面 43 | */ 44 | private void initView() { 45 | 46 | mChatIdEdit = findViewById(R.id.ec_edit_chat_id); 47 | 48 | mStartChatBtn = findViewById(R.id.ec_btn_start_chat); 49 | mStartChatBtn.setOnClickListener(new View.OnClickListener() { 50 | @Override public void onClick(View v) { 51 | // 获取我们发起聊天的者的username 52 | String chatId = mChatIdEdit.getText().toString().trim(); 53 | if (!TextUtils.isEmpty(chatId)) { 54 | // 获取当前登录用户的 username 55 | String currUsername = EMClient.getInstance().getCurrentUser(); 56 | if (chatId.equals(currUsername)) { 57 | Toast.makeText(ECMainActivity.this, "不能和自己聊天", Toast.LENGTH_SHORT).show(); 58 | return; 59 | } 60 | // 跳转到聊天界面,开始聊天 61 | Intent intent = new Intent(ECMainActivity.this, ECChatActivity.class); 62 | intent.putExtra("ec_chat_id", chatId); 63 | startActivity(intent); 64 | } else { 65 | Toast.makeText(ECMainActivity.this, "Username 不能为空", Toast.LENGTH_LONG).show(); 66 | } 67 | } 68 | }); 69 | 70 | mSignOutBtn = findViewById(R.id.ec_btn_sign_out); 71 | mSignOutBtn.setOnClickListener(new View.OnClickListener() { 72 | @Override public void onClick(View v) { 73 | signOut(); 74 | } 75 | }); 76 | } 77 | 78 | /** 79 | * 退出登录 80 | */ 81 | private void signOut() { 82 | // 调用sdk的退出登录方法,第一个参数表示是否解绑推送的token,没有使用推送或者被踢都要传false 83 | EMClient.getInstance().logout(false, new EMCallBack() { 84 | @Override public void onSuccess() { 85 | Log.i("lzan13", "logout success"); 86 | // 调用退出成功,结束app 87 | finish(); 88 | } 89 | 90 | @Override public void onError(int i, String s) { 91 | Log.i("lzan13", "logout error " + i + " - " + s); 92 | } 93 | 94 | @Override public void onProgress(int i, String s) { 95 | 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 19 | 20 |