【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯_第1页
【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯_第2页
【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯_第3页
【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯_第4页
【移动应用开发技术】怎么在Android中利用WebSocket实现即时通讯_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】怎么在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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论