diff --git a/app/build.gradle b/app/build.gradle index 5e691ba..9750ed5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ android { // 还可以添加 'x86', 'x86_64', 'mips', 'mips64' } + buildConfigField "String", "WEBSOCKET_URL", '"wss://led.aolelearn.com/wss/device"' } dataBinding { @@ -175,6 +176,8 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14' //磁盘缓存 implementation 'com.jakewharton:disklrucache:2.0.2' + //Java WebSocket + implementation "org.java-websocket:Java-WebSocket:1.5.3" //glide implementation 'com.github.bumptech.glide:glide:4.13.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f6be341..8b4a30e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -178,7 +178,21 @@ android:name=".alarm.AlarmService" android:enabled="true" android:exported="true" /> + + + + + + + + + + + + diff --git a/app/src/main/java/com/uiui/zyos/activity/main/MainActivity.java b/app/src/main/java/com/uiui/zyos/activity/main/MainActivity.java index be64930..0a9ed3f 100644 --- a/app/src/main/java/com/uiui/zyos/activity/main/MainActivity.java +++ b/app/src/main/java/com/uiui/zyos/activity/main/MainActivity.java @@ -40,6 +40,7 @@ import com.uiui.zyos.fragment.user.UserFragment; import com.uiui.zyos.jxw.JxwPackageConfig; import com.uiui.zyos.manager.AmapManager; import com.uiui.zyos.manager.RemoteManager; +import com.uiui.zyos.service.SocketService; import com.uiui.zyos.utils.ApkUtils; import com.uiui.zyos.utils.OpenApkUtils; import com.uiui.zyos.utils.Utils; @@ -193,6 +194,12 @@ public class MainActivity extends BaseMvvmActivity= Build.VERSION_CODES.O) { + startForegroundService(intent); + } else { + startService(intent); + } } @Override diff --git a/app/src/main/java/com/uiui/zyos/service/SocketService.java b/app/src/main/java/com/uiui/zyos/service/SocketService.java new file mode 100644 index 0000000..73435a9 --- /dev/null +++ b/app/src/main/java/com/uiui/zyos/service/SocketService.java @@ -0,0 +1,398 @@ +package com.uiui.zyos.service; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.blankj.utilcode.util.NetworkUtils; +import com.google.gson.JsonObject; +import com.uiui.zyos.BuildConfig; +import com.uiui.zyos.R; +import com.uiui.zyos.activity.main.MainActivity; +import com.uiui.zyos.manager.RemoteManager; +import com.uiui.zyos.utils.ActivationUtil; +import com.uiui.zyos.utils.ServiceAliveUtils; +import com.uiui.zyos.utils.Utils; +import com.uiui.zyos.websocket.JWebSocketClient; + +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.functions.Consumer; + +/** + * 【有道云笔记】学生项目-用户端websocket对接.md + * https://note.youdao.com/s/Pbv87efV + *

+ * 【有道云笔记】学生项目-设备端websocket对接.md + * https://note.youdao.com/s/Ea0tebj8 + *

+ * 【有道云笔记】学生项目-管理端websocket对接.md + * https://note.youdao.com/s/MazEq49Q + *

+ * 老人在线websocket重构了,按照这个重新对接,跟之前售后差不多是一样的。 + *

+ * 1.设备未激活 不链接websocket 形成不了关联,设备激活后,才链接websocket + * 2.已激活的设备,设备若已链接websocket,用户确认绑定后,断开websocket 等待5秒 再重新链接websocket 以刷新用户关联 + * 3.设备用户解除绑定后,断开websocket 等待5秒 再重新链接websocket 以刷新用户关联 + */ +public class SocketService extends Service implements NetworkUtils.OnNetworkStatusChangedListener { + private final static String TAG = "JWebSocketClientService"; + + public JWebSocketClient mJWebSocketClient; +// private SocketServiceBinder mBinder = new SocketServiceBinder(); + + @Override + public void onDisconnected() { + Log.i(TAG, "网络断开连接"); + } + + @Override + public void onConnected(NetworkUtils.NetworkType networkType) { + Log.i(TAG, "网络已连接"); + } + + //用于Activity和service通讯 + public class SocketServiceBinder extends Binder { + public SocketService getService() { + return SocketService.this; + } + } + +// private ServiceConnection mServiceConnection = new ServiceConnection() { +// @Override +// public void onServiceConnected(ComponentName componentName, IBinder iBinder) { +// Log.e(TAG, "onServiceConnected: componentName = " + componentName); +// boolean isServiceRunning = ServiceAliveUtils.isServiceAlive(SocketService.this, ManagerService.class.getName()); +// Log.e(TAG, "onServiceConnected: isServiceRunning = " + isServiceRunning); +// if (!isServiceRunning) { +// startService(new Intent(SocketService.this, ManagerService.class)); +// } +// } +// +// @Override +// public void onServiceDisconnected(ComponentName componentName) { +// Log.e(TAG, "onServiceDisconnected: "); +// // 断开链接 +// startService(new Intent(SocketService.this, ManagerService.class)); +// // 重新绑定 +// bindService(new Intent(SocketService.this, ManagerService.class), mServiceConnection, Context.BIND_IMPORTANT); +// } +// }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + Log.e(TAG, "onCreate: "); + NetworkUtils.registerNetworkStatusChangedListener(this); + registerScreenLockReceiver(); + + if (ActivationUtil.isActivation(SocketService.this)) { + //初始化websocket + initSocketClient(); + startLoop(); + } else { + Log.e(TAG, "onCreate: 未激活不连接"); + } +// boolean isServiceRunning = ServiceAliveUtils.isServiceAlive(this, MainService.class.getName()); +// Log.e(TAG, "onCreate: isServiceRunning = " + isServiceRunning); +// if (!isServiceRunning) { +// startService(new Intent(this, MainService.class)); +// } +// bindService(new Intent(this, ManagerService.class), mServiceConnection, Context.BIND_IMPORTANT); + + mNotificationManagerCompat = NotificationManagerCompat.from(this); + createNotificationChannel(); +// showNotification(); + sendSimpleNotification(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.e(TAG, "onStartCommand: "); + return START_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + NetworkUtils.unregisterNetworkStatusChangedListener(this); + dispose(); + closeConnect(); + if (mScreenLockReceiver != null) { + unregisterReceiver(mScreenLockReceiver); + } +// unbindService(mServiceConnection); + } + + private static final String CHANNEL_ID = "CHANNEL_ID"; + private static final String CHANNEL_NAME = "系统通知"; + private static final String CHANNEL_DESCRIPTION = "学习课堂通知"; + + private void createNotificationChannel() { + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = CHANNEL_NAME; + String description = CHANNEL_DESCRIPTION; + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } + + private NotificationManagerCompat mNotificationManagerCompat; + private int NotificationID = 1; + + private void sendSimpleNotification() { + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "CHANNEL_ID") + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle("学习课堂正在运行") +// .setContentText("测试内容") + .setAutoCancel(false) + .setShowWhen(false) + .setContentIntent(pendingIntent) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_MAX); + // notificationId is a unique int for each notification that you must define +// mNotificationManagerCompat.notify(NotificationID, builder.build()); + startForeground(NotificationID, builder.build()); + } + + private ScreenLockReceiver mScreenLockReceiver; + + private void registerScreenLockReceiver() { + if (null == mScreenLockReceiver) { + mScreenLockReceiver = new ScreenLockReceiver(); + } + IntentFilter filter = new IntentFilter(); + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_SHUTDOWN); + filter.addAction(Intent.ACTION_FACTORY_RESET); + filter.addAction(Intent.ACTION_MASTER_CLEAR); + registerReceiver(mScreenLockReceiver, filter); + } + + private class ScreenLockReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.e(TAG, "onReceive:" + action); + if (TextUtils.isEmpty(action)) { + Log.e(TAG, "onReceive: is NULL"); + return; + } + switch (action) { + case Intent.ACTION_BOOT_COMPLETED: + case Intent.ACTION_USER_PRESENT: + case Intent.ACTION_SCREEN_ON: + sendMsgScreen(); + break; + case Intent.ACTION_SCREEN_OFF: + case Intent.ACTION_SHUTDOWN: + case Intent.ACTION_FACTORY_RESET: + case Intent.ACTION_MASTER_CLEAR: + sendMsgScreen(); + break; + default: + break; + } + } + } + + private Disposable mDisposable; + + private void dispose() { + if (mDisposable != null && !mDisposable.isDisposed()) { + mDisposable.dispose(); + mDisposable = null; + } + } + + private void startLoop() { + if (mDisposable != null && !mDisposable.isDisposed()) { + mDisposable.dispose(); + mDisposable = null; + } + mDisposable = Observable.interval(30, TimeUnit.SECONDS) + .subscribe(new Consumer() { + @Override + public void accept(Long s) throws Exception { + Log.d(TAG, "startLoop accept: " + s); + Log.i(TAG, "心跳包检测websocket连接状态"); + if (!ActivationUtil.isActivation(SocketService.this)) { + dispose(); + } + //每隔一定的时间,对长连接进行一次心跳检测 + if (Utils.isScreenOn(SocketService.this)) { + if (mJWebSocketClient != null) { + if (mJWebSocketClient.isOpen()) { + Log.i(TAG, "websocket已连接"); + sendPingMsg(); + } else if (mJWebSocketClient.isClosed()) { + Log.i(TAG, "websocket重连中"); + reconnectWs(); + } + } else { + //如果client已为空,重新初始化连接 + initSocketClient(); + } + } else { + Log.i(TAG, "websocket息屏不重连"); + } + } + }); + } + + /** + * 初始化websocket连接 + */ + private void initSocketClient() { + URI uri = URI.create(BuildConfig.WEBSOCKET_URL + "?sn=" + RemoteManager.getInstance().getSerial()); + mJWebSocketClient = new JWebSocketClient(uri) { + @Override + public void onMessage(String message) { + Log.i(TAG, "onMessage: 收到服务器发来的消息:" + message); + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + super.onOpen(handshakedata); + Log.i(TAG, "onOpen: websocket连接成功"); + sendPingMsg(); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + super.onClose(code, reason, remote); + Log.e(TAG, "onClose: websocket连接关闭:" + reason); + mJWebSocketClient = null; + } + + @Override + public void onError(Exception ex) { + super.onError(ex); + Log.e(TAG, "onError: websocket连接错误:" + ex.getMessage()); + mJWebSocketClient = null; + } + }; + connect(); + } + + /** + * 连接websocket + */ + private void connect() { + try { + //connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错 + Log.i(TAG, "connect: websocket连接中"); + mJWebSocketClient.connectBlocking(); + } catch (Exception e) { + Log.i(TAG, "connect: " + e.getMessage()); + } + } + + /** + * 开启重连 + */ + private void reconnectWs() { + Log.e(TAG, "reconnectWs: "); + try { + Log.i(TAG, "reconnectWs: 开启重连"); + mJWebSocketClient.reconnectBlocking(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 断开连接 + */ + private void closeConnect() { + Log.e(TAG, "closeConnect: "); + try { + if (null != mJWebSocketClient) { + mJWebSocketClient.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + mJWebSocketClient = null; + } + } + + /** + * 发送消息 + */ + public void sendPingMsg() { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("sn", RemoteManager.getInstance().getSerial()); + jsonObject.addProperty("type", "ping"); + if (null != mJWebSocketClient) { + Log.i(TAG, "sendPingMsg: 发送的消息:" + jsonObject.toString()); + mJWebSocketClient.send(jsonObject.toString()); + } + } + + public void sendMsgScreen() { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("sn", RemoteManager.getInstance().getSerial()); + if (Utils.isScreenOn(SocketService.this)) { + jsonObject.addProperty("type", "device_open_screen"); + } else { + jsonObject.addProperty("type", "device_close_screen"); + //熄屏状态 + } + if (null != mJWebSocketClient) { + Log.i(TAG, "sendMsgScreen: 发送的消息" + jsonObject.toString()); + try { + mJWebSocketClient.send(jsonObject.toString()); + } catch (Exception e) { + Log.i(TAG, "sendMsgScreen: sendMsg Exception: " + e.getLocalizedMessage()); + } + } else { + Log.i(TAG, "sendMsgScreen: 未连接"); + initSocketClient(); + } + } + + +} diff --git a/app/src/main/java/com/uiui/zyos/utils/ServiceAliveUtils.java b/app/src/main/java/com/uiui/zyos/utils/ServiceAliveUtils.java new file mode 100644 index 0000000..b0430c9 --- /dev/null +++ b/app/src/main/java/com/uiui/zyos/utils/ServiceAliveUtils.java @@ -0,0 +1,37 @@ +package com.uiui.zyos.utils; + +import android.app.ActivityManager; +import android.content.Context; +import android.util.Log; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ServiceAliveUtils { + public static boolean isServiceAlive(Context context, String className) { + boolean isServiceRunning = false; + ActivityManager manager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (manager == null) { + return false; + } + List serviceInfos = manager.getRunningServices(Integer.MAX_VALUE); + List serviceClass = serviceInfos.stream().map(new Function() { + @Override + public String apply(ActivityManager.RunningServiceInfo runningServiceInfo) { + return runningServiceInfo.service.getClassName(); + } + }).collect(Collectors.toList()); +// for (ActivityManager.RunningServiceInfo service : serviceInfos) { +// if (className.equals(service.service.getClassName())) { +// isServiceRunning = true; +// } +// } + if (serviceClass.contains(className)) { + isServiceRunning = true; + } + Log.e("ServiceAliveUtils", className + " isServiceAlice: " + isServiceRunning); + return isServiceRunning; + } +} diff --git a/app/src/main/java/com/uiui/zyos/websocket/JWebSocketClient.java b/app/src/main/java/com/uiui/zyos/websocket/JWebSocketClient.java new file mode 100644 index 0000000..6139f23 --- /dev/null +++ b/app/src/main/java/com/uiui/zyos/websocket/JWebSocketClient.java @@ -0,0 +1,36 @@ +package com.uiui.zyos.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.i("JWebSocketClient", "onOpen()"); + } + + @Override + public void onMessage(String message) { + Log.i("JWebSocketClient", "onMessage()"); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + Log.i("JWebSocketClient", "onClose():" + reason); + } + + @Override + public void onError(Exception ex) { + Log.i("JWebSocketClient", "onError():" + ex.getMessage()); + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/wechat_service.png b/app/src/main/res/drawable-hdpi/wechat_service.png index 169ffa6..bd76804 100644 Binary files a/app/src/main/res/drawable-hdpi/wechat_service.png and b/app/src/main/res/drawable-hdpi/wechat_service.png differ diff --git a/app/src/main/res/layout/activity_wechat.xml b/app/src/main/res/layout/activity_wechat.xml index 1322f0d..9469b8f 100644 --- a/app/src/main/res/layout/activity_wechat.xml +++ b/app/src/main/res/layout/activity_wechat.xml @@ -13,7 +13,7 @@ android:layout_height="wrap_content">