功能

  1. 与 websocket 服务端建立长连接
  2. 与 websocket 服务端进行即时双向通讯
  3. websocket 心跳检测和重连(保证 websocket 连接稳定性)
  4. Service 和 Activity 之间通讯、UI更新
  5. 弹出消息通知(包括锁屏通知)
  6. 服务(Service)保活

步骤

app/build.gradle

1
implementation "org.java-websocket:Java-WebSocket:1.4.0"

AndroidManifest.xml

1
2
3
4
5
6
7
8
9
<uses-permission android:name="android.permission.INTERNET" />
<!-- 解锁屏幕需要的权限 -->
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<!-- 申请电源锁需要的权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--震动权限-->
<uses-permission android:name="android.permission.VIBRATE" />
<!--android 9.0之后使用前台服务,需要添加权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1
2
3
4
5
6
7
<service android:name=".websocket.JWebSocketClientService"
android:enabled="true"
android:exported="true" />
<service android:name=".websocket.JWebSocketClientService$GrayInnerService"
android:enabled="true"
android:exported="false"
android:process=":gray" />

WebSocketConstant.java

常量定义

1
2
3
4
5
6
7
8
9
package com.bjtcrj.gms9x.websocket;

public class WebSocketConstant {
//回复消息类型
public static final String CONNECTION_SUCCESS = "0"; // 连接成功
public static final String PONG = "1"; // 心跳回复
public static final String CALL = "2"; // 视频通话请求
public static final String INVALID = "3"; // 无法处理
}

JWebSocketClient.java

websocket 客户端

1
2
3
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
package com.bjtcrj.gms9x.websocket;

import android.util.Log;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

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()");
}
}

JWebSocketClientService.java

service 后台服务:启动服务,创建 websocket 连接

1
2
3
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package com.bjtcrj.gms9x.websocket;

import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;

import androidx.core.app.NotificationCompat;

import com.bjtcrj.gms9x.MainActivity;
import com.bjtcrj.gms9x.R;
import com.bjtcrj.gms9x.common.Constants;
import com.bjtcrj.gms9x.utils.StringEx;

import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

public class JWebSocketClientService extends Service {
public JWebSocketClient client;
private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();
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;
}
}

PowerManager.WakeLock wakeLock;//锁屏唤醒
//获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持运行
@SuppressLint("InvalidWakeLockTag")
private void acquireWakeLock()
{
if (null == wakeLock)
{
PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "PostLocationService");
if (null != wakeLock)
{
wakeLock.acquire();
}
}
}

//用于Activity和service通讯
public class JWebSocketClientBinder extends Binder {
public JWebSocketClientService getService() {
return JWebSocketClientService.this;
}
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//初始化websocket
initSocketClient();
mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测

//设置service为前台服务,提高优先级
if (Build.VERSION.SDK_INT < 18) {
//Android4.3以下 ,隐藏Notification上的图标
startForeground(GRAY_SERVICE_ID, new Notification());
} else if(Build.VERSION.SDK_INT>18 && Build.VERSION.SDK_INT<25){
//Android4.3 - Android7.0,隐藏Notification上的图标
Intent innerIntent = new Intent(this, GrayInnerService.class);
startService(innerIntent);
startForeground(GRAY_SERVICE_ID, new Notification());
}else{
//Android7.0以上app启动后通知栏会出现一条"正在运行"的通知
startForeground(GRAY_SERVICE_ID, new Notification());
}

acquireWakeLock();
return START_STICKY;
}


@Override
public void onDestroy() {
closeConnect();
super.onDestroy();
}

public JWebSocketClientService() {
}


/**
* 初始化websocket连接
*/
private void initSocketClient() {
URI uri = URI.create(Constants.WEBSOCKET_SERVER_URL + "?userid=" + Constants.SESSION_USER_ID);
client = new JWebSocketClient(uri) {
@Override
public void onMessage(String message) {
Log.e("JWebSocketClientService", "收到的消息:" + message);

if(StringEx.isNull(message)) {
return;
}

String[] split = message.split("#");
if(WebSocketConstant.CONNECTION_SUCCESS.equals(split[0])
|| WebSocketConstant.PONG.equals(split[0])
|| WebSocketConstant.INVALID.equals(split[0])) {
//连接成功、心跳回复、无法处理
return;
}

Intent intent = new Intent();
intent.setAction("com.bjtcrj.servicecallback.content");
intent.putExtra("message", message);
sendBroadcast(intent);

checkLockAndShowNotification(message);
}

@Override
public void onOpen(ServerHandshake handshakedata) {
super.onOpen(handshakedata);
Log.e("JWebSocketClientService", "websocket连接成功");
}
};
connect();
}

