1、注册账号
2、创建应用
![](https://img.haomeiwen.com/i16287050/11d3f591f2f51975.png)
![](https://img.haomeiwen.com/i16287050/109ea204e1a93b90.png)
3、创建IM用户
![](https://img.haomeiwen.com/i16287050/7c03cbe77d22d4a8.png)
4、集成
文档地址:http://docs-im.easemob.com/im/android/sdk/import
SDK下载地址:http://www.easemob.com/download/im
5、下载sdk+demo
![](https://img.haomeiwen.com/i16287050/ebb5e7244498ab23.png)
![](https://img.haomeiwen.com/i16287050/d54acb1c50d87793.png)
在这里主要介绍后面四个文件夹内容:
doc 文件夹:SDK 相关 API 文档
examples 文件夹:ChatDemoUI3.0(Demo,依赖 EaseUI 库)、EaseUI
libs.av 文件夹:包含IM和实时音视频功能所需要的 jar 和 so 文件
libs.lite 文件夹:无实时语音、实时视频功能的 SDK 包,如果项目中只用到聊天功能,可以把项目里的 jar 和 so 文件替换成此文件夹里的文件
6、配置工程
导入 SDK
在自行开发的应用中,集成环信聊天需要把 libs 文件夹下的 jar 及 so 文件复制到你的项目的 libs 文件夹相应位置,如果不需要语音和视频通话功能,导入libs.lite 下的文件即可。
导完libs文件夹后在项目的build.gradle文件中添加以下代码
android {
......
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
7、清单文件配置,注意设置appkey
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.huanxin">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="MockLocation,ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 设置环信应用的AppKey -->
<meta-data
android:name="EASEMOB_APPKEY"
android:value="1103190409211363#galiao" />
<!-- 声明SDK所需的service SDK核心功能 -->
<service
android:name="com.hyphenate.chat.EMChatService"
android:exported="true" />
<service
android:name="com.hyphenate.chat.EMJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<activity android:name=".MainActivity" />
<activity android:name=".RegisterActivity"></activity>
</application>
</manifest>
8、MyApplication
import android.app.ActivityManager;
import android.app.Application;
import android.content.pm.PackageManager;
import android.util.Log;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMOptions;
import java.util.Iterator;
import java.util.List;
public class MyApplication extends Application{
private static final String TAG = "MyApplication";
private static MyApplication myApplication;
@Override
public void onCreate() {
super.onCreate();
myApplication=this;
initMob();
}
private void initMob() {
EMOptions options = new EMOptions();
// 默认添加好友时,是不需要验证的,改成需要验证
options.setAcceptInvitationAlways(false);
// 是否自动将消息附件上传到环信服务器,默认为True是使用环信服务器上传下载,如果设为 false,需要开发者自己处理附件消息的上传和下载
options.setAutoTransferMessageAttachments(true);
// 是否自动下载附件类消息的缩略图等,默认为 true 这里和上边这个参数相关联
options.setAutoDownloadThumbnail(true);
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null ||!processAppName.equalsIgnoreCase(getPackageName())) {
Log.e(TAG, "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(this, options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
public static MyApplication getInstance(){
return myApplication;
}
}
9、工具类
ToastUtil(Toast的工具类)
import android.widget.Toast;
import com.example.huanxin.MyApplication;
public class ToastUtil {
public static void showShort(String msg){
//避免内存泄漏的一个方法,用到上下文的地方,能用application的就application
Toast.makeText(MyApplication.getInstance(),msg,Toast.LENGTH_SHORT).show();
}
public static void showLong(String msg){
//避免内存泄漏的一个方法,用到上下文的地方,能用application的就application
Toast.makeText(MyApplication.getInstance(),msg,Toast.LENGTH_LONG).show();
}
}
SpUtil(Sharepreference的工具类)
import android.content.Context;
import android.content.SharedPreferences;
import com.example.huanxin.MyApplication;
//Sharepreference
public class SpUtil {
/**
* 保存在手机里面的文件名
*/
private static final String FILE_NAME = "share_date";
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
* @param key
* @param object
*/
public static void setParam( String key, Object object){
String type = object.getClass().getSimpleName();
SharedPreferences sp = MyApplication.getInstance().getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
if("String".equals(type)){
editor.putString(key, (String)object);
}
else if("Integer".equals(type)){
editor.putInt(key, (Integer)object);
}
else if("Boolean".equals(type)){
editor.putBoolean(key, (Boolean)object);
}
else if("Float".equals(type)){
editor.putFloat(key, (Float)object);
}
else if("Long".equals(type)){
editor.putLong(key, (Long)object);
}
editor.commit();
}
/**
* 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
* @param key
* @param defaultObject
* @return
*/
public static Object getParam(String key, Object defaultObject){
String type = defaultObject.getClass().getSimpleName();
SharedPreferences sp = MyApplication.getInstance().getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
if("String".equals(type)){
return sp.getString(key, (String)defaultObject);
}
else if("Integer".equals(type)){
return sp.getInt(key, (Integer)defaultObject);
}
else if("Boolean".equals(type)){
return sp.getBoolean(key, (Boolean)defaultObject);
}
else if("Float".equals(type)){
return sp.getFloat(key, (Float)defaultObject);
}
else if("Long".equals(type)){
return sp.getLong(key, (Long)defaultObject);
}
return null;
}
}
AudioUtil(录音的工具类)
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class AudioUtil {
/**
* 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
*/
public static final int SAMPLE_RATE_INHZ = 44100;
/**
* 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
*/
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
/**
* 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
*/
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final String TAG = "AudioUtil";
private static AudioRecord audioRecord;
private static String mPath = Environment.getExternalStorageDirectory().getAbsolutePath()
+"/录音/";
public static boolean isRecording;
private static long mAudioName;
private static long sEndTime;
private static long sDuration;
/**
* 开始录音
* @param callBack
*/
public static void startRecord(final ResultCallBack callBack) {
//当前时间的毫秒
mAudioName = System.currentTimeMillis();
final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
final byte data[] = new byte[minBufferSize];
final File file = new File(mPath, mAudioName+".pcm");
Log.d(TAG, "startRecord: "+mPath);
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
}
if (file.exists()) {
file.delete();
}
//录音
audioRecord.startRecording();
isRecording = true;
//将录音写入本地文件
new Thread(new Runnable() {
@Override
public void run() {
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (null != os) {
while (isRecording) {
int read = audioRecord.read(data, 0, minBufferSize);
// 如果读取音频数据没有出现错误,就将数据写入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
Log.i(TAG, "run: close file output stream !");
os.close();
} catch (IOException e) {
e.printStackTrace();
callBack.onFail(e.toString());
}
sEndTime = System.currentTimeMillis();
sDuration = sEndTime - mAudioName;
//将PCM格式的录音转换为wav的,方便播放
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
File pcmFile = new File(mPath, mAudioName+".pcm");
File wavFile = new File(mPath, mAudioName+".wav");
if (!wavFile.mkdirs()) {
Log.e(TAG, "wavFile Directory not created");
}
if (wavFile.exists()) {
wavFile.delete();
}
pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath());
//将结果回调给调用者
callBack.onSuccess(wavFile.getAbsolutePath(),sDuration);
}
}
}).start();
}
/**
* 停止录音
*/
public static void stopRecord() {
isRecording = false;
// 释放资源
if (null != audioRecord) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
//recordingThread = null;
}
}
/**
* 将pcm音频文件转换为wav音频文件
*/
public static class PcmToWavUtil {
/**
* 缓存的音频大小
*/
private static int mBufferSize;
/**
* 采样率
*/
private static int mSampleRate;
/**
* 声道数
*/
private static int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public static void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件头
*/
private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
public interface ResultCallBack{
void onSuccess(String path, long time);
void onFail(String msg);
}
}
10、常量接口
public interface Constants {
String NAME="name";
String MName="mname";
}
11、LoginActivity(登录界面)
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity"
android:orientation="vertical">
<EditText
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"/>
<EditText
android:id="@+id/user_psw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"/>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"/>
<Button
android:id="@+id/register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册"/>
</LinearLayout>
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
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.example.huanxin.utils.SpUtil;
import com.example.huanxin.utils.ToastUtil;
import com.hyphenate.EMCallBack;
import com.hyphenate.chat.EMClient;
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
private EditText user_name;
private EditText user_psw;
private Button login;
private Button register;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 判断之前有没有登陆过
boolean loggedInBefore = EMClient.getInstance().isLoggedInBefore();
if (loggedInBefore){
goMainActivity();
}
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
user_name = (EditText) findViewById(R.id.user_name);
user_psw = (EditText) findViewById(R.id.user_psw);
login = (Button) findViewById(R.id.login);
register = (Button) findViewById(R.id.register);
login.setOnClickListener(this);
register.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.login:
submit();
break;
case R.id.register:
register();
break;
}
}
private void register() {
startActivity(new Intent(LoginActivity.this,RegisterActivity.class));
}
private void submit() {
// validate
final String name = user_name.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "请输入用户名", Toast.LENGTH_SHORT).show();
return;
}
final String psw = user_psw.getText().toString().trim();
if (TextUtils.isEmpty(psw)) {
Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
}
// TODO validate success, do something
new Thread(new Runnable() {
@Override
public void run() {
EMClient.getInstance().login(name,psw,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
//保存用户名
SpUtil.setParam(Constants.NAME,name);
showToast("登陆成功");
goMainActivity();
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
showToast("登录失败");
}
});
}
}).start();
}
private void goMainActivity() {
startActivity(new Intent(LoginActivity.this,MainActivity.class));
finish();
}
private void showToast(final String string) {
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtil.showShort(string);
}
});
}
}
12、RegisterActivity(注册界面)
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RegisterActivity"
android:orientation="vertical">
<EditText
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"/>
<EditText
android:id="@+id/user_psw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"/>
<Button
android:id="@+id/register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册"/>
</LinearLayout>
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.huanxin.utils.SpUtil;
import com.example.huanxin.utils.ToastUtil;
import com.hyphenate.chat.EMClient;
import com.hyphenate.exceptions.HyphenateException;
public class RegisterActivity extends AppCompatActivity implements View.OnClickListener {
private EditText user_name;
private EditText user_psw;
private Button register;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
private void initView() {
user_name = (EditText) findViewById(R.id.user_name);
user_psw = (EditText) findViewById(R.id.user_psw);
register = (Button) findViewById(R.id.register);
register.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.register:
submit();
break;
}
}
private void submit() {
// validate
final String name = user_name.getText().toString().trim();
if (TextUtils.isEmpty(name)) {
ToastUtil.showShort("请输入用户名");
return;
}
final String psw = user_psw.getText().toString().trim();
if (TextUtils.isEmpty(psw)) {
ToastUtil.showShort("请输入密码");
return;
}
// TODO validate success, do something
new Thread(new Runnable() {
@Override
public void run() {
//注册失败会抛出HyphenateException
try {
EMClient.getInstance().createAccount(name, psw);//同步方法
showToast("注册成功");
goLoginActivity();
} catch (HyphenateException e) {
showToast("注册失败");
e.printStackTrace();
}
}
}).start();
}
private void goLoginActivity() {
startActivity(new Intent(RegisterActivity.this,MainActivity.class));
finish();
}
private void showToast(final String string) {
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtil.showShort(string);
}
});
}
}
网友评论