From 534ec286373dca8658bde709f3521b20f9c7425e Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:08:54 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=9B=B4=E6=96=B0Maven=E5=BA=93=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=B1=BB=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gpsrelaysentinel/build.gradle | 4 ++-- libgpsrelaysentinel/build.gradle | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) 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/libgpsrelaysentinel/build.gradle b/libgpsrelaysentinel/build.gradle index b11c04e..6b489a2 100644 --- a/libgpsrelaysentinel/build.gradle +++ b/libgpsrelaysentinel/build.gradle @@ -21,5 +21,10 @@ android { } dependencies { + + // WinBoLL库 nexus.winboll.cc 地址 + api 'cc.winboll.studio:libaes:15.15.9' + api 'cc.winboll.studio:libappbase:15.15.21' + api fileTree(dir: 'libs', include: ['*.jar']) } From 39b4761e499eda8d28582c59816b68ae0ddb0da7 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:30:50 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=9A=E5=90=91?= =?UTF-8?q?=E6=96=B9=E4=BD=8D=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gpsrelaysentinel/src/main/res/values/arrays.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 gpsrelaysentinel/src/main/res/values/arrays.xml 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 @@ + + + + 正北 + 正南 + 正东 + 正西 + 东北 + 西北 + 东南 + 西南 + + + From 3c3922508775ab93e31bd4b8a1d6de1ba8346632 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:32:58 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=81=B0=E8=89=B2?= =?UTF-8?q?=E8=BE=B9=E6=A1=86=E8=B5=84=E6=BA=90=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=BE=85=E5=8A=A9=E6=B7=B1=E8=89=B2=E8=A7=86=E5=9B=BE=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/border_gray.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 gpsrelaysentinel/src/main/res/drawable/border_gray.xml 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"> + + + + + + + + From 6ffcbbc4f4b65fb2da0159861810c37d13a116f0 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:34:33 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E6=96=B9=E4=BD=8D=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E9=A1=B9?= =?UTF-8?q?=E7=9A=84=E8=A7=86=E5=9B=BE=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/spinner_item_gray.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 gpsrelaysentinel/src/main/res/layout/spinner_item_gray.xml diff --git a/gpsrelaysentinel/src/main/res/layout/spinner_item_gray.xml b/gpsrelaysentinel/src/main/res/layout/spinner_item_gray.xml new file mode 100644 index 0000000..82e0eeb --- /dev/null +++ b/gpsrelaysentinel/src/main/res/layout/spinner_item_gray.xml @@ -0,0 +1,9 @@ + + + From 9c16685c1f2a466580cc13ab3074597c4431902e Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:35:58 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=94=E7=94=A8GPS?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E7=A4=BA=E4=BE=8B=E6=9C=8D=E5=8A=A1=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GpsReceiverChildService1.java | 27 +++++++++++++++++++ .../GpsReceiverChildService2.java | 26 ++++++++++++++++++ .../GpsReceiverChildService3.java | 26 ++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService1.java create mode 100644 gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService2.java create mode 100644 gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/GpsReceiverChildService3.java 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; + } +} + From e24c9bdce36e60ef8d121c4765e59577373bfc78 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:37:07 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E6=94=B9=E8=BF=9BGPS=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=8F=91=E9=80=81=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/SubscribeLocationManager.java | 81 ++++-- .../service/GpsSubscribeReceiverService.java | 136 ++------- .../view/GpsSubscribeControlView.java | 266 +++++++++++------- .../res/layout/view_gps_subscribe_control.xml | 158 +++++------ 4 files changed, 301 insertions(+), 340 deletions(-) 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"> + + + + + + + + + + + + + + + + + From ceeacb50225616df8c00e8d39bb11ac8e57e0f83 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:38:56 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E4=B8=BB=E8=A6=81=E6=9C=8D=E5=8A=A1=E5=90=AF=E5=8A=A8=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studio/gpsrelaysentinel/MainService.java | 248 ++++++++++++------ 1 file changed, 166 insertions(+), 82 deletions(-) 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监听已停止"); } } + From 42d135068c69e90a726370a0a5e1efb9c62ba32f Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 7 May 2026 14:39:49 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E4=B8=BB=E7=AA=97=E5=8F=A3=E4=B8=8E=E8=B0=83=E8=AF=95=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studio/gpsrelaysentinel/MainActivity.java | 351 +++++++++++++++--- .../src/main/res/layout/activity_main.xml | 161 +++++++- 2 files changed, 438 insertions(+), 74 deletions(-) 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/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"/> +