/**
* 连接websocket
*/
private void connect() {
new Thread() {
@Override
public void run() {
try {
//connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错
client.connectBlocking();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();

}

/**
* 发送消息
*
* @param msg
*/
public void sendMsg(String msg) {
if (null != client) {
Log.e("JWebSocketClientService", "发送的消息:" + msg);
client.send(msg);
}
}

/**
* 断开连接
*/
private void closeConnect() {
try {
if (null != client) {
client.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
client = null;
}
}


// -----------------------------------消息通知--------------------------------------------------------

/**
* 检查锁屏状态,如果锁屏先点亮屏幕
*
* @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.drawable.logo)
.setContentTitle("通知")
.setContentText(content)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setWhen(System.currentTimeMillis())
// 向通知添加声音、闪灯和振动效果
.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND)
.setContentIntent(pendingIntent)
.build();
notifyManager.notify(1, notification);//id要保证唯一
}


// -------------------------------------websocket心跳检测------------------------------------------------
private static final long HEART_BEAT_RATE = 10 * 1000;//每隔10秒进行一次对长连接的心跳检测
private Handler mHandler = new Handler();
private Runnable heartBeatRunnable = new Runnable() {
@Override
public void run() {
Log.e("JWebSocketClientService", "心跳包检测websocket连接状态");
if (client != null) {
if (client.isClosed()) {
reconnectWs();
}
} else {
//如果client已为空,重新初始化连接
client = null;
initSocketClient();
}
//每隔一定的时间,对长连接进行一次心跳检测
mHandler.postDelayed(this, HEART_BEAT_RATE);
}
};

/**
* 开启重连
*/
private void reconnectWs() {
mHandler.removeCallbacks(heartBeatRunnable);
new Thread() {
@Override
public void run() {
try {
Log.e("JWebSocketClientService", "开启重连");
client.reconnectBlocking();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}

MainActivity.java

1
2
3
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package com.bjtcrj.gms9x;

import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import com.yxc.websocketclientdemo.adapter.Adapter_ChatMessage;
import com.yxc.websocketclientdemo.im.JWebSocketClient;
import com.yxc.websocketclientdemo.im.JWebSocketClientService;
import com.yxc.websocketclientdemo.modle.ChatMessage;
import com.yxc.websocketclientdemo.util.Util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Context mContext;
private JWebSocketClient client;
private JWebSocketClientService.JWebSocketClientBinder binder;
private JWebSocketClientService jWebSClientService;
private EditText et_content;
private ListView listView;
private Button btn_send;
private List<ChatMessage> chatMessageList = new ArrayList<>();//消息列表
private Adapter_ChatMessage adapter_chatMessage;
private ChatMessageReceiver chatMessageReceiver;

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", "服务与活动成功断开");
}
};

//接收器-接收消息
private class ChatMessageReceiver extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
String message=intent.getStringExtra("message");
ChatMessage chatMessage=new ChatMessage();
chatMessage.setContent(message);
chatMessage.setIsMeSend(0);
chatMessage.setIsRead(1);
chatMessage.setTime(System.currentTimeMillis()+"");
chatMessageList.add(chatMessage);
initChatMsgListView();
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
mContext=MainActivity.this;
//启动服务
startJWebSClientService();
//绑定服务
bindService();
//注册广播
doRegisterReceiver();
//检测通知是否开启
checkNotification(mContext);
findViewById();
initView();
}

/**
* 绑定服务
*/
private void bindService() {
Intent bindIntent = new Intent(mContext, JWebSocketClientService.class);
bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
}
/**
* 启动服务(websocket客户端服务)
*/
private void startJWebSClientService() {
Intent intent = new Intent(mContext, JWebSocketClientService.class);
startService(intent);
}
/**
* 动态注册广播
*/
private void doRegisterReceiver() {
chatMessageReceiver = new ChatMessageReceiver();
IntentFilter filter = new IntentFilter("com.bjtcrj.servicecallback.content");
registerReceiver(chatMessageReceiver, filter);
}


private void findViewById() {
listView = findViewById(R.id.chatmsg_listView);
btn_send = findViewById(R.id.btn_send);
et_content = findViewById(R.id.et_content);
btn_send.setOnClickListener(this);
}
private void initView() {
//监听输入框的变化
et_content.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

}

@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (et_content.getText().toString().length() > 0) {
btn_send.setVisibility(View.VISIBLE);
} else {
btn_send.setVisibility(View.GONE);
}
}

@Override
public void afterTextChanged(Editable editable) {

}
});
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_send:
String content = et_content.getText().toString();
if (content.length() <= 0) {
Util.showToast(mContext, "消息不能为空哟");
return;
}

if (client != null && client.isOpen()) {
jWebSClientService.sendMsg(content);

//暂时将发送的消息加入消息列表,实际以发送成功为准(也就是服务器返回你发的消息时)
ChatMessage chatMessage=new ChatMessage();
chatMessage.setContent(content);
chatMessage.setIsMeSend(1);
chatMessage.setIsRead(1);
chatMessage.setTime(System.currentTimeMillis()+"");
chatMessageList.add(chatMessage);
initChatMsgListView();
et_content.setText("");
} else {
Util.showToast(mContext, "连接已断开,请稍等或重启App哟");
}
break;
default:
break;
}
}

private void initChatMsgListView(){
adapter_chatMessage = new Adapter_ChatMessage(mContext, chatMessageList);
listView.setAdapter(adapter_chatMessage);
listView.setSelection(chatMessageList.size());
}


/**
* 检测是否开启通知
*
* @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;
}

}

服务端

参考《Websocket》