This commit is contained in:
1
libgpsrelaysentinel/.gitignore
vendored
Normal file
1
libgpsrelaysentinel/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
35
libgpsrelaysentinel/build.gradle
Normal file
35
libgpsrelaysentinel/build.gradle
Normal file
@@ -0,0 +1,35 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply from: '../.winboll/winboll_lib_build.gradle'
|
||||
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||
|
||||
android {
|
||||
// 适配MIUI12
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 30
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
}
|
||||
|
||||
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'])
|
||||
}
|
||||
8
libgpsrelaysentinel/build.properties
Normal file
8
libgpsrelaysentinel/build.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Fri May 01 17:09:11 HKT 2026
|
||||
stageCount=57
|
||||
libraryProject=libdebugtemp
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.56
|
||||
buildCount=0
|
||||
baseBetaVersion=15.0.57
|
||||
17
libgpsrelaysentinel/proguard-rules.pro
vendored
Normal file
17
libgpsrelaysentinel/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
27
libgpsrelaysentinel/src/main/AndroidManifest.xml
Normal file
27
libgpsrelaysentinel/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.libgpsrelaysentinel">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".service.GpsSubscribeReceiverService"
|
||||
android:exported="true"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="cc.winboll.studio.libgpsrelaysentinel.action.RECEIVE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".receiver.GpsSubscribeObserverReceiver">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name=".receiver.GpsSubscribeObserverReceiver"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,75 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.manager;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:25
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult;
|
||||
|
||||
public final class GpsSubscribeManager {
|
||||
|
||||
private static GpsSubscribeManager instance;
|
||||
private final Map<String,GpsSubscribeMsg> subscribeMap;
|
||||
private Context appContext;
|
||||
|
||||
private GpsSubscribeManager(){
|
||||
subscribeMap = new HashMap<String, GpsSubscribeMsg>();
|
||||
}
|
||||
|
||||
public static GpsSubscribeManager getInstance(){
|
||||
if(instance == null){
|
||||
instance = new GpsSubscribeManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void initContext(final Context context){
|
||||
this.appContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public void addSubscribe(final GpsSubscribeMsg subscribeMsg){
|
||||
if(subscribeMsg == null){
|
||||
return;
|
||||
}
|
||||
subscribeMap.put(subscribeMsg.getSubscribeUniqueId(),subscribeMsg);
|
||||
}
|
||||
|
||||
public void removeSubscribe(final String sid){
|
||||
if(sid == null){
|
||||
return;
|
||||
}
|
||||
subscribeMap.remove(sid);
|
||||
SubscribeLocationManager.getInstance().removeSubscribe(sid);
|
||||
}
|
||||
|
||||
public boolean isSubscribeExist(final String sid){
|
||||
return subscribeMap.containsKey(sid);
|
||||
}
|
||||
|
||||
public void sendSubscribeResult(final GpsSubscribeResult result){
|
||||
if(appContext == null || result == null){
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK);
|
||||
intent.putExtra("data",result);
|
||||
appContext.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public void clearAllSubscribe(){
|
||||
subscribeMap.clear();
|
||||
SubscribeLocationManager.getInstance().clearAll();
|
||||
}
|
||||
|
||||
public Map<String, GpsSubscribeMsg> getSubscribeMap() {
|
||||
return subscribeMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.manager;
|
||||
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class SubscribeLocationManager {
|
||||
|
||||
private static SubscribeLocationManager instance;
|
||||
|
||||
//订阅配置
|
||||
private final Map<String,GpsSubscribeMsg> subscribeConfigMap;
|
||||
//基准定点坐标
|
||||
private final Map<String,LocationPoint> subscriberPointMap;
|
||||
//真实推送计数(精准统计)
|
||||
private final Map<String,Integer> subscriberPushCountMap;
|
||||
|
||||
private SubscribeLocationManager(){
|
||||
subscribeConfigMap = new HashMap<String, GpsSubscribeMsg>();
|
||||
subscriberPointMap = new HashMap<String, LocationPoint>();
|
||||
subscriberPushCountMap = new HashMap<String, Integer>();
|
||||
}
|
||||
|
||||
public static SubscribeLocationManager getInstance(){
|
||||
if(instance == null){
|
||||
instance = new SubscribeLocationManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
//========= 订阅配置 =========
|
||||
public void putSubscribeConfig(String sid,GpsSubscribeMsg msg){
|
||||
subscribeConfigMap.put(sid,msg);
|
||||
}
|
||||
|
||||
public GpsSubscribeMsg getSubscribeConfig(String sid){
|
||||
return subscribeConfigMap.get(sid);
|
||||
}
|
||||
|
||||
//========= 基准定点坐标 =========
|
||||
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);
|
||||
double radLng1 = Math.toRadians(lng1);
|
||||
double radLng2 = Math.toRadians(lng2);
|
||||
|
||||
double latDiff = radLat1 - radLat2;
|
||||
double lngDiff = radLng1 - radLng2;
|
||||
|
||||
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(String sid){
|
||||
subscribeConfigMap.remove(sid);
|
||||
subscriberPointMap.remove(sid);
|
||||
subscriberPushCountMap.remove(sid);
|
||||
}
|
||||
|
||||
public void clearAll(){
|
||||
subscribeConfigMap.clear();
|
||||
subscriberPointMap.clear();
|
||||
subscriberPushCountMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.model;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:22
|
||||
* WinBoLL Studio
|
||||
* Java7 | API26-30
|
||||
*/
|
||||
public final class GpsSubscribeConst {
|
||||
|
||||
// 新增:GPS定位推送广播
|
||||
public static final String ACTION_GPS_LOCATION = "cc.winboll.studio.ACTION_GPS_LOCATION";
|
||||
|
||||
//订阅运行模式
|
||||
public static final int SUB_TYPE_ALL = 1;
|
||||
public static final int SUB_TYPE_STEP_DISTANCE = 2;
|
||||
|
||||
//原始数据订阅类型
|
||||
public static final int SUBSCRIBE_TYPE_LOCATION = 1;
|
||||
public static final int SUBSCRIBE_TYPE_SATELLITE = 2;
|
||||
public static final int SUBSCRIBE_TYPE_NMEA = 3;
|
||||
|
||||
//订阅返回码
|
||||
public static final int RESULT_SUCCESS = 0;
|
||||
public static final int RESULT_PERMISSION_DENY = 1;
|
||||
public static final int RESULT_PARAM_ERROR = 2;
|
||||
public static final int RESULT_GPS_NOT_AVAILABLE = 3;
|
||||
public static final int RESULT_SYSTEM_LIMIT = 4;
|
||||
|
||||
//GPS设备状态
|
||||
public static final int GPS_STATE_CLOSE = 0;
|
||||
public static final int GPS_STATE_SCANNING = 1;
|
||||
public static final int GPS_STATE_LOCATED = 2;
|
||||
public static final int GPS_STATE_SIGNAL_WEAK = 3;
|
||||
|
||||
//广播Action
|
||||
public static final String ACTION_SUBSCRIBE_REQUEST = "cc.winboll.studio.GPS_SUBSCRIBE_REQUEST";
|
||||
public static final String ACTION_SUBSCRIBE_CALLBACK = "cc.winboll.studio.GPS_SUBSCRIBE_CALLBACK";
|
||||
|
||||
//超时毫秒
|
||||
public static final long SUBSCRIBE_TIME_OUT = 5000;
|
||||
|
||||
//地球半径 距离计算常量
|
||||
public static final double EARTH_RADIUS = 6378137.0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.model;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:24
|
||||
*/
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public final class GpsSubscribeMsg implements Parcelable {
|
||||
|
||||
private final String subscribePackage;
|
||||
private final int subscribeMode;
|
||||
private final float stepDistanceM;
|
||||
|
||||
private final int subscribeType;
|
||||
private final long updateInterval;
|
||||
private final float minDistance;
|
||||
private final boolean backgroundPush;
|
||||
private final String subscribeUniqueId;
|
||||
|
||||
public GpsSubscribeMsg(String subscribePackage,
|
||||
int subscribeMode,
|
||||
float stepDistanceM,
|
||||
int subscribeType,
|
||||
long updateInterval,
|
||||
float minDistance,
|
||||
boolean backgroundPush,
|
||||
String subscribeUniqueId) {
|
||||
this.subscribePackage = subscribePackage;
|
||||
this.subscribeMode = subscribeMode;
|
||||
this.stepDistanceM = stepDistanceM;
|
||||
this.subscribeType = subscribeType;
|
||||
this.updateInterval = updateInterval;
|
||||
this.minDistance = minDistance;
|
||||
this.backgroundPush = backgroundPush;
|
||||
this.subscribeUniqueId = subscribeUniqueId;
|
||||
}
|
||||
|
||||
public String getSubscribePackage() {
|
||||
return subscribePackage;
|
||||
}
|
||||
|
||||
public int getSubscribeMode() {
|
||||
return subscribeMode;
|
||||
}
|
||||
|
||||
public float getStepDistanceM() {
|
||||
return stepDistanceM;
|
||||
}
|
||||
|
||||
public int getSubscribeType() {
|
||||
return subscribeType;
|
||||
}
|
||||
|
||||
public long getUpdateInterval() {
|
||||
return updateInterval;
|
||||
}
|
||||
|
||||
public float getMinDistance() {
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
public boolean isBackgroundPush() {
|
||||
return backgroundPush;
|
||||
}
|
||||
|
||||
public String getSubscribeUniqueId() {
|
||||
return subscribeUniqueId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(subscribePackage);
|
||||
dest.writeInt(subscribeMode);
|
||||
dest.writeFloat(stepDistanceM);
|
||||
dest.writeInt(subscribeType);
|
||||
dest.writeLong(updateInterval);
|
||||
dest.writeFloat(minDistance);
|
||||
dest.writeByte((byte) (backgroundPush ? 1 : 0));
|
||||
dest.writeString(subscribeUniqueId);
|
||||
}
|
||||
|
||||
public static final Creator<GpsSubscribeMsg> CREATOR = new Creator<GpsSubscribeMsg>() {
|
||||
@Override
|
||||
public GpsSubscribeMsg createFromParcel(Parcel in) {
|
||||
return new GpsSubscribeMsg(
|
||||
in.readString(),
|
||||
in.readInt(),
|
||||
in.readFloat(),
|
||||
in.readInt(),
|
||||
in.readLong(),
|
||||
in.readFloat(),
|
||||
in.readByte() == 1,
|
||||
in.readString()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GpsSubscribeMsg[] newArray(int size) {
|
||||
return new GpsSubscribeMsg[size];
|
||||
}
|
||||
};
|
||||
|
||||
public Bundle convertToBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("pkg", subscribePackage);
|
||||
bundle.putInt("subMode",subscribeMode);
|
||||
bundle.putFloat("stepM",stepDistanceM);
|
||||
bundle.putInt("type", subscribeType);
|
||||
bundle.putLong("interval", updateInterval);
|
||||
bundle.putFloat("distance", minDistance);
|
||||
bundle.putBoolean("bgPush", backgroundPush);
|
||||
bundle.putString("sid", subscribeUniqueId);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static GpsSubscribeMsg createByBundle(Bundle bundle) {
|
||||
return new GpsSubscribeMsg(
|
||||
bundle.getString("pkg"),
|
||||
bundle.getInt("subMode"),
|
||||
bundle.getFloat("stepM"),
|
||||
bundle.getInt("type"),
|
||||
bundle.getLong("interval"),
|
||||
bundle.getFloat("distance"),
|
||||
bundle.getBoolean("bgPush"),
|
||||
bundle.getString("sid")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.model;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:25
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public final class GpsSubscribeResult implements Parcelable {
|
||||
|
||||
private final String subscribeUniqueId;
|
||||
private final int resultCode;
|
||||
private final String resultDesc;
|
||||
private final int gpsRunningState;
|
||||
private final long realEffectiveInterval;
|
||||
private final long currentTimeStamp;
|
||||
|
||||
public GpsSubscribeResult(String subscribeUniqueId,
|
||||
int resultCode,
|
||||
String resultDesc,
|
||||
int gpsRunningState,
|
||||
long realEffectiveInterval,
|
||||
long currentTimeStamp) {
|
||||
this.subscribeUniqueId = subscribeUniqueId;
|
||||
this.resultCode = resultCode;
|
||||
this.resultDesc = resultDesc;
|
||||
this.gpsRunningState = gpsRunningState;
|
||||
this.realEffectiveInterval = realEffectiveInterval;
|
||||
this.currentTimeStamp = currentTimeStamp;
|
||||
}
|
||||
|
||||
public String getSubscribeUniqueId() {
|
||||
return subscribeUniqueId;
|
||||
}
|
||||
|
||||
public int getResultCode() {
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
public String getResultDesc() {
|
||||
return resultDesc;
|
||||
}
|
||||
|
||||
public int getGpsRunningState() {
|
||||
return gpsRunningState;
|
||||
}
|
||||
|
||||
public long getRealEffectiveInterval() {
|
||||
return realEffectiveInterval;
|
||||
}
|
||||
|
||||
public long getCurrentTimeStamp() {
|
||||
return currentTimeStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(subscribeUniqueId);
|
||||
dest.writeInt(resultCode);
|
||||
dest.writeString(resultDesc);
|
||||
dest.writeInt(gpsRunningState);
|
||||
dest.writeLong(realEffectiveInterval);
|
||||
dest.writeLong(currentTimeStamp);
|
||||
}
|
||||
|
||||
public static final Creator<GpsSubscribeResult> CREATOR = new Creator<GpsSubscribeResult>() {
|
||||
@Override
|
||||
public GpsSubscribeResult createFromParcel(Parcel in) {
|
||||
return new GpsSubscribeResult(
|
||||
in.readString(),
|
||||
in.readInt(),
|
||||
in.readString(),
|
||||
in.readInt(),
|
||||
in.readLong(),
|
||||
in.readLong()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GpsSubscribeResult[] newArray(int size) {
|
||||
return new GpsSubscribeResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
public Bundle convertToBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("sid", subscribeUniqueId);
|
||||
bundle.putInt("code", resultCode);
|
||||
bundle.putString("desc", resultDesc);
|
||||
bundle.putInt("gpsState", gpsRunningState);
|
||||
bundle.putLong("realInterval", realEffectiveInterval);
|
||||
bundle.putLong("time", currentTimeStamp);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static GpsSubscribeResult createByBundle(Bundle bundle) {
|
||||
return new GpsSubscribeResult(
|
||||
bundle.getString("sid"),
|
||||
bundle.getInt("code"),
|
||||
bundle.getString("desc"),
|
||||
bundle.getInt("gpsState"),
|
||||
bundle.getLong("realInterval"),
|
||||
bundle.getLong("time")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:23
|
||||
* 订阅者基准定点坐标
|
||||
* 每次推送成功自动更新
|
||||
*/
|
||||
public final class LocationPoint implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
private final long recordTime;
|
||||
|
||||
public LocationPoint(double latitude, double longitude, long recordTime) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.recordTime = recordTime;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public long getRecordTime() {
|
||||
return recordTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:27
|
||||
*/
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult;
|
||||
|
||||
public final class GpsSubscribeObserverReceiver extends BroadcastReceiver {
|
||||
|
||||
private OnSubscribeResultListener listener;
|
||||
|
||||
public void setOnSubscribeResultListener(OnSubscribeResultListener listener){
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK.equals(action)){
|
||||
GpsSubscribeResult result = intent.getParcelableExtra("data");
|
||||
if(listener != null && result != null){
|
||||
listener.onResultBack(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnSubscribeResultListener{
|
||||
void onResultBack(GpsSubscribeResult result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
|
||||
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
|
||||
|
||||
/**
|
||||
* 全局消息接收父类服务
|
||||
* 所有应用内接收服务全部继承此类
|
||||
*/
|
||||
public abstract class GpsSubscribeReceiverService extends Service {
|
||||
|
||||
public static final String TAG_PARENT = "GpsSubscribeReceiverService";
|
||||
|
||||
//当前绑定的视图订阅SID
|
||||
protected String bindViewSid;
|
||||
|
||||
public void bindControlSid(String sid){
|
||||
this.bindViewSid = sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一接收GPS推送入口
|
||||
*/
|
||||
public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config){
|
||||
//父类统一日志溯源
|
||||
LogUtils.d(TAG_PARENT,"【消息溯源】接收视图SID:" + bindViewSid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.util;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/05/07 10:26
|
||||
*/
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
public final class TimeCountUtil {
|
||||
|
||||
private final Handler mHandler;
|
||||
private long totalTime;
|
||||
private boolean isRunning;
|
||||
public static final int COUNT_FINISH = 1001;
|
||||
|
||||
public TimeCountUtil(final OnCountListener listener) {
|
||||
mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
if(msg.what == COUNT_FINISH){
|
||||
isRunning = false;
|
||||
if(listener != null){
|
||||
listener.onTimeOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void start(long time){
|
||||
if(isRunning){
|
||||
return;
|
||||
}
|
||||
totalTime = time;
|
||||
isRunning = true;
|
||||
mHandler.sendEmptyMessageDelayed(COUNT_FINISH,totalTime);
|
||||
}
|
||||
|
||||
public void cancel(){
|
||||
mHandler.removeMessages(COUNT_FINISH);
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
public interface OnCountListener{
|
||||
void onTimeOut();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package cc.winboll.studio.libgpsrelaysentinel.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
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;
|
||||
|
||||
public final class GpsSubscribeControlView extends LinearLayout {
|
||||
|
||||
//常量抽取
|
||||
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 String currentSubscribeSid;
|
||||
//一对一专属绑定的接收服务
|
||||
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);
|
||||
mSubscribeManager = GpsSubscribeManager.getInstance();
|
||||
mLocationManager = SubscribeLocationManager.getInstance();
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public GpsSubscribeControlView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mSubscribeManager = GpsSubscribeManager.getInstance();
|
||||
mLocationManager = SubscribeLocationManager.getInstance();
|
||||
initView(context);
|
||||
}
|
||||
|
||||
private void initView(Context context) {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_gps_subscribe_control, this, true);
|
||||
|
||||
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);
|
||||
|
||||
initDefaultConfig();
|
||||
initModeSwitch();
|
||||
initSubscribeSwitch();
|
||||
startAutoRefreshRecord();
|
||||
}
|
||||
|
||||
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) {
|
||||
etStepMeter.setVisibility(checkedId == R.id.rb_mode_step ? VISIBLE : GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initSubscribeSwitch() {
|
||||
switchSubscribe.setOnCheckedChangeListener(new android.widget.CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(android.widget.CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
startSubscribe();
|
||||
} else {
|
||||
stopSubscribe();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startSubscribe() {
|
||||
int subMode = GpsSubscribeConst.SUB_TYPE_ALL;
|
||||
float stepVal = 10f;
|
||||
|
||||
if (rbModeStep.isChecked()) {
|
||||
subMode = GpsSubscribeConst.SUB_TYPE_STEP_DISTANCE;
|
||||
try {
|
||||
stepVal = Float.parseFloat(etStepMeter.getText().toString().trim());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
GpsSubscribeMsg subscribeMsg = new GpsSubscribeMsg(
|
||||
getContext().getPackageName(),
|
||||
subMode,
|
||||
stepVal,
|
||||
GpsSubscribeConst.SUBSCRIBE_TYPE_LOCATION,
|
||||
1000,
|
||||
1f,
|
||||
true,
|
||||
currentSubscribeSid
|
||||
);
|
||||
|
||||
mSubscribeManager.addSubscribe(subscribeMsg);
|
||||
mLocationManager.putSubscribeConfig(currentSubscribeSid, subscribeMsg);
|
||||
mLocationManager.clearPushCount(currentSubscribeSid);
|
||||
|
||||
//开启订阅自动启动专属接收服务
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
BIN
libgpsrelaysentinel/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
BIN
libgpsrelaysentinel/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
libgpsrelaysentinel/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
BIN
libgpsrelaysentinel/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
libgpsrelaysentinel/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
libgpsrelaysentinel/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
libgpsrelaysentinel/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
libgpsrelaysentinel/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="14dp"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:background="#282828"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<!-- SID标识 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_subscribe_sid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#999999"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<!-- 订阅模式选择 -->
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_subscribe_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_mode_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="全量订阅"
|
||||
android:textColor="#FFFFFF"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_mode_step"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步长订阅"
|
||||
android:layout_marginStart="18dp"
|
||||
android:textColor="#FFFFFF"/>
|
||||
</RadioGroup>
|
||||
|
||||
<!-- 步长输入 -->
|
||||
<EditText
|
||||
android:id="@+id/et_step_meter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="移动阈值(米)"
|
||||
android:inputType="numberDecimal"
|
||||
android:text="10"
|
||||
android:visibility="gone"
|
||||
android:textColor="#ffffff"
|
||||
android:textColorHint="#666666"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
<!-- 订阅开关 -->
|
||||
<Switch
|
||||
android:id="@+id/switch_subscribe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开启独立订阅"
|
||||
android:textColor="#EEEEEE"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="#444444"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
<!-- 订阅数据记录表【ID完全对应源码 tv_subscribe_record】 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_subscribe_record"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#88EE88"
|
||||
android:textSize="10sp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="start"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
5
libgpsrelaysentinel/src/main/res/values-v21/styles.xml
Normal file
5
libgpsrelaysentinel/src/main/res/values-v21/styles.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="@android:style/Theme.Material.Light">
|
||||
</style>
|
||||
</resources>
|
||||
8
libgpsrelaysentinel/src/main/res/values/strings.xml
Normal file
8
libgpsrelaysentinel/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="lib_name">libdebugtemp</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="text_libraryactivity">LibraryActivity</string>
|
||||
|
||||
</resources>
|
||||
5
libgpsrelaysentinel/src/main/res/values/styles.xml
Normal file
5
libgpsrelaysentinel/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="@android:style/Theme.Holo.Light">
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user