客户端集成环信推送【被挤下线】原理及步骤
1.原理:
被挤下线
功能,即一个账号在A客户端保持登陆状态,然后又在B客户端进行了登陆操作,那么A客户端就会被挤下线。
当用户user1使用账号123456登录app后,用户user2在另一台手机使用同一账号123456登录,这时需要给A发通知给出提示,并强制user1下线。在app不在前台(包括完全退出和按了home键)的时候,不给提示,重新进入app给出提示.
那么其实有4种情况:
1.user1在app内,直接弹出dialog并强制下线;
非常好实现
2.user1按了home键
这时候是能收到消息的,但是不应该给出提示,需要在app onResume的时候弹出提示。我是这么做的,在Receiver的onReceive方法中进行状态保存(使用SP保存个值userLoginAnotherPlace),在BaseActiviy的onResume方法里进行判断,userLoginAnotherPlace的值变化了,那么弹出提示。
3.user1退出app,但是service仍然在后台运行,这时候是能收到推送消息的
同2.
4.user1完全杀死app,收不到推送消息,但是在很短的一段时间内重新打开app,消息仍然会收到。这时候因为极光会提供有限时长的离线消息
同1,但是有一个小问题(情况1一般不会出现,但是也是有可能的),一般app都有启动页,那其实不应该再启动页就弹出dialog,这样会造成非常差的用户体验,所以在弹出dialog的时候要对当前正在运行的Activity进行判断。
5.user1完全杀死app,收不到推送消息,但是过了很长的一段时间后重新打开app,消息不会收到。
这时候会出现这种现象,user1和user2同时登录成功。但是没给user1提示。所以在启动的时候强制用户user1自动登录来进行状态的判别,比如返回200是正常登录成功,自动登录返回203(注:自动登录和正常登录的服务器接口需要区分开,可以使用不同接口,也可以通过添加参数来标识)
2.说明:
Android SDK 导入
集成前准备
手动复制jar包及so导入
到环信官网下载环信 SDK。
考虑到开发者需求不一样,在下载的 SDK 中,提供了两个不同的 jar 包:
-
一个是 libs 下带实时语音功能和实时视频功能的 jar 包和 so 文件。
-
如果你不需要实时语音、实时视频功能,那就直接用 libs.without.audio 文件夹下的 jar 包及 so 文件。
SDK 目录讲解
从官网上下载下来的包,解压后内容如下:
在这里主要介绍后面四个文件夹内容:
-
doc 文件夹:SDK 相关 API 文档
-
examples 文件夹:ChatDemoUI3.0(Demo,依赖 EaseUI 库)、EaseUI
-
libs.av 文件夹:包含IM和实时音视频功能所需要的 jar 和 so 文件
-
libs.lite 文件夹:无实时语音、实时视频功能的 SDK 包,如果项目中只用到聊天功能,可以把项目里的 jar 和 so 文件替换成此文件夹里的文件
配置工程
导入 SDK
在自行开发的应用中,集成环信聊天需要把 libs 文件夹下的 jar 及 so 文件复制到你的项目的 libs 文件夹相应位置,如果不需要语音和视频通话功能,导入libs.lite 下的文件即可。
配置信息
在清单文件 AndroidManifest.xml 里加入以下权限,以及写上你注册的 AppKey。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- 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" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
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>
关于 EASEMOB_APPKEY 对应的 value 获取,在创建应用后,申请 AppKey 并进行相关配置。
注意事项:在build.gradle中添加so库配置,不然报错哟
sourceSets { main { jniLibs.srcDirs = ['libs'] } }
APP 打包混淆
在 ProGuard 文件中加入以下 keep。
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**
添加了混淆代码后 在build.gradle的minifyEnabled false 设置为true ,作用:为了防止别人篡改或查看自己的代码
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
3.进入代码:
初始化 SDK
集成第一步:要求自定义App继承 application重写oncreate
方法中做初始化
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(context.getPackageName())) { Log.e("TAG", "enter the service process!"); // 则此application::onCreate 是被service 调用的,直接返回 return; }
注意事项:需在清单文件做声明自定义的App类
<application android:name=".login_register.App"
集成第二步 : 在自定义App中操作
EMOptions options = new EMOptions();
// 默认添加好友时,是不需要验证的,改成需要验证
options.setAcceptInvitationAlways(false);
// 是否自动将消息附件上传到环信服务器,默认为True是使用环信服务器上传下载,如果设为 false,需要开发者自己处理附件消息的上传和下载
options.setAutoTransferMessageAttachments(true);
// 是否自动下载附件类消息的缩略图等,默认为 true 这里和上边这个参数相关联
options.setAutoDownloadThumbnail(true);
//初始化
EMClient.getInstance().init(context, options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
//第6步自动登录
options.setAutoLogin(true);
集成第三步 :在自定义App中操作,getAppName实现监听
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; }
集成第四步:在LogActivity中操作
注册
注意事项:因为咱们的后台服务器是环信(当然如果后台是自己公司里的服务器也一样)所以在注册需在环信管理后台中+注册IM用户,然后才能在自己的项目中登录时用咱们在环信后台注册的用户填写用户名和密码。
注册模式分两种,开放注册和授权注册。只有开放注册时,才可以客户端注册。
//正则手机号匹配规则
private String userlog="[1][3,4,5,7,8][0-9]{9}";if(user.matches(userlog)){//注册失败会抛出
EMClient.getInstance().login(user,password,LoginActivity.this);}
集成第五步:实现,LoginActivity.this全局监听,在LogActivity中操作
@Override public void onSuccess() { EMClient.getInstance().groupManager().loadAllGroups(); EMClient.getInstance().chatManager().loadAllConversations(); Log.d("main", "登录聊天服务器成功!"); Intent intent = new Intent(LoginActivity.this, MainActivity.class); startActivityForResult(intent,1); } @Override public void onError(int i, String s) { } @Override public void onProgress(int i, String s) { }
集成第六步:自动登录options.setAutoLogin(true)此方法
在第二步App中:
SDK 中自动登录属性默认是 true 打开的,如果不需要自动登录,在初始化 SDK 初始化的时候,调用options.setAutoLogin(false);
设置为 false 关闭。
集成第八步:回调弹出Dialog
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==1&&resultCode==2){ new AlertDialog.Builder(this) .setTitle("微信提示") .setMessage("您已下线") .setNegativeButton("退出",null) .setPositiveButton("重新登录", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new Thread(new Runnable() { @Override public void run() { EMClient.getInstance().login(et_login_user.getText().toString(),et_login_password.getText().toString(),LoginActivity.this); } }).start(); } }).create().show(); } }
集成第九步:在MainActivity中oncreate()在初始控件initView()之前调用
new Thread(new Runnable() { @Override public void run() { //第八步注册一个监听连接状态的listener EMClient.getInstance().addConnectionListener(new MyConnectionListener()); } }).start();
集成第十步:在MainActivity中实现ConnectionListener接口
private class MyConnectionListener implements EMConnectionListener {
@Override
public void onConnected() {
}
@Override
public void onDisconnected(final int error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(error == EMError.USER_REMOVED){
// 显示帐号已经被移除
Toast.makeText(MainActivity.this,"aaaa1",Toast.LENGTH_SHORT).show();
}else if (error == EMError.USER_LOGIN_ANOTHER_DEVICE) {
// 显示帐号在其他设备登录
new Thread(new Runnable() {
@Override
public void run() {
//第7步 同步退出登录
EMClient.getInstance().logout(true);
}
}).start();
setResult(2);
finish();
} else {
if (NetUtils.hasNetwork(MainActivity.this)){
//连接不到聊天服务器
}else{
//当前网络不可用,请检查网络设置
}
}
}
});
}
实现以上步骤那么就完成了,此次项目的需求(实现A用户登录后,B登录时A用户被迫下线并弹出Dilog提示框是否重新登录或退出)。
最后咱们看看最后的图片显示: