Browse Source

实现获取蓝牙列表接口,还需完善

master
白凤吉 4 months ago
parent
commit
0d72bcd223
  1. 12
      app/src/main/java/com/iflytop/profilometer/MainActivity.java
  2. 56
      app/src/main/java/com/iflytop/profilometer/ProfilometerApplication.java
  3. 1
      app/src/main/java/com/iflytop/profilometer/api/auth/AuthApi.java
  4. 39
      app/src/main/java/com/iflytop/profilometer/api/ble/BleApi.java
  5. 4
      app/src/main/java/com/iflytop/profilometer/api/ble/BleRoutes.kt
  6. 60
      app/src/main/java/com/iflytop/profilometer/api/ble/BleWebsocketManager.java
  7. 2
      app/src/main/java/com/iflytop/profilometer/api/record/RecordApi.java
  8. 2
      app/src/main/java/com/iflytop/profilometer/api/sync/SyncRoutes.kt
  9. 2
      app/src/main/java/com/iflytop/profilometer/api/system/SystemRoutes.kt
  10. 23
      app/src/main/java/com/iflytop/profilometer/core/bluetooth/BleManager.java
  11. 2
      app/src/main/java/com/iflytop/profilometer/core/websocket/WebSocketManager.kt
  12. 5
      app/src/main/java/com/iflytop/profilometer/server/HttpServer.kt

12
app/src/main/java/com/iflytop/profilometer/MainActivity.java

@ -1,6 +1,7 @@
package com.iflytop.profilometer; package com.iflytop.profilometer;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.webkit.WebView; import android.webkit.WebView;
@ -10,6 +11,7 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import com.iflytop.profilometer.core.bluetooth.BleManager;
import com.iflytop.profilometer.server.HttpServer; import com.iflytop.profilometer.server.HttpServer;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
@ -33,6 +35,15 @@ public class MainActivity extends AppCompatActivity {
webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setJavaScriptEnabled(true);
WebView.setWebContentsDebuggingEnabled(true); WebView.setWebContentsDebuggingEnabled(true);
webView.loadUrl("http://127.0.0.1:8080/"); webView.loadUrl("http://127.0.0.1:8080/");
BleManager bleManager = BleManager.getInstance(this);
// 针对 Android 12 及以上版本请求必要的运行时权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
bleManager.checkAndRequestPermissions(this);
}
// 检查蓝牙是否开启如果未开启则提示用户开启
bleManager.promptAndEnableBluetooth(this);
} }
@Override @Override
@ -40,5 +51,6 @@ public class MainActivity extends AppCompatActivity {
super.onDestroy(); super.onDestroy();
// Activity 销毁时停止服务器 // Activity 销毁时停止服务器
HttpServer.stop(); HttpServer.stop();
BleManager.getInstance(this).stopScan();
} }
} }

56
app/src/main/java/com/iflytop/profilometer/ProfilometerApplication.java

@ -1,15 +1,67 @@
package com.iflytop.profilometer; package com.iflytop.profilometer;
import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.iflytop.profilometer.dao.UserDao; import com.iflytop.profilometer.dao.UserDao;
public class ProfilometerApplication extends Application { public class ProfilometerApplication extends Application {
private Activity currentActivity;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
//
// UserDao userDao = new UserDao(this);
// userDao.insertAdminUserIfNotExists();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
currentActivity = activity;
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
currentActivity = activity;
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
currentActivity = activity;
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
// 如果停止的 Activity 就是当前的 Activity则清空引用
if (currentActivity == activity) {
currentActivity = null;
}
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
// no-op
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
if (currentActivity == activity) {
currentActivity = null;
}
}
});
}
UserDao userDao = new UserDao(this);
userDao.insertAdminUserIfNotExists();
public Activity getCurrentActivity() {
return currentActivity;
} }
} }

1
app/src/main/java/com/iflytop/profilometer/api/auth/AuthApi.java

