版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯
本篇文章为大家展示了怎么在Android中利用WebSocket实现即时通讯,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。WebSocketWebSocket协议就不细讲了,感兴趣的可以具体查阅资料,简而言之,它就是一个可以建立长连接的全双工(full-duplex)通信协议,允许服务器端主动发送信息给客户端。Java-WebSocket框架对于使用websocket协议,Android端已经有些成熟的框架了,在经过对比之后,我选择了Java-WebSocket这个开源框架,目前已经有五千以上star,并且还在更新维护中,所以本文将介绍如何利用此开源库实现一个稳定的即时通讯功能。1、与websocket建立长连接2、与websocket进行即时通讯3、Service和Activity之间通讯和UI更新4、弹出消息通知(包括锁屏通知)5、心跳检测和重连(保证websocket连接稳定性)6、服务(Service)保活一、引入Java-WebSocket1、build.gradle中加入implementation
"org.java-websocket:Java-WebSocket:1.4.0"2、加入网络请求权限<uses-permission
android:name="android.permission.INTERNET"
/>3、新建客户端类新建一个客户端类并继承WebSocketClient,需要实现它的四个抽象方法和构造函数,如下:public
class
JWebSocketClient
extends
WebSocketClient
{
public
JWebSocketClient(URI
serverUri)
{
super(serverUri,
new
Draft_6455());
}
@Override
public
void
onOpen(ServerHandshake
handshakedata)
{
Log.e("JWebSocketClient",
"onOpen()");
}
@Override
public
void
onMessage(String
message)
{
Log.e("JWebSocketClient",
"onMessage()");
}
@Override
public
void
onClose(int
code,
String
reason,
boolean
remote)
{
Log.e("JWebSocketClient",
"onClose()");
}
@Override
public
void
onError(Exception
ex)
{
Log.e("JWebSocketClient",
"onError()");
}
}其中onOpen()方法在websocket连接开启时调用,onMessage()方法在接收到消息时调用,onClose()方法在连接断开时调用,onError()方法在连接出错时调用。构造方法中的newDraft_6455()代表使用的协议版本,这里可以不写或者写成这样即可。4、建立websocket连接建立连接只需要初始化此客户端再调用连接方法,需要注意的是WebSocketClient对象是不能重复使用的,所以不能重复初始化,其他地方只能调用当前这个Client。URI
uri
=
URI.create("ws://*******");
JWebSocketClient
client
=
new
JWebSocketClient(uri)
{
@Override
public
void
onMessage(String
message)
{
//message就是接收到的消息
Log.e("JWebSClientService",
message);
}
};为了方便对接收到的消息进行处理,可以在这重写onMessage()方法。初始化客户端时需要传入websocket地址(测试地址:ws://),websocket协议地址大致是这样的ws://ip地址:端口号连接时可以使用connect()方法或connectBlocking()方法,建议使用connectBlocking()方法,connectBlocking多出一个等待操作,会先连接再发送。try
{
client.connectBlocking();
}
catch
(InterruptedException
e)
{
e.printStackTrace();
}运行之后可以看到客户端的onOpen()方法得到了执行,表示已经和websocket建立了连接5、发送消息发送消息只需要调用send()方法,如下if
(client
!=
null
&&
client.isOpen())
{
client.send("你好");
}6、关闭socket连接关闭连接调用close()方法,最后为了避免重复实例化WebSocketClient对象,关闭时一定要将对象置空。/**
*
断开连接
*/
private
void
closeConnect()
{
try
{
if
(null
!=
client)
{
client.close();
}
}
catch
(Exception
e)
{
e.printStackTrace();
}
finally
{
client
=
null;
}
}二、后台运行一般来说即时通讯功能都希望像QQ微信这些App一样能在后台保持运行,当然App保活这个问题本身就是个伪命题,我们只能尽可能保活,所以首先就是建一个Service,将websocket的逻辑放入服务中运行并尽可能保活,让websocket保持连接。1、新建Service新建一个Service,在启动Service时实例化WebSocketClient对象并建立连接,将上面的代码搬到服务里即可。2、Service和Activity之间通讯由于消息是在Service中接收,从Activity中发送,需要获取到Service中的WebSocketClient对象,所以需要进行服务和活动之间的通讯,这就需要用到Service中的onBind()方法了。首先新建一个Binder类,让它继承自Binder,并在内部提供相应方法,然后在onBind()方法中返回这个类的实例。public
class
JWebSocketClientService
extends
Service
{
private
URI
uri;
public
JWebSocketClient
client;
private
JWebSocketClientBinder
mBinder
=
new
JWebSocketClientBinder();
//用于Activity和service通讯
class
JWebSocketClientBinder
extends
Binder
{
public
JWebSocketClientService
getService()
{
return
JWebSocketClientService.this;
}
}
@Override
public
IBinder
onBind(Intent
intent)
{
return
mBinder;
}
}接下来就需要对应的Activity绑定Service,并获取Service的东西,代码如下public
class
MainActivity
extends
AppCompatActivity
{
private
JWebSocketClient
client;
private
JWebSocketClientService.JWebSocketClientBinder
binder;
private
JWebSocketClientService
jWebSClientService;
private
ServiceConnection
serviceConnection
=
new
ServiceConnection()
{
@Override
public
void
onServiceConnected(ComponentName
componentName,
IBinder
iBinder)
{
//服务与活动成功绑定
Log.e("MainActivity",
"服务与活动成功绑定");
binder
=
(JWebSocketClientService.JWebSocketClientBinder)
iBinder;
jWebSClientService
=
binder.getService();
client
=
jWebSClientService.client;
}
@Override
public
void
onServiceDisconnected(ComponentName
componentName)
{
//服务与活动断开
Log.e("MainActivity",
"服务与活动成功断开");
}
};
@Override
protected
void
onCreate(Bundle
savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
}
/**
*
绑定服务
*/
private
void
bindService()
{
Intent
bindIntent
=
new
Intent(MainActivity.this,
JWebSocketClientService.class);
bindService(bindIntent,
serviceConnection,
BIND_AUTO_CREATE);
}
}这里首先创建了一个ServiceConnection匿名类,在里面重写onServiceConnected()和onServiceDisconnected()方法,这两个方法会在活动与服务成功绑定以及连接断开时调用。在onServiceConnected()首先得到JWebSocketClientBinder的实例,有了这个实例便可调用服务的任何public方法,这里调用getService()方法得到Service实例,得到了Service实例也就得到了WebSocketClient对象,也就可以在活动中发送消息了。三、从Service中更新Activity的UI当Service中接收到消息时需要更新Activity中的界面,方法有很多,这里我们利用广播来实现,在对应Activity中定义广播接收者,Service中收到消息发出广播即可。public
class
MainActivity
extends
AppCompatActivity
{
...
private
class
ChatMessageReceiver
extends
BroadcastReceiver{
@Override
public
void
onReceive(Context
context,
Intent
intent)
{
String
message=intent.getStringExtra("message");
}
}
/**
*
动态注册广播
*/
private
void
doRegisterReceiver()
{
chatMessageReceiver
=
new
ChatMessageReceiver();
IntentFilter
filter
=
new
IntentFilter("com.xch.servicecallback.content");
registerReceiver(chatMessageReceiver,
filter);
}
...
}上面的代码很简单,首先创建一个内部类并继承自BroadcastReceiver,也就是代码中的广播接收器ChatMessageReceiver,然后动态注册这个广播接收器。当Service中接收到消息时发出广播,就能在ChatMessageReceiver里接收广播了。发送广播:client
=
new
JWebSocketClient(uri)
{
@Override
public
void
onMessage(String
message)
{
Intent
intent
=
new
Intent();
intent.setAction("com.xch.servicecallback.content");
intent.putExtra("message",
message);
sendBroadcast(intent);
}
};获取广播传过来的消息后即可更新UI,具体布局就不细说,比较简单,看下我的源码就知道了,demo地址我会放到文章末尾。四、消息通知消息通知直接使用Notification,只是当锁屏时需要先点亮屏幕,代码如下
/**
*
检查锁屏状态,如果锁屏先点亮屏幕
*
*
@param
content
*/
private
void
checkLockAndShowNotification(String
content)
{
//管理锁屏的一个服务
KeyguardManager
km
=
(KeyguardManager)
getSystemService(Context.KEYGUARD_SERVICE);
if
(km.inKeyguardRestrictedInputMode())
{//锁屏
//获取电源管理器对象
PowerManager
pm
=
(PowerManager)
this.getSystemService(Context.POWER_SERVICE);
if
(!pm.isScreenOn())
{
@SuppressLint("InvalidWakeLockTag")
PowerManager.WakeLock
wl
=
pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP
|
PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
"bright");
wl.acquire();
//点亮屏幕
wl.release();
//任务结束后释放
}
sendNotification(content);
}
else
{
sendNotification(content);
}
}
/**
*
发送通知
*
*
@param
content
*/
private
void
sendNotification(String
content)
{
Intent
intent
=
new
Intent();
intent.setClass(this,
MainActivity.class);
PendingIntent
pendingIntent
=
PendingIntent.getActivity(this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager
notifyManager
=
(NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
Notification
notification
=
new
NotificationCompat.Builder(this)
.setAutoCancel(true)
//
设置该通知优先级
.setPriority(Notification.PRIORITY_MAX)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("昵称")
.setContentText(content)
.setVisibility(VISIBILITY_PUBLIC)
.setWhen(System.currentTimeMillis())
//
向通知添加声音、闪灯和振动效果
.setDefaults(Notification.DEFAULT_VIBRATE
|
Notification.DEFAULT_ALL
|
Notification.DEFAULT_SOUND)
.setContentIntent(pendingIntent)
.build();
notifyManager.notify(1,
notification);//id要保证唯一
}如果未收到通知可能是设置里通知没开,进入设置打开即可,如果锁屏时无法弹出通知,可能是未开启锁屏通知权限,也需进入设置开启。为了保险起见我们可以判断通知是否开启,未开启引导用户开启,代码如下:最后加/**
*
检测是否开启通知
*
*
@param
context
*/
private
void
checkNotification(final
Context
context)
{
if
(!isNotificationEnabled(context))
{
new
AlertDialog.Builder(context).setTitle("温馨提示")
.setMessage("你还未开启系统通知,将影响消息的接收,要去开启吗?")
.setPositiveButton("确定",
new
DialogInterface.OnClickListener()
{
@Override
public
void
onClick(DialogInterface
dialog,
int
which)
{
setNotification(context);
}
}).setNegativeButton("取消",
new
DialogInterface.OnClickListener()
{
@Override
public
void
onClick(DialogInterface
dialog,
int
which)
{
}
}).show();
}
}
/**
*
如果没有开启通知,跳转至设置界面
*
*
@param
context
*/
private
void
setNotification(Context
context)
{
Intent
localIntent
=
new
Intent();
//直接跳转到应用通知设置的代码:
if
(android.os.Build.VERSION.SDK_INT
>=
Build.VERSION_CODES.LOLLIPOP)
{
localIntent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
localIntent.putExtra("app_package",
context.getPackageName());
localIntent.putExtra("app_uid",
context.getApplicationInfo().uid);
}
else
if
(android.os.Build.VERSION.SDK_INT
==
Build.VERSION_CODES.KITKAT)
{
localIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
localIntent.addCategory(Intent.CATEGORY_DEFAULT);
localIntent.setData(Uri.parse("package:"
+
context.getPackageName()));
}
else
{
//4.4以下没有从app跳转到应用通知设置页面的Action,可考虑跳转到应用详情页面
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if
(Build.VERSION.SDK_INT
>=
9)
{
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package",
context.getPackageName(),
null));
}
else
if
(Build.VERSION.SDK_INT
<=
8)
{
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings",
"com.android.setting.InstalledAppDetails");
localIntent.putExtra("com.android.settings.ApplicationPkgName",
context.getPackageName());
}
}
context.startActivity(localIntent);
}
/**
*
获取通知权限,检测是否开启了系统通知
*
*
@param
context
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private
boolean
isNotificationEnabled(Context
context)
{
String
CHECK_OP_NO_THROW
=
"checkOpNoThrow";
String
OP_POST_NOTIFICATION
=
"OP_POST_NOTIFICATION";
AppOpsManager
mAppOps
=
(AppOpsManager)
context.getSystemService(Context.APP_OPS_SERVICE);
ApplicationInfo
appInfo
=
context.getApplicationInfo();
String
pkg
=
context.getApplicationContext().getPackageName();
int
uid
=
appInfo.uid;
Class
appOpsClass
=
null;
try
{
appOpsClass
=
Class.forName(AppOpsManager.class.getName());
Method
checkOpNoThrowMethod
=
appOpsClass.getMethod(CHECK_OP_NO_THROW,
Integer.TYPE,
Integer.TYPE,
String.class);
Field
opPostNotificationValue
=
appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
int
value
=
(Integer)
opPostNotificationValue.get(Integer.class);
return
((Integer)
checkOpNoThrowMethod.invoke(mAppOps,
value,
uid,
pkg)
==
AppOpsManager.MODE_ALLOWED);
}
catch
(Exception
e)
{
e.printStackTrace();
}
return
false;
}入相关的权限<!--
解锁屏幕需要的权限
-->
<uses-permission
android:name="android.permission.DISABLE_KEYGUARD"
/>
<!--
申请电源锁需要的权限
-->
<uses-permission
android:name="android.permission.WAKE_LOCK"
/>
<!--震动权限-->
<uses-permission
android:name="android.permission.VIBRATE"
/>五、心跳检测和重连由于很多不确定因素会导致websocket连接断开,例如网络断开,所以需要保证websocket的连接稳定性,这就需要加入心跳检测和重连。心跳检测其实就是个定时器,每个一段时间检测一次,如果连接断开则重连,Java-WebSocket框架在目前最新版本中有两个重连的方法,分别是reconnect()和reconnectBlocking(),这里同样使用后者。private
static
final
long
HEART_BEAT_RATE
=
10
*
1000;//每隔10秒进行一次对长连接的心跳检测
private
Handler
mHandler
=
new
Handler();
private
Runnable
heartBeatRunnable
=
new
Runnable()
{
@Override
public
void
run()
{
if
(client
!=
null)
{
if
(client.isClosed())
{
reconnectWs();
}
}
else
{
//如果client已为空,重新初始化websocket
initSocketClient();
}
//定时对长连接进行心跳检测
mHandler.postDelayed(this,
HEART_BEAT_RATE);
}
};
/**
*
开启重连
*/
private
void
reconnectWs()
{
mHandler.removeCallbacks(heartBeatRunnable);
new
Thread()
{
@Override
public
void
run()
{
try
{
//重连
client.reconnectBlocking();
}
catch
(InterruptedException
e)
{
e.printStackTrace();
}
}
}.start();
}然后在服务启动时开启心跳检测mHandler.postDelayed(heartBeatRunnable,
HEART_BEAT_RATE);//开启心跳检测我们打印一下日志,如图所示六、服务(Service)保活如果某些业务场景需要App保活,例如利用这个websocket来做推送,那就需要我们的App后台服务不被kill掉,当然如果和手机厂商没有合作,要保证服务一直不被杀死,这可能是所有Android开发者比较头疼的一个事,这里我们只能尽可能的来保证Service的存活。1、提高服务优先级(前台服务)前台服务的优先级比较高,它会在状态栏显示类似于通知的效果,可以尽量避免在内存不足时被系统回收,前台服务比较简单就不细说了。有时候我们希望可以使用前台服务但是又不希望在状态栏有显示,那就可以利用灰色保活的办法,如下private
final
static
int
GRAY_SERVICE_ID
=
1001;
//灰色保活手段
public
static
class
GrayInnerService
extends
Service
{
@Override
public
int
onStartCommand(Intent
intent,
int
flags,
int
startId)
{
startForeground(GRAY_SERVICE_ID,
new
Notification());
stopForeground(true);
stopSelf();
return
super.onStartCommand(intent,
flags,
startId);
}
@Override
public
IBinder
onBind(Intent
intent)
{
return
null;
}
}
//设置service为前台服务,提高优先级
if
(Build.VERS
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年浙江省温州市鹿城区初中科学学业水平检测仿真提优卷
- 房屋拆建申请书
- 即食海带制品质量通则 编制说明
- 出租店面合同5篇
- 题型01 作图专练-备战2024年中考物理真题题源解密(原卷版)
- 2024年广西壮族防城港市东兴市小升初语文预习模拟卷含答案
- 债权转让协议6篇
- 离婚协议车辆权属变更
- 离婚协议的子女福利和关怀
- 离婚协议制定中对子女隐私的保护
- 立体构成技法——半立构
- 柴油发电机组检测与调试验收报告
- 高压旋喷桩施工方案及施工工艺(完整版)
- TFDCA002-2021化妆品生产用水
- 砖砌排水沟工程施工工艺(完整版)
- 惠东县住建局办理业务事项目录
- 剧本脚本大纲
- 酒店客房管理信息系统设计
- (2021年整理)入团志愿书表格下载(可以直接打印)
- 2021年我国企业文化建设研究论文答辩稿
- NDA保密协议中英文模版
评论
0/150
提交评论