diff --git a/gpsrelaysentinel/build.gradle b/gpsrelaysentinel/build.gradle
index 8b8ddd9..1106541 100644
--- a/gpsrelaysentinel/build.gradle
+++ b/gpsrelaysentinel/build.gradle
@@ -115,8 +115,8 @@ dependencies {
implementation 'com.termux:termux-shared:0.118.0'
// WinBoLL库 nexus.winboll.cc 地址
- api 'cc.winboll.studio:libaes:15.15.2'
- api 'cc.winboll.studio:libappbase:15.15.11'
+ api 'cc.winboll.studio:libaes:15.15.9'
+ api 'cc.winboll.studio:libappbase:15.15.21'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
diff --git a/gpsrelaysentinel/build.properties b/gpsrelaysentinel/build.properties
index 5095505..0f99e65 100644
--- a/gpsrelaysentinel/build.properties
+++ b/gpsrelaysentinel/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Thu May 07 10:59:47 CST 2026
+#Thu May 07 15:04:39 CST 2026
stageCount=27
libraryProject=
baseVersion=15.11
publishVersion=15.11.26
-buildCount=31
+buildCount=33
baseBetaVersion=15.11.27
diff --git a/gpsrelaysentinel/src/main/AndroidManifest.xml b/gpsrelaysentinel/src/main/AndroidManifest.xml
index 7e89b56..1d557fb 100644
--- a/gpsrelaysentinel/src/main/AndroidManifest.xml
+++ b/gpsrelaysentinel/src/main/AndroidManifest.xml
@@ -3,10 +3,17 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.gpsrelaysentinel">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+ android:exported="false"/>
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService1.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService1.java
new file mode 100644
index 0000000..d65cce0
--- /dev/null
+++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService1.java
@@ -0,0 +1,27 @@
+package cc.winboll.studio.gpsrelaysentinel;
+
+import android.content.Intent;
+
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
+import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
+import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
+
+public final class GpsReceiverChildService1 extends GpsSubscribeReceiverService {
+
+ public static final String TAG = "GpsReceiverChildService1";
+
+ @Override
+ public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
+ super.onReceiveGpsData(point, config);
+ //当前独立接收日志
+ LogUtils.d(TAG,"独立接收服务1 成功收到GPS消息");
+ LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_NOT_STICKY;
+ }
+}
+
diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService2.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService2.java
new file mode 100644
index 0000000..d058f92
--- /dev/null
+++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService2.java
@@ -0,0 +1,26 @@
+package cc.winboll.studio.gpsrelaysentinel;
+
+import android.content.Intent;
+
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
+import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
+import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
+
+public final class GpsReceiverChildService2 extends GpsSubscribeReceiverService {
+
+ public static final String TAG = "GpsReceiverChildService2";
+
+ @Override
+ public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
+ super.onReceiveGpsData(point, config);
+ LogUtils.d(TAG,"独立接收服务2 成功收到GPS消息");
+ LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_NOT_STICKY;
+ }
+}
+
diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService3.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService3.java
new file mode 100644
index 0000000..fb04c12
--- /dev/null
+++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService3.java
@@ -0,0 +1,26 @@
+package cc.winboll.studio.gpsrelaysentinel;
+
+import android.content.Intent;
+
+import cc.winboll.studio.libappbase.LogUtils;
+import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
+import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
+import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
+
+public final class GpsReceiverChildService3 extends GpsSubscribeReceiverService {
+
+ public static final String TAG = "GpsReceiverChildService3";
+
+ @Override
+ public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
+ super.onReceiveGpsData(point, config);
+ LogUtils.d(TAG,"独立接收服务3 成功收到GPS消息");
+ LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_NOT_STICKY;
+ }
+}
+
diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainActivity.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainActivity.java
index 9026924..9cf9284 100644
--- a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainActivity.java
+++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainActivity.java
@@ -5,18 +5,58 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.Spinner;
import android.widget.Switch;
+import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import cc.winboll.studio.gpsrelaysentinel.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.ToastUtils;
-public class MainActivity extends AppCompatActivity {
+/**
+ * WinBoLL Studio
+ * GPSRelaySentinel 主控制页面
+ * Java7 | API26~30
+ * 新增:模拟模式勾选控制 + 按钮互斥可用状态
+ */
+public final class MainActivity extends AppCompatActivity {
- LogView mLogView;
- Switch mSwitchService;
+ //原有控件
+ private Toolbar mToolbar;
+ private LogView mLogView;
+ private Switch mSwitchService;
+
+ //新增
+ private CheckBox mCheckBoxSimMode;
+ private Button btnSendLastGps;
+ private Spinner spinDirection;
+ private EditText etSimDistance;
+ private TextView tvTargetPreview;
+ private Button btnSimSend;
+
+ //全局模式标识 供给MainService判断
+ public static boolean IS_GPS_SIM_MODE = false;
+
+ //最后真实GPS坐标
+ public static double lastLat = 30.5928;
+ public static double lastLng = 114.3055;
+
+ //全局模拟坐标 供给MainService使用
+ public static double simLat = 30.5928;
+ public static double simLng = 114.3055;
+
+ //方位对应角度(正北0° 顺时针)
+ private double currentAngle = 0.0D;
+
+ //权限请求常量
private static final int REQUEST_LOCATION_PERMISSION = 1;
@Override
@@ -24,78 +64,273 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
-
- mLogView = findViewById(R.id.logview);
- mSwitchService = findViewById(R.id.switch_service);
-
- // 根据当前权限状态初始化switch
- mSwitchService.setChecked(hasLocationPermission());
-
- mSwitchService.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (isChecked) {
- // 打开时检查权限
- if (hasLocationPermission()) {
- startService();
- } else {
- // 没有权限,申请权限
- requestLocationPermission();
- // 暂时不打开switch,等权限申请结果
- mSwitchService.setChecked(false);
- }
- } else {
- stopService();
- }
- }
- });
+ initView();
+ initToolbar();
+ initSwitchEvent();
+ initSimPanelEvent();
+ initSimModeCheck();
ToastUtils.show("onCreate");
}
+ /**
+ * 全部控件绑定
+ */
+ private void initView() {
+ //原有
+ mToolbar = findViewById(R.id.toolbar);
+ mLogView = findViewById(R.id.logview);
+ mSwitchService = findViewById(R.id.switch_service);
+
+ //新增
+ mCheckBoxSimMode = findViewById(R.id.checkbox_sim_mode);
+ btnSendLastGps = findViewById(R.id.btn_send_last_gps);
+ spinDirection = findViewById(R.id.spin_direction);
+ etSimDistance = findViewById(R.id.et_sim_distance);
+ tvTargetPreview = findViewById(R.id.tv_target_point_preview);
+ btnSimSend = findViewById(R.id.btn_sim_send_gps);
+
+ //方位下拉 全局灰色文字
+ ArrayAdapter dirAdapter = ArrayAdapter.createFromResource(
+ this,
+ R.array.direction_list,
+ R.layout.spinner_item_gray
+ );
+ dirAdapter.setDropDownViewResource(R.layout.spinner_item_gray);
+ spinDirection.setAdapter(dirAdapter);
+
+ //初始化开关状态
+ mSwitchService.setChecked(hasLocationPermission());
+ refreshButtonEnableStatus();
+ refreshTargetPreview();
+ }
+
+ //模拟勾选框监听
+ private void initSimModeCheck() {
+ mCheckBoxSimMode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ IS_GPS_SIM_MODE = isChecked;
+ refreshButtonEnableStatus();
+ if (isChecked) {
+ ToastUtils.show("已进入GPS模拟模式");
+ } else {
+ ToastUtils.show("退出模拟模式,使用真实GPS");
+ }
+ }
+ });
+ }
+
+ //刷新按钮互斥可用状态
+ private void refreshButtonEnableStatus() {
+ if (IS_GPS_SIM_MODE) {
+ //模拟模式:真实按钮禁用、模拟按钮可用
+ btnSendLastGps.setEnabled(false);
+ btnSimSend.setEnabled(true);
+ } else {
+ //正常模式:真实可用、模拟禁用
+ btnSendLastGps.setEnabled(true);
+ btnSimSend.setEnabled(false);
+ }
+ }
+
+ /**
+ * 初始化标题栏
+ */
+ private void initToolbar() {
+ setSupportActionBar(mToolbar);
+ }
+
+ /**
+ * GPS服务开关监听
+ */
+ private void initSwitchEvent() {
+ mSwitchService.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ if (hasLocationPermission()) {
+ startGpsService();
+ } else {
+ requestLocationPermission();
+ mSwitchService.setChecked(false);
+ }
+ } else {
+ stopGpsService();
+ }
+ }
+ });
+ }
+
+ /**
+ * 模拟发送面板 全部事件初始化
+ */
+ private void initSimPanelEvent() {
+ //1.原按钮:发送最后一条真实GPS
+ btnSendLastGps.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ sendLastRealGpsBroadcast();
+ }
+ });
+
+ //2.方位下拉选择 -> 切换角度并刷新预览
+ spinDirection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ currentAngle = getDirectionAngle(position);
+ refreshTargetPreview();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {}
+ });
+
+ //3.距离输入变化自动预览
+ etSimDistance.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ refreshTargetPreview();
+ }
+ }
+ });
+
+ //4.模拟发送按钮:计算偏移并赋值全局模拟坐标
+ btnSimSend.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveSimGpsData();
+ ToastUtils.show("已设置当前模拟GPS坐标");
+ }
+ });
+ }
+
+ /**
+ * 保存模拟坐标到全局静态变量 供给MainService使用
+ */
+ private void saveSimGpsData() {
+ String disText = etSimDistance.getText().toString().trim();
+ double distance = 10D;
+ try {
+ distance = Double.parseDouble(disText);
+ } catch (Exception e) {
+ ToastUtils.show("请输入合法距离");
+ return;
+ }
+ double[] target = calculateOffsetLatLng(lastLat, lastLng, distance, currentAngle);
+ simLat = target[0];
+ simLng = target[1];
+ refreshTargetPreview();
+ }
+
+ /**
+ * 根据下拉position获取对应方位角度
+ */
+ private double getDirectionAngle(int pos) {
+ switch (pos) {
+ case 0: return 0.0D; //正北
+ case 1: return 180.0D; //正南
+ case 2: return 90.0D; //正东
+ case 3: return 270.0D; //正西
+ case 4: return 45.0D; //东北
+ case 5: return 315.0D; //西北
+ case 6: return 135.0D; //东南
+ case 7: return 225.0D; //西南
+ default:return 0.0D;
+ }
+ }
+
+ /**
+ * 根据基准坐标+距离+角度 计算偏移经纬度
+ */
+ private double[] calculateOffsetLatLng(double lat, double lng, double distanceMeter, double angle) {
+ double radAngle = Math.toRadians(angle);
+ double radLat = Math.toRadians(lat);
+
+ double meterPerLat = 111320D;
+ double meterPerLng = Math.cos(radLat) * 111320D;
+
+ double offsetLat = (distanceMeter * Math.cos(radAngle)) / meterPerLat;
+ double offsetLng = (distanceMeter * Math.sin(radAngle)) / meterPerLng;
+
+ return new double[]{lat + offsetLat , lng + offsetLng};
+ }
+
+ /**
+ * 刷新目标坐标预览
+ */
+ private void refreshTargetPreview() {
+ String disText = etSimDistance.getText().toString().trim();
+ double distance = 10D;
+ try {
+ distance = Double.parseDouble(disText);
+ } catch (Exception e) {}
+
+ double[] target = calculateOffsetLatLng(lastLat, lastLng, distance, currentAngle);
+ String info = "目标模拟坐标:"
+ + String.format("%.6f", target[0])
+ + " , "
+ + String.format("%.6f", target[1]);
+ tvTargetPreview.setText(info);
+ }
+
+ /**
+ * 发送【最后真实GPS】广播
+ */
+ private void sendLastRealGpsBroadcast() {
+ Intent broadcast = new Intent("GPS_DATA_BROADCAST");
+ broadcast.putExtra("isSim", false);
+ broadcast.putExtra("lat", lastLat);
+ broadcast.putExtra("lng", lastLng);
+ sendBroadcast(broadcast);
+ LogUtils.d("GPS_SEND", "发送真实GPS -> lat:" + lastLat + " lng:" + lastLng);
+ }
+
+ //—————— 原有权限 & 服务启停 完全原样保留 ——————
+
private boolean hasLocationPermission() {
- boolean hasBasic = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
- || checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
- if (hasBasic && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
+ boolean basicPermission = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ || checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+
+ if (basicPermission && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
return checkSelfPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
- return hasBasic;
+ return basicPermission;
}
private void requestLocationPermission() {
+ String[] permissionArray;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
- String[] permissions = new String[] {
- android.Manifest.permission.ACCESS_FINE_LOCATION,
- android.Manifest.permission.ACCESS_COARSE_LOCATION,
- android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ permissionArray = new String[]{
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
};
- requestPermissions(permissions, REQUEST_LOCATION_PERMISSION);
} else {
- String[] permissions = new String[] {
- android.Manifest.permission.ACCESS_FINE_LOCATION,
- android.Manifest.permission.ACCESS_COARSE_LOCATION
+ permissionArray = new String[]{
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION
};
- requestPermissions(permissions, REQUEST_LOCATION_PERMISSION);
}
+ requestPermissions(permissionArray, REQUEST_LOCATION_PERMISSION);
}
- private void startService() {
- Intent intent = new Intent(MainActivity.this, MainService.class);
- startForegroundService(intent);
+ private void startGpsService() {
+ Intent serviceIntent = new Intent(MainActivity.this, MainService.class);
+ startForegroundService(serviceIntent);
ToastUtils.show("GPS Service started");
LogUtils.d(MainService.TAG, "GPS Service started from MainActivity");
}
- private void stopService() {
- // 先设置SP标记为不启用
- MainActivity.this.getSharedPreferences(MainService.PREF_NAME, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(MainService.KEY_SERVICE_ENABLED, false)
- .apply();
- Intent intent = new Intent(MainActivity.this, MainService.class);
- stopService(intent);
+ private void stopGpsService() {
+ getSharedPreferences(MainService.PREF_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(MainService.KEY_SERVICE_ENABLED, false)
+ .apply();
+
+ Intent serviceIntent = new Intent(MainActivity.this, MainService.class);
+ stopService(serviceIntent);
ToastUtils.show("GPS Service stopped");
LogUtils.d(MainService.TAG, "GPS Service stopped from MainActivity");
}
@@ -105,9 +340,8 @@ public class MainActivity extends AppCompatActivity {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_LOCATION_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // 权限申请成功,启动服务
mSwitchService.setChecked(true);
- startService();
+ startGpsService();
} else {
ToastUtils.show("需要位置权限才能使用GPS服务");
mSwitchService.setChecked(false);
@@ -120,8 +354,5 @@ public class MainActivity extends AppCompatActivity {
super.onResume();
mLogView.start();
}
-
-// public void onLibraryActivity(View view) {
-// startActivity(new Intent(this, LibraryActivity.class));
-// }
}
+
diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java
index f1aded1..cc4f25a 100644
--- a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java
+++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java
@@ -12,154 +12,236 @@ import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+
import androidx.core.app.NotificationCompat;
+
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager;
import cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
+
import java.util.Map;
-public class MainService extends Service {
+/**
+ * WinBoLL Studio
+ * GPS定位核心前台服务
+ * 负责GPS持续监听、订阅者步长判断、基准坐标刷新、前台常驻通知
+ * Java7 | API26~30
+ * 新增:实时同步最新GPS到MainActivity静态坐标
+ */
+public final class MainService extends Service {
+ //日志标签
public static final String TAG = "MainService";
- private LocationManager mLocationManager;
- private LocationListener mLocationListener;
- private boolean mIsRunning = false;
- private NotificationManager mNotificationManager;
- private NotificationCompat.Builder mNotificationBuilder;
+
+ //前台通知常量
private static final String CHANNEL_ID = "gps_relay_channel";
private static final int NOTIFICATION_ID = 1;
+
+ //SP配置常量
static final String PREF_NAME = "gps_relay_service_prefs";
static final String KEY_SERVICE_ENABLED = "service_enabled";
- private int mGpsCount = 0;
+
+ //系统定位 & 通知控件
+ private LocationManager mLocationManager;
+ private LocationListener mLocationListener;
+ private NotificationManager mNotificationManager;
+ private NotificationCompat.Builder mNotificationBuilder;
+
+ //运行状态 & 计数
+ private boolean mIsRunning = false;
+ private int mGpsLocationCount = 0;
+
+ //订阅管理器
+ private GpsSubscribeManager mSubscribeManager;
+ private SubscribeLocationManager mLocationRuleManager;
+
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "Service onCreate");
- mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- createNotificationChannel();
- if (isServiceEnabled()) {
- LogUtils.d(TAG, "Service was enabled, starting GPS updates");
- run();
+
+ initManager();
+ initNotificationConfig();
+
+ //上次开启状态则自动重启GPS监听
+ if (checkServiceEnableStatus()) {
+ LogUtils.d(TAG, "历史服务已启用,自动启动GPS监听");
+ startGpsLocationListen();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "Service onStartCommand");
- setServiceEnabled(true);
- run();
+ saveServiceEnableStatus(true);
+ startGpsLocationListen();
return START_STICKY;
}
- private boolean isServiceEnabled() {
- return getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).getBoolean(KEY_SERVICE_ENABLED, false);
+ /**
+ * 初始化订阅规则管理器
+ */
+ private void initManager() {
+ mSubscribeManager = GpsSubscribeManager.getInstance();
+ mLocationRuleManager = SubscribeLocationManager.getInstance();
}
- private void setServiceEnabled(boolean enabled) {
- getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit().putBoolean(KEY_SERVICE_ENABLED, enabled).apply();
- LogUtils.d(TAG, "Service enabled set to: " + enabled);
+ /**
+ * 初始化通知渠道与管理类
+ */
+ private void initNotificationConfig() {
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ createSystemNotificationChannel();
}
- private void run() {
+ /**
+ * 读取服务启用状态
+ */
+ private boolean checkServiceEnableStatus() {
+ return getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .getBoolean(KEY_SERVICE_ENABLED, false);
+ }
+
+ /**
+ * 保存服务启用状态
+ */
+ private void saveServiceEnableStatus(boolean enabled) {
+ getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(KEY_SERVICE_ENABLED, enabled)
+ .apply();
+ LogUtils.d(TAG, "服务启用状态已设置:" + enabled);
+ }
+
+ /**
+ * 启动GPS定位监听核心逻辑
+ */
+ private void startGpsLocationListen() {
if (mIsRunning) {
- LogUtils.d(TAG, "GPS updates already running");
+ LogUtils.d(TAG, "GPS监听已正在运行,无需重复启动");
return;
}
+
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ initLocationListener();
+
+ try {
+ if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ //定位间隔:1000毫秒 / 最小位移1米
+ mLocationManager.requestLocationUpdates(
+ LocationManager.GPS_PROVIDER,
+ 1000,
+ 1,
+ mLocationListener
+ );
+ mIsRunning = true;
+ startServiceForegroundNotification();
+ LogUtils.d(TAG, "GPS定位监听已成功注册");
+ }
+ } catch (SecurityException e) {
+ LogUtils.e(TAG, "定位权限缺失,监听启动失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 初始化定位监听回调
+ */
+ private void initLocationListener() {
mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
- mGpsCount++;
- String gpsInfo = "Lat: " + location.getLatitude() + ", Lng: " + location.getLongitude();
- LogUtils.d(TAG, "Location changed: " + gpsInfo);
- updateNotification(gpsInfo);
-
- //管理器初始化
- GpsSubscribeManager subscribeManager = GpsSubscribeManager.getInstance();
- SubscribeLocationManager locationManager = SubscribeLocationManager.getInstance();
-
- //遍历所有订阅者做距离判断+定点更新
- Map subscribeMap = subscribeManager.getSubscribeMap();
- for (Map.Entry entry : subscribeMap.entrySet()) {
- String sid = entry.getKey();
- GpsSubscribeMsg subscribeMsg = entry.getValue();
-
- double nowLat = location.getLatitude();
- double nowLng = location.getLongitude();
-
- //判断是否满足推送
- boolean canPush = locationManager.isNeedPush(sid, nowLat, nowLng);
- if (canPush) {
- //执行发送GPS广播
- //sendGpsBroadcast(...);
-
- //推送成功立刻刷新订阅者基准坐标
- locationManager.updateSubscriberPoint(sid, nowLat, nowLng);
- }
- }
-
+ handleLocationUpdate(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
- LogUtils.d(TAG, "Status changed: " + provider + ", status: " + status);
+ LogUtils.d(TAG, "GPS状态变更 -> 提供者:" + provider + " 状态:" + status);
}
@Override
public void onProviderEnabled(String provider) {
- LogUtils.d(TAG, "Provider enabled: " + provider);
+ LogUtils.d(TAG, "GPS提供者已启用:" + provider);
}
@Override
public void onProviderDisabled(String provider) {
- LogUtils.d(TAG, "Provider disabled: " + provider);
+ LogUtils.d(TAG, "GPS提供者已禁用:" + provider);
}
};
+ }
- try {
- if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
- mLocationManager.requestLocationUpdates(
- LocationManager.GPS_PROVIDER,
- 1000,
- 1,
- mLocationListener
- );
- LogUtils.d(TAG, "GPS location updates requested");
- mIsRunning = true;
- startForegroundNotification();
+ /**
+ * 处理每次定位刷新|核心:步长判断 + 基准坐标更新
+ * 新增:同步最新坐标到MainActivity静态变量
+ */
+ private void handleLocationUpdate(Location location) {
+ mGpsLocationCount ++;
+ String locationInfo = "纬度:" + location.getLatitude() + " , 经度:" + location.getLongitude();
+ LogUtils.d(TAG, "定位刷新 -> " + locationInfo);
+
+ //========== 新增关键代码:实时同步最新真实GPS坐标 ==========
+ MainActivity.lastLat = location.getLatitude();
+ MainActivity.lastLng = location.getLongitude();
+ //==========================================================
+
+ //更新前台通知文案
+ updateForegroundNotification(locationInfo);
+
+ //遍历全部订阅者进行推送规则判断
+ Map subscribeAllMap = mSubscribeManager.getSubscribeMap();
+ for (Map.Entry entry : subscribeAllMap.entrySet()) {
+ final String subscribeSid = entry.getKey();
+ final GpsSubscribeMsg subscribeConfig = entry.getValue();
+
+ double currentLat = location.getLatitude();
+ double currentLng = location.getLongitude();
+
+ //判断是否满足推送条件(全订阅/步长阈值)
+ boolean allowPush = mLocationRuleManager.isNeedPush(subscribeSid, currentLat, currentLng);
+ if (allowPush) {
+ //推送成功后刷新该订阅者基准定点坐标
+ mLocationRuleManager.updateSubscriberPoint(subscribeSid, currentLat, currentLng);
}
- } catch (SecurityException e) {
- LogUtils.e(TAG, "Permission denied: " + e.getMessage());
}
}
- private void createNotificationChannel() {
+ /**
+ * 创建系统通知渠道
+ */
+ private void createSystemNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
- "GPS Relay Service",
- NotificationManager.IMPORTANCE_LOW
+ NotificationChannel notificationChannel = new NotificationChannel(
+ CHANNEL_ID,
+ "GPS Relay Service",
+ NotificationManager.IMPORTANCE_LOW
);
- channel.setDescription("GPS Relay Sentinel service channel");
- mNotificationManager.createNotificationChannel(channel);
+ notificationChannel.setDescription("GPSRelaySentinel 后台常驻服务通知");
+ mNotificationManager.createNotificationChannel(notificationChannel);
}
}
- private void startForegroundNotification() {
+ /**
+ * 开启前台常驻通知
+ */
+ private void startServiceForegroundNotification() {
mNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle("GPS Relay Service")
- .setContentText("Waiting for GPS data...")
- .setSmallIcon(android.R.drawable.ic_menu_mylocation)
- .setOngoing(true);
+ .setContentTitle("GPS 中继服务")
+ .setContentText("等待GPS定位数据...")
+ .setSmallIcon(android.R.drawable.ic_menu_mylocation)
+ .setOngoing(true);
+
Notification notification = mNotificationBuilder.build();
startForeground(NOTIFICATION_ID, notification);
}
- private void updateNotification(String gpsInfo) {
+ /**
+ * 动态更新通知内容
+ */
+ private void updateForegroundNotification(String locationText) {
if (mNotificationBuilder != null) {
- mNotificationBuilder.setContentText(gpsInfo + " | Count: " + mGpsCount);
+ mNotificationBuilder.setContentText(locationText + " | 定位次数:" + mGpsLocationCount);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
}
}
@@ -172,14 +254,16 @@ public class MainService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
+ //注销定位监听
if (mLocationManager != null && mLocationListener != null) {
try {
mLocationManager.removeUpdates(mLocationListener);
} catch (SecurityException e) {
- LogUtils.e(TAG, "Permission denied when removing updates: " + e.getMessage());
+ LogUtils.e(TAG, "移除定位监听权限异常:" + e.getMessage());
}
}
mIsRunning = false;
- LogUtils.d(TAG, "Service onDestroy");
+ LogUtils.d(TAG, "MainService 已销毁,GPS监听已停止");
}
}
+
diff --git a/gpsrelaysentinel/src/main/res/drawable/border_gray.xml b/gpsrelaysentinel/src/main/res/drawable/border_gray.xml
new file mode 100644
index 0000000..a8c93bd
--- /dev/null
+++ b/gpsrelaysentinel/src/main/res/drawable/border_gray.xml
@@ -0,0 +1,13 @@
+
+
+ android:shape="rectangle">
+
+
+
+
+
+
+
+
diff --git a/gpsrelaysentinel/src/main/res/layout/activity_main.xml b/gpsrelaysentinel/src/main/res/layout/activity_main.xml
index 6c15ec1..4e9f9bc 100644
--- a/gpsrelaysentinel/src/main/res/layout/activity_main.xml
+++ b/gpsrelaysentinel/src/main/res/layout/activity_main.xml
@@ -4,7 +4,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:background="#1c1c1c">
-
+
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+
+
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginTop="8dp"
+ android:spacing="12dp">
-
+ android:text="模拟模式"
+ android:textColor="#999999"
+ android:padding="4dp"
+ android:background="@drawable/border_gray"
+ android:textSize="11sp"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_height="200dp"
+ android:orientation="vertical"
+ android:id="@+id/container_log_show"
+ android:background="@drawable/border_gray">
+
+
diff --git a/gpsrelaysentinel/src/main/res/values/arrays.xml b/gpsrelaysentinel/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..6d40162
--- /dev/null
+++ b/gpsrelaysentinel/src/main/res/values/arrays.xml
@@ -0,0 +1,14 @@
+
+
+
+ - 正北
+ - 正南
+ - 正东
+ - 正西
+ - 东北
+ - 西北
+ - 东南
+ - 西南
+
+
+
diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java
index 9c3548b..962086f 100644
--- a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java
+++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java
@@ -1,10 +1,5 @@
package cc.winboll.studio.libgpsrelaysentinel.manager;
-/**
- * @Author 豆包&ZhanGSKen
- * @Date 2026/05/07 10:26
- */
-
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
@@ -15,12 +10,18 @@ import java.util.Map;
public final class SubscribeLocationManager {
private static SubscribeLocationManager instance;
+
+ //订阅配置
private final Map subscribeConfigMap;
+ //基准定点坐标
private final Map subscriberPointMap;
+ //真实推送计数(精准统计)
+ private final Map subscriberPushCountMap;
private SubscribeLocationManager(){
subscribeConfigMap = new HashMap();
subscriberPointMap = new HashMap();
+ subscriberPushCountMap = new HashMap();
}
public static SubscribeLocationManager getInstance(){
@@ -30,48 +31,70 @@ public final class SubscribeLocationManager {
return instance;
}
- public void putSubscribeConfig(final String sid,final GpsSubscribeMsg msg){
+ //========= 订阅配置 =========
+ public void putSubscribeConfig(String sid,GpsSubscribeMsg msg){
subscribeConfigMap.put(sid,msg);
}
- public void initSubscriberPoint(final String sid,double lat,double lng){
- subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis()));
- }
-
- public void updateSubscriberPoint(final String sid,double lat,double lng){
- subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis()));
- }
-
- public LocationPoint getLastPoint(final String sid){
- return subscriberPointMap.get(sid);
- }
-
- public GpsSubscribeMsg getSubscribeConfig(final String sid){
+ public GpsSubscribeMsg getSubscribeConfig(String sid){
return subscribeConfigMap.get(sid);
}
- public boolean isNeedPush(final String sid,double nowLat,double nowLng){
+ //========= 基准定点坐标 =========
+ public void initSubscriberPoint(String sid,double lat,double lng){
+ subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis()));
+ }
+
+ public void updateSubscriberPoint(String sid,double lat,double lng){
+ subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis()));
+ }
+
+ public LocationPoint getLastPoint(String sid){
+ return subscriberPointMap.get(sid);
+ }
+
+ //========= 精准推送计数 =========
+ public void addPushCount(String sid){
+ int current = subscriberPushCountMap.get(sid) == null ? 0 : subscriberPushCountMap.get(sid);
+ subscriberPushCountMap.put(sid,current + 1);
+ }
+
+ public int getPushCount(String sid){
+ return subscriberPushCountMap.get(sid) == null ? 0 : subscriberPushCountMap.get(sid);
+ }
+
+ public void clearPushCount(String sid){
+ subscriberPushCountMap.put(sid,0);
+ }
+
+ //========= 步长规则判断 =========
+ public boolean isNeedPush(String sid,double nowLat,double nowLng){
GpsSubscribeMsg config = getSubscribeConfig(sid);
if(config == null){
return false;
}
+ //全量订阅直接放行
if(config.getSubscribeMode() == GpsSubscribeConst.SUB_TYPE_ALL){
return true;
}
+ //无初始定点 → 先建立第一个基准点
LocationPoint lastPoint = getLastPoint(sid);
if(lastPoint == null){
return true;
}
+ //计算实际移动距离
double distance = calculateDistance(
lastPoint.getLatitude(),lastPoint.getLongitude(),
nowLat,nowLng
);
+
return distance >= config.getStepDistanceM();
}
+ //两点经纬度距离计算(米)
private double calculateDistance(double lat1,double lng1,double lat2,double lng2){
double radLat1 = Math.toRadians(lat1);
double radLat2 = Math.toRadians(lat2);
@@ -81,23 +104,25 @@ public final class SubscribeLocationManager {
double latDiff = radLat1 - radLat2;
double lngDiff = radLng1 - radLng2;
- double result = 2 * Math.asin(Math.sqrt(
- Math.pow(Math.sin(latDiff / 2),2)
- + Math.cos(radLat1) * Math.cos(radLat2)
- * Math.pow(Math.sin(lngDiff / 2),2)
- ));
- result = result * GpsSubscribeConst.EARTH_RADIUS;
- return result;
+ double value = 2 * Math.asin(Math.sqrt(
+ Math.pow(Math.sin(latDiff / 2),2)
+ + Math.cos(radLat1) * Math.cos(radLat2)
+ * Math.pow(Math.sin(lngDiff / 2),2)
+ ));
+ return value * 6378137;
}
- public void removeSubscribe(final String sid){
+ //========= 移除 & 清空 =========
+ public void removeSubscribe(String sid){
subscribeConfigMap.remove(sid);
subscriberPointMap.remove(sid);
+ subscriberPushCountMap.remove(sid);
}
public void clearAll(){
subscribeConfigMap.clear();
subscriberPointMap.clear();
+ subscriberPushCountMap.clear();
}
}
diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java
index ce2c659..0dcfec9 100644
--- a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java
+++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java
@@ -4,136 +4,36 @@ import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
-/**
- * @Author 豆包&ZhanGSKen
- * @Date 2026/05/07 10:46
- */
-
- import android.app.Service;
- import android.content.Context;
- import android.content.Intent;
- import android.os.Binder;
- import android.os.IBinder;
- import android.os.RemoteException;
-
- import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
+import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
-import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult;
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
/**
- * 对外消息接收服务:外部App可bind或start
- * 收到GPS消息后,通过本地广播回调给外部App
+ * 全局消息接收父类服务
+ * 所有应用内接收服务全部继承此类
*/
-public final class GpsSubscribeReceiverService extends Service {
+public abstract class GpsSubscribeReceiverService extends Service {
- // 外部回调监听
- public interface GpsMessageListener {
- void onGpsLocation(LocationPoint point, GpsSubscribeMsg config);
- void onSubscribeResult(GpsSubscribeResult result);
+ public static final String TAG_PARENT = "GpsSubscribeReceiverService";
+
+ //当前绑定的视图订阅SID
+ protected String bindViewSid;
+
+ public void bindControlSid(String sid){
+ this.bindViewSid = sid;
}
- private final List listeners = new CopyOnWriteArrayList();
- private final IBinder localBinder = new LocalBinder();
-
- @Override
- public void onCreate() {
- super.onCreate();
+ /**
+ * 统一接收GPS推送入口
+ */
+ public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config){
+ //父类统一日志溯源
+ LogUtils.d(TAG_PARENT,"【消息溯源】接收视图SID:" + bindViewSid);
}
- // 外部App绑定服务
@Override
public IBinder onBind(Intent intent) {
- return localBinder;
- }
-
- // 外部App startService 入口:接收订阅请求
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent != null && intent.getParcelableExtra("req") != null) {
- GpsSubscribeMsg msg = intent.getParcelableExtra("req");
- handleSubscribeRequest(msg);
- }
- return START_STICKY;
- }
-
- // 处理订阅请求:发送给管理器,并回执
- private void handleSubscribeRequest(final GpsSubscribeMsg msg) {
- // 加入订阅管理
- cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager
- .getInstance().addSubscribe(msg);
- cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager
- .getInstance().putSubscribeConfig(msg.getSubscribeUniqueId(), msg);
-
- // 回执成功
- GpsSubscribeResult result = new GpsSubscribeResult(
- msg.getSubscribeUniqueId(),
- GpsSubscribeConst.RESULT_SUCCESS,
- "subscribe ok",
- GpsSubscribeConst.GPS_STATE_LOCATED,
- 1000,
- System.currentTimeMillis()
- );
- sendSubscribeResultBroadcast(result);
- notifySubscribeResult(result);
- }
-
- // 供内部(GPS服务)调用:推送定位消息
- public void pushLocation(final LocationPoint point, final GpsSubscribeMsg config) {
- sendLocationBroadcast(point, config);
- notifyGpsLocation(point, config);
- }
-
- // ---------- 广播回调(跨进程/外部App接收) ----------
- private void sendLocationBroadcast(final LocationPoint point, final GpsSubscribeMsg config) {
- Intent intent = new Intent(GpsSubscribeConst.ACTION_GPS_LOCATION);
- intent.putExtra("point", point);
- intent.putExtra("config", config);
- sendBroadcast(intent);
- }
-
- private void sendSubscribeResultBroadcast(final GpsSubscribeResult result) {
- Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK);
- intent.putExtra("data", result);
- sendBroadcast(intent);
- }
-
- // ---------- 本地Binder(同进程直接回调) ----------
- public class LocalBinder extends Binder {
- public GpsSubscribeReceiverService getService() {
- return GpsSubscribeReceiverService.this;
- }
- }
-
- public void addListener(final GpsMessageListener l) {
- if (l != null && !listeners.contains(l)) {
- listeners.add(l);
- }
- }
-
- public void removeListener(final GpsMessageListener l) {
- listeners.remove(l);
- }
-
- private void notifyGpsLocation(final LocationPoint point, final GpsSubscribeMsg config) {
- for (GpsMessageListener l : listeners) {
- l.onGpsLocation(point, config);
- }
- }
-
- private void notifySubscribeResult(final GpsSubscribeResult result) {
- for (GpsMessageListener l : listeners) {
- l.onSubscribeResult(result);
- }
- }
-
- @Override
- public void onDestroy() {
- listeners.clear();
- super.onDestroy();
+ return null;
}
}
diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java
index 08b0e95..732e614 100644
--- a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java
+++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java
@@ -1,174 +1,226 @@
package cc.winboll.studio.libgpsrelaysentinel.view;
-/**
- * @Author 豆包&ZhanGSKen
- * @Date 2026/05/07 10:27
- */
-
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
import android.util.AttributeSet;
-import android.view.View;
-import android.widget.CompoundButton;
+import android.view.LayoutInflater;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Switch;
import android.widget.TextView;
+
import cc.winboll.studio.libgpsrelaysentinel.R;
+import cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager;
+import cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager;
+import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
+import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
+import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
import java.util.UUID;
-import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
-import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
-import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult;
-import cc.winboll.studio.libgpsrelaysentinel.receiver.GpsSubscribeObserverReceiver;
-import cc.winboll.studio.libgpsrelaysentinel.util.TimeCountUtil;
-
public final class GpsSubscribeControlView extends LinearLayout {
- private RadioGroup rgSubMode;
- private RadioButton rbAll;
- private RadioButton rbStep;
- private LinearLayout layoutStepSetting;
+ //常量抽取
+ private static final long REFRESH_INTERVAL = 600;
+
+ private RadioGroup rgSubscribeMode;
+ private RadioButton rbModeAll;
+ private RadioButton rbModeStep;
private EditText etStepMeter;
+ private Switch switchSubscribe;
+ private TextView tvSubscribeSid;
+ private TextView tvSubscribeRecord;
- private Switch mSwitchSubscribe;
- private TextView mTvCountTip;
-
- private TimeCountUtil mTimeCountUtil;
- private GpsSubscribeObserverReceiver mResultReceiver;
private String currentSubscribeSid;
- private boolean isSubscribeSuccess;
+ //一对一专属绑定的接收服务
+ private Class> mBindReceiverServiceClazz;
+
+ //final管理器 构造器初始化
+ private final GpsSubscribeManager mSubscribeManager;
+ private final SubscribeLocationManager mLocationManager;
+
+ private final Handler mRefreshHandler = new Handler(Looper.getMainLooper());
+
public GpsSubscribeControlView(Context context) {
super(context);
- initView();
+ mSubscribeManager = GpsSubscribeManager.getInstance();
+ mLocationManager = SubscribeLocationManager.getInstance();
+ initView(context);
}
public GpsSubscribeControlView(Context context, AttributeSet attrs) {
super(context, attrs);
- initView();
+ mSubscribeManager = GpsSubscribeManager.getInstance();
+ mLocationManager = SubscribeLocationManager.getInstance();
+ initView(context);
}
- private void initView(){
- setOrientation(VERTICAL);
- inflate(getContext(),R.layout.view_gps_subscribe_control,this);
+ private void initView(Context context) {
+ LayoutInflater.from(context).inflate(R.layout.view_gps_subscribe_control, this, true);
- rgSubMode = findViewById(R.id.rg_sub_mode);
- rbAll = findViewById(R.id.rb_all);
- rbStep = findViewById(R.id.rb_step);
- layoutStepSetting = findViewById(R.id.layout_step_setting);
+ rgSubscribeMode = findViewById(R.id.rg_subscribe_mode);
+ rbModeAll = findViewById(R.id.rb_mode_all);
+ rbModeStep = findViewById(R.id.rb_mode_step);
etStepMeter = findViewById(R.id.et_step_meter);
+ switchSubscribe = findViewById(R.id.switch_subscribe);
+ tvSubscribeSid = findViewById(R.id.tv_subscribe_sid);
+ tvSubscribeRecord = findViewById(R.id.tv_subscribe_record);
- mSwitchSubscribe = findViewById(R.id.switch_subscribe);
- mTvCountTip = findViewById(R.id.tv_count_tip);
-
+ initDefaultConfig();
initModeSwitch();
- initCountUtil();
- initReceiver();
- initSwitchEvent();
+ initSubscribeSwitch();
+ startAutoRefreshRecord();
}
- private void initModeSwitch(){
- rgSubMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ private void initDefaultConfig() {
+ currentSubscribeSid = UUID.randomUUID().toString().substring(0, 16);
+ tvSubscribeSid.setText("订阅SID:" + currentSubscribeSid);
+ rbModeAll.setChecked(true);
+ etStepMeter.setText("10");
+ }
+
+ /**
+ * 外部绑定当前视图专属的接收服务Class
+ */
+ public void bindReceiverService(Class> serviceClazz){
+ this.mBindReceiverServiceClazz = serviceClazz;
+ }
+
+ private void initModeSwitch() {
+ rgSubscribeMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
- layoutStepSetting.setVisibility(checkedId == R.id.rb_step ? View.VISIBLE : View.GONE);
+ etStepMeter.setVisibility(checkedId == R.id.rb_mode_step ? VISIBLE : GONE);
}
});
}
- private void initCountUtil(){
- mTimeCountUtil = new TimeCountUtil(new TimeCountUtil.OnCountListener() {
+ private void initSubscribeSwitch() {
+ switchSubscribe.setOnCheckedChangeListener(new android.widget.CompoundButton.OnCheckedChangeListener() {
@Override
- public void onTimeOut() {
- if(!isSubscribeSuccess){
- mSwitchSubscribe.setChecked(false);
- mTvCountTip.setText("订阅超时,已自动关闭");
- }
- }
- });
- }
-
- private void initReceiver(){
- mResultReceiver = new GpsSubscribeObserverReceiver();
- mResultReceiver.setOnSubscribeResultListener(new GpsSubscribeObserverReceiver.OnSubscribeResultListener() {
- @Override
- public void onResultBack(GpsSubscribeResult result) {
- if(currentSubscribeSid.equals(result.getSubscribeUniqueId())){
- isSubscribeSuccess = true;
- mTimeCountUtil.cancel();
- mTvCountTip.setText("订阅已生效,通讯正常");
- }
- }
- });
- IntentFilter filter = new IntentFilter();
- filter.addAction(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK);
- getContext().registerReceiver(mResultReceiver,filter);
- }
-
- private void initSwitchEvent(){
- mSwitchSubscribe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if(isChecked){
+ public void onCheckedChanged(android.widget.CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
startSubscribe();
- }else{
+ } else {
stopSubscribe();
}
}
});
}
- private void startSubscribe(){
- isSubscribeSuccess = false;
- currentSubscribeSid = UUID.randomUUID().toString();
- mTvCountTip.setText("等待订阅返回中...");
-
+ private void startSubscribe() {
int subMode = GpsSubscribeConst.SUB_TYPE_ALL;
- float stepMeter = 10.0f;
+ float stepVal = 10f;
- if(rbStep.isChecked()){
+ if (rbModeStep.isChecked()) {
subMode = GpsSubscribeConst.SUB_TYPE_STEP_DISTANCE;
- try{
- stepMeter = Float.parseFloat(etStepMeter.getText().toString().trim());
- }catch (Exception e){
- stepMeter = 10.0f;
- }
+ try {
+ stepVal = Float.parseFloat(etStepMeter.getText().toString().trim());
+ } catch (Exception ignored) {}
}
- GpsSubscribeMsg msg = new GpsSubscribeMsg(
+ GpsSubscribeMsg subscribeMsg = new GpsSubscribeMsg(
getContext().getPackageName(),
subMode,
- stepMeter,
+ stepVal,
GpsSubscribeConst.SUBSCRIBE_TYPE_LOCATION,
1000,
- 1.0f,
+ 1f,
true,
currentSubscribeSid
);
- Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_REQUEST);
- intent.putExtra("req",msg);
- getContext().sendBroadcast(intent);
+ mSubscribeManager.addSubscribe(subscribeMsg);
+ mLocationManager.putSubscribeConfig(currentSubscribeSid, subscribeMsg);
+ mLocationManager.clearPushCount(currentSubscribeSid);
- mTimeCountUtil.start(GpsSubscribeConst.SUBSCRIBE_TIME_OUT);
- }
-
- private void stopSubscribe(){
- mTimeCountUtil.cancel();
- isSubscribeSuccess = false;
- mTvCountTip.setText("订阅已关闭");
- }
-
- public void release(){
- mTimeCountUtil.cancel();
- if(mResultReceiver != null){
- getContext().unregisterReceiver(mResultReceiver);
+ //开启订阅自动启动专属接收服务
+ if(mBindReceiverServiceClazz != null){
+ Intent startServiceIntent = new Intent(getContext(), mBindReceiverServiceClazz);
+ getContext().startService(startServiceIntent);
}
}
+
+ private void stopSubscribe() {
+ mSubscribeManager.removeSubscribe(currentSubscribeSid);
+ mLocationManager.removeSubscribe(currentSubscribeSid);
+ tvSubscribeRecord.setText("状态:未订阅");
+
+ //关闭订阅 同步停止专属接收服务
+ if(mBindReceiverServiceClazz != null){
+ Intent stopServiceIntent = new Intent(getContext(), mBindReceiverServiceClazz);
+ getContext().stopService(stopServiceIntent);
+ }
+ }
+
+ private void startAutoRefreshRecord() {
+ mRefreshHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ refreshRecordInfo();
+ mRefreshHandler.postDelayed(this, REFRESH_INTERVAL);
+ }
+ }, REFRESH_INTERVAL);
+ }
+
+ private void refreshRecordInfo() {
+ if (!switchSubscribe.isChecked()) {
+ tvSubscribeRecord.setText("状态:空闲未订阅");
+ return;
+ }
+
+ GpsSubscribeMsg config = mLocationManager.getSubscribeConfig(currentSubscribeSid);
+ LocationPoint lastPoint = mLocationManager.getLastPoint(currentSubscribeSid);
+
+ if (config == null) {
+ tvSubscribeRecord.setText("状态:已订阅|等待管理器加载");
+ return;
+ }
+
+ String modeText = config.getSubscribeMode() == GpsSubscribeConst.SUB_TYPE_ALL
+ ? "全量订阅" : "步长订阅";
+
+ int realPushCount = mLocationManager.getPushCount(currentSubscribeSid);
+
+ StringBuilder record = new StringBuilder();
+ record.append("【订阅实时数据表】\n");
+ record.append("订阅模式:").append(modeText).append("\n");
+ record.append("步长阈值:").append(config.getStepDistanceM()).append(" 米\n");
+
+ if(lastPoint != null){
+ record.append("基准定点:").append(lastPoint.getLatitude()).append(" , ").append(lastPoint.getLongitude()).append("\n");
+ }else{
+ record.append("基准定点:等待首次定位建立\n");
+ }
+
+ record.append("真实推送次数:").append(realPushCount).append(" 次");
+
+ tvSubscribeRecord.setText(record);
+ }
+
+ public String getCurrentSid() {
+ return currentSubscribeSid;
+ }
+
+ public boolean isSubscribeOpen() {
+ return switchSubscribe.isChecked();
+ }
+
+ /**
+ * 视图销毁:强制停止订阅 + 停止服务 + 清空刷新任务
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ if(switchSubscribe.isChecked()){
+ switchSubscribe.setChecked(false);
+ }
+ mRefreshHandler.removeCallbacksAndMessages(null);
+ super.onDetachedFromWindow();
+ }
}
diff --git a/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml b/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml
index e35e2ad..86f3437 100644
--- a/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml
+++ b/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml
@@ -3,97 +3,81 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:padding="16dp">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:padding="14dp"
+ android:layout_marginVertical="6dp"
+ android:background="#282828"
+ android:clipToPadding="false">
+
+
+
+
+ android:orientation="horizontal"
+ android:layout_marginTop="8dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+