@ -25,7 +25,6 @@ public class AuthApi {
UserDao userDao = new UserDao(context); UserDao userDao = new UserDao(context);
AppUser user = userDao.login(username, password); AppUser user = userDao.login(username, password);
if (user != null) { if (user != null) {
WebSocketManager.broadcast(GsonUtil.toJson(user));
return Result.success(user); return Result.success(user);
} else { } else {
return Result.failed(); return Result.failed();

39
app/src/main/java/com/iflytop/profilometer/api/ble/BleApi.java

@ -1,21 +1,50 @@
package com.iflytop.profilometer.api.ble; package com.iflytop.profilometer.api.ble;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.iflytop.profilometer.ProfilometerApplication;
import com.iflytop.profilometer.common.result.Result; import com.iflytop.profilometer.common.result.Result;
import com.iflytop.profilometer.common.utils.GsonUtil;
import com.iflytop.profilometer.core.websocket.WebSocketManager;
import com.iflytop.profilometer.dao.UserDao;
import com.iflytop.profilometer.model.entiy.AppUser;
import com.iflytop.profilometer.core.bluetooth.BleManager;
/** /**
* 蓝牙接口 * 蓝牙接口
*/ */
public class BleApi { public class BleApi {
private static final String TAG = "BleApi";
private final Context context; private final Context context;
public BleApi(Context context) { public BleApi(Context context) {
this.context = context;
this.context = context.getApplicationContext();
}
/**
* 开始获取蓝牙设备列表
*/
public String start() {
try {
BleManager.getInstance(context).startScan();
BleWebsocketManager.getInstance(context).startWsPush();
return Result.success();
} catch (Exception e) {
Log.e(TAG, "开始获取蓝牙列表失败", e);
return Result.failed("获取蓝牙列表失败");
}
}
/**
* 结束获取蓝牙设备列表
*/
public String stop() {
try {
BleManager.getInstance(context).stopScan();
BleWebsocketManager.getInstance(context).stopWsPush();
return Result.success();
} catch (Exception e) {
Log.e(TAG, "结束获取蓝牙设备列表失败", e);
return Result.failed();
}
} }

4
app/src/main/java/com/iflytop/profilometer/api/ble/BleRoutes.kt

@ -15,12 +15,16 @@ fun Routing.bleRoutes(context: Context) {
* *
*/ */
post("/api/ble/list/start") { post("/api/ble/list/start") {
val jsonResponse = api.start()
call.respondText(jsonResponse, ContentType.Application.Json)
} }
/** /**
* *
*/ */
post("/api/ble/list/stop") { post("/api/ble/list/stop") {
val jsonResponse = api.stop()
call.respondText(jsonResponse, ContentType.Application.Json)
} }
/** /**

60
app/src/main/java/com/iflytop/profilometer/api/ble/BleWebsocketManager.java

@ -0,0 +1,60 @@
package com.iflytop.profilometer.api.ble;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import com.iflytop.profilometer.common.utils.GsonUtil;
import com.iflytop.profilometer.core.bluetooth.BleManager;
import com.iflytop.profilometer.core.websocket.WebSocketManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class BleWebsocketManager {
private static BleWebsocketManager instance;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> scheduledTask;
private final Context context;
private BleWebsocketManager(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized BleWebsocketManager getInstance(Context context) {
if (instance == null) {
instance = new BleWebsocketManager(context);
}
return instance;
}
/**
* 开始定时任务
*/
@SuppressLint("MissingPermission")
public void startWsPush() {
stopWsPush();
scheduledTask = scheduler.scheduleWithFixedDelay(() -> {
List<BluetoothDevice> scannedDevices = BleManager.getInstance(context).getScannedDevices();
List<String> bleList = new ArrayList<>();
for(BluetoothDevice bluetoothDevice : scannedDevices){
bleList.add(bluetoothDevice.getName() + "_" + bluetoothDevice.getAddress());
}
WebSocketManager.send(GsonUtil.toJson(bleList));
}, 2, 2, TimeUnit.SECONDS);
}
/**
* 结束定时任务
*/
public void stopWsPush() {
if (scheduledTask != null && !scheduledTask.isCancelled()) {
scheduledTask.cancel(false);
}
}
}

2
app/src/main/java/com/iflytop/profilometer/api/record/RecordApi.java

@ -25,7 +25,7 @@ public class RecordApi {
UserDao userDao = new UserDao(context); UserDao userDao = new UserDao(context);
AppUser user = userDao.login(username, password); AppUser user = userDao.login(username, password);
if (user != null) { if (user != null) {
WebSocketManager.broadcast(GsonUtil.toJson(user));
WebSocketManager.send(GsonUtil.toJson(user));
return Result.success(user); return Result.success(user);
} else { } else {
return Result.failed(); return Result.failed();

2
app/src/main/java/com/iflytop/profilometer/api/sync/SyncRoutes.kt

@ -4,7 +4,7 @@ import android.content.Context
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import io.ktor.server.routing.post import io.ktor.server.routing.post
fun Routing.authRoutes(context: Context) {
fun Routing.syncRoutes(context: Context) {
val api = SyncApi(context) val api = SyncApi(context)
/** /**

2
app/src/main/java/com/iflytop/profilometer/api/system/SystemRoutes.kt

@ -4,7 +4,7 @@ import android.content.Context
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
import io.ktor.server.routing.post import io.ktor.server.routing.post
fun Routing.authRoutes(context: Context) {
fun Routing.systemRoutes(context: Context) {
val api = SystemApi(context) val api = SystemApi(context)
/** /**

23
app/src/main/java/com/iflytop/profilometer/core/bluetooth/BLEManager.java → app/src/main/java/com/iflytop/profilometer/core/bluetooth/BleManager.java

@ -14,19 +14,22 @@ import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanResult;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BLEManager {
public class BleManager {
private static final String TAG = "BLEManager"; private static final String TAG = "BLEManager";
// 单例实例 // 单例实例
private static BLEManager instance;
private static BleManager instance;
private BluetoothManager bluetoothManager; private BluetoothManager bluetoothManager;
private BluetoothAdapter bluetoothAdapter; private BluetoothAdapter bluetoothAdapter;
@ -36,9 +39,7 @@ public class BLEManager {
private BluetoothGatt bluetoothGatt; private BluetoothGatt bluetoothGatt;
private Context context; private Context context;
// 私有构造防止外部直接实例化
private BLEManager(Context context) {
// 使用 ApplicationContext 防止内存泄露
private BleManager(Context context) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
bluetoothManager = (BluetoothManager) this.context.getSystemService(Context.BLUETOOTH_SERVICE); bluetoothManager = (BluetoothManager) this.context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) { if (bluetoothManager != null) {
@ -49,9 +50,9 @@ public class BLEManager {
/** /**
* 获取单例实例 * 获取单例实例
*/ */
public static BLEManager getInstance(Context context) {
public static synchronized BleManager getInstance(Context context) {
if (instance == null) { if (instance == null) {
instance = new BLEManager(context);
instance = new BleManager(context);
} }
return instance; return instance;
} }
@ -67,6 +68,7 @@ public class BLEManager {
* 检查并请求必要权限例如位置BLUETOOTH_SCANBLUETOOTH_CONNECT * 检查并请求必要权限例如位置BLUETOOTH_SCANBLUETOOTH_CONNECT
* 此方法需要在 Activity 中调用且在 onRequestPermissionsResult 中处理回调 * 此方法需要在 Activity 中调用且在 onRequestPermissionsResult 中处理回调
*/ */
@RequiresApi(api = Build.VERSION_CODES.S)
public void checkAndRequestPermissions(Activity activity) { public void checkAndRequestPermissions(Activity activity) {
List<String> permissionsNeeded = new ArrayList<>(); List<String> permissionsNeeded = new ArrayList<>();
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
@ -79,16 +81,14 @@ public class BLEManager {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT); permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT);
} }
if (!permissionsNeeded.isEmpty()) { if (!permissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(activity,
permissionsNeeded.toArray(new String[0]),
100);
ActivityCompat.requestPermissions(activity, permissionsNeeded.toArray(new String[0]), 100);
} }
} }
public void promptAndEnableBluetooth(Activity activity) { public void promptAndEnableBluetooth(Activity activity) {
if (bluetoothAdapter == null) { if (bluetoothAdapter == null) {
Log.e(TAG, "设备不支持蓝牙");
Log.e(TAG, "设备不支持蓝牙");
return; return;
} }
if (bluetoothAdapter.isEnabled()) { if (bluetoothAdapter.isEnabled()) {
@ -96,6 +96,7 @@ public class BLEManager {
} }
// 直接提示用户蓝牙未开启无需点击开启 // 直接提示用户蓝牙未开启无需点击开启
Toast.makeText(activity, "蓝牙未开启,请先开启蓝牙", Toast.LENGTH_LONG).show(); Toast.makeText(activity, "蓝牙未开启,请先开启蓝牙", Toast.LENGTH_LONG).show();
startScan();
} }
/** /**

2
app/src/main/java/com/iflytop/profilometer/core/websocket/WebSocketManager.kt

@ -24,7 +24,7 @@ object WebSocketManager {
*/ */
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@JvmStatic @JvmStatic
fun broadcast(message: String) {
fun send(message: String) {
GlobalScope.launch { GlobalScope.launch {
sessions.forEach { session -> sessions.forEach { session ->
try { try {

5
app/src/main/java/com/iflytop/profilometer/server/HttpServer.kt

@ -5,6 +5,8 @@ import com.iflytop.profilometer.api.auth.authRoutes
import com.iflytop.profilometer.api.ble.bleRoutes import com.iflytop.profilometer.api.ble.bleRoutes
import com.iflytop.profilometer.api.measure.measureRoutes import com.iflytop.profilometer.api.measure.measureRoutes
import com.iflytop.profilometer.api.record.recordRoutes import com.iflytop.profilometer.api.record.recordRoutes
import com.iflytop.profilometer.api.sync.syncRoutes
import com.iflytop.profilometer.api.system.systemRoutes
import com.iflytop.profilometer.core.websocket.WebSocketManager import com.iflytop.profilometer.core.websocket.WebSocketManager
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@ -82,6 +84,8 @@ object HttpServer {
measureRoutes(context) measureRoutes(context)
bleRoutes(context) bleRoutes(context)
recordRoutes(context) recordRoutes(context)
syncRoutes(context)
systemRoutes(context)
} }
} }
server?.start(wait = false) server?.start(wait = false)
@ -107,6 +111,7 @@ object HttpServer {
else -> ContentType.Application.OctetStream else -> ContentType.Application.OctetStream
} }
} }
private suspend fun WebSocketSession.awaitClose() { private suspend fun WebSocketSession.awaitClose() {
try { try {
incoming.consumeEach {} incoming.consumeEach {}

Loading…
Cancel
Save