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;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.webkit.WebView;
@ -10,6 +11,7 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.iflytop.profilometer.core.bluetooth.BleManager;
import com.iflytop.profilometer.server.HttpServer;
public class MainActivity extends AppCompatActivity {
@ -33,6 +35,15 @@ public class MainActivity extends AppCompatActivity {
webView.getSettings().setJavaScriptEnabled(true);
WebView.setWebContentsDebuggingEnabled(true);
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
@ -40,5 +51,6 @@ public class MainActivity extends AppCompatActivity {
super.onDestroy();
// Activity 销毁时停止服务器
HttpServer.stop();
BleManager.getInstance(this).stopScan();
}
}

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

@ -1,15 +1,67 @@
package com.iflytop.profilometer;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.iflytop.profilometer.dao.UserDao;
public class ProfilometerApplication extends Application {
private Activity currentActivity;
@Override
public void 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);
AppUser user = userDao.login(username, password);
if (user != null) {
WebSocketManager.broadcast(GsonUtil.toJson(user));
return Result.success(user);
} else {
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;
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.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 {
private static final String TAG = "BleApi";
private final 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") {
val jsonResponse = api.start()
call.respondText(jsonResponse, ContentType.Application.Json)
}
/**
*
*/
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);
AppUser user = userDao.login(username, password);
if (user != null) {
WebSocketManager.broadcast(GsonUtil.toJson(user));
WebSocketManager.send(GsonUtil.toJson(user));
return Result.success(user);
} else {
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.post
fun Routing.authRoutes(context: Context) {
fun Routing.syncRoutes(context: 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.post
fun Routing.authRoutes(context: Context) {
fun Routing.systemRoutes(context: 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.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
public class BLEManager {
public class BleManager {
private static final String TAG = "BLEManager";
// 单例实例
private static BLEManager instance;
private static BleManager instance;
private BluetoothManager bluetoothManager;
private BluetoothAdapter bluetoothAdapter;
@ -36,9 +39,7 @@ public class BLEManager {
private BluetoothGatt bluetoothGatt;
private Context context;
// 私有构造防止外部直接实例化
private BLEManager(Context context) {
// 使用 ApplicationContext 防止内存泄露
private BleManager(Context context) {
this.context = context.getApplicationContext();
bluetoothManager = (BluetoothManager) this.context.getSystemService(Context.BLUETOOTH_SERVICE);
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) {
instance = new BLEManager(context);
instance = new BleManager(context);
}
return instance;
}
@ -67,6 +68,7 @@ public class BLEManager {
* 检查并请求必要权限例如位置BLUETOOTH_SCANBLUETOOTH_CONNECT
* 此方法需要在 Activity 中调用且在 onRequestPermissionsResult 中处理回调
*/
@RequiresApi(api = Build.VERSION_CODES.S)
public void checkAndRequestPermissions(Activity activity) {
List<String> permissionsNeeded = new ArrayList<>();
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
@ -79,16 +81,14 @@ public class BLEManager {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT);
}
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) {
if (bluetoothAdapter == null) {
Log.e(TAG, "设备不支持蓝牙");
Log.e(TAG, "设备不支持蓝牙");
return;
}
if (bluetoothAdapter.isEnabled()) {
@ -96,6 +96,7 @@ public class BLEManager {
}
// 直接提示用户蓝牙未开启无需点击开启
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)
@JvmStatic
fun broadcast(message: String) {
fun send(message: String) {
GlobalScope.launch {
sessions.forEach { session ->
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.measure.measureRoutes
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 io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
@ -82,6 +84,8 @@ object HttpServer {
measureRoutes(context)
bleRoutes(context)
recordRoutes(context)
syncRoutes(context)
systemRoutes(context)
}
}
server?.start(wait = false)
@ -107,6 +111,7 @@ object HttpServer {
else -> ContentType.Application.OctetStream
}
}
private suspend fun WebSocketSession.awaitClose() {
try {
incoming.consumeEach {}

Loading…
Cancel
Save