Compare commits
	
		
			4 Commits
		
	
	
		
			cec2e82550
			...
			positions-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 377ec4de09 | |||
| 
						 | 
					033da4d544 | ||
| 
						 | 
					1398ffc064 | ||
| 247f3f9b49 | 
@@ -62,7 +62,7 @@ dependencies {
 | 
			
		||||
    // 应用介绍页类库
 | 
			
		||||
    api 'io.github.medyo:android-about-page:2.0.0'
 | 
			
		||||
    // 吐司类库
 | 
			
		||||
    api 'com.github.getActivity:ToastUtils:10.5'
 | 
			
		||||
    //api 'com.github.getActivity:ToastUtils:10.5'
 | 
			
		||||
    // 网络连接类库
 | 
			
		||||
    api 'com.squareup.okhttp3:okhttp:4.4.1'
 | 
			
		||||
    // AndroidX 类库
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Mon Oct 27 02:06:36 GMT 2025
 | 
			
		||||
stageCount=14
 | 
			
		||||
#Tue Oct 28 13:36:57 HKT 2025
 | 
			
		||||
stageCount=16
 | 
			
		||||
libraryProject=
 | 
			
		||||
baseVersion=15.0
 | 
			
		||||
publishVersion=15.0.13
 | 
			
		||||
buildCount=13
 | 
			
		||||
baseBetaVersion=15.0.14
 | 
			
		||||
publishVersion=15.0.15
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.0.16
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,9 @@ import android.widget.HorizontalScrollView;
 | 
			
		||||
import android.widget.ScrollView;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
 | 
			
		||||
import cc.winboll.studio.libappbase.GlobalApplication;
 | 
			
		||||
import com.hjq.toast.ToastUtils;
 | 
			
		||||
import com.hjq.toast.style.WhiteToastStyle;
 | 
			
		||||
import cc.winboll.studio.libappbase.ToastUtils;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.Closeable;
 | 
			
		||||
@@ -41,7 +41,6 @@ import java.util.Arrays;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
 | 
			
		||||
 | 
			
		||||
public class App extends GlobalApplication {
 | 
			
		||||
 | 
			
		||||
@@ -58,8 +57,8 @@ public class App extends GlobalApplication {
 | 
			
		||||
        ToastUtils.init(this);
 | 
			
		||||
        // 设置 Toast 布局样式
 | 
			
		||||
        //ToastUtils.setView(R.layout.view_toast);
 | 
			
		||||
        ToastUtils.setStyle(new WhiteToastStyle());
 | 
			
		||||
        ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
 | 
			
		||||
        //ToastUtils.setStyle(new WhiteToastStyle());
 | 
			
		||||
        //ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
 | 
			
		||||
        
 | 
			
		||||
        //CrashHandler.getInstance().registerGlobal(this);
 | 
			
		||||
        //CrashHandler.getInstance().registerPart(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,16 +20,14 @@ import android.os.Handler;
 | 
			
		||||
import android.os.IBinder;
 | 
			
		||||
import android.os.Looper;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.ToastUtils;
 | 
			
		||||
import cc.winboll.studio.positions.models.PositionModel;
 | 
			
		||||
import cc.winboll.studio.positions.models.PositionTaskModel;
 | 
			
		||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
 | 
			
		||||
import cc.winboll.studio.positions.utils.DistanceCalculatorUtil;
 | 
			
		||||
import cc.winboll.studio.positions.utils.NotificationUtil;
 | 
			
		||||
import cc.winboll.studio.positions.utils.ServiceUtil;
 | 
			
		||||
import com.hjq.toast.ToastUtils;
 | 
			
		||||
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
@@ -78,7 +76,7 @@ public class MainService extends Service {
 | 
			
		||||
    // 数据存储集合(Java 7 基础集合,避免Stream/forEach等Java 8+特性)
 | 
			
		||||
    private final ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>();   // 位置数据列表
 | 
			
		||||
    private final ArrayList<PositionTaskModel> mAllTasks = new ArrayList<PositionTaskModel>();// 任务数据列表
 | 
			
		||||
    private PositionModel mCurrentGpsPosition; // 当前GPS定位数据
 | 
			
		||||
    private static PositionModel _mCurrentGpsPosition; // 当前GPS定位数据
 | 
			
		||||
 | 
			
		||||
    // 服务相关变量(Java 7 显式声明,保持原逻辑)
 | 
			
		||||
    MyServiceConnection mMyServiceConnection;
 | 
			
		||||
@@ -109,7 +107,7 @@ public class MainService extends Service {
 | 
			
		||||
				public void run() {
 | 
			
		||||
					LogUtils.d(TAG, "定时任务触发:开始校验任务(间隔1分钟)");
 | 
			
		||||
					// 调用任务校验核心方法(与GPS位置变化时逻辑一致)
 | 
			
		||||
					checkAllTaskTriggerCondition();
 | 
			
		||||
					DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(MainService._mCurrentGpsPosition);
 | 
			
		||||
				}
 | 
			
		||||
			}, TASK_CHECK_INIT_DELAY, TASK_CHECK_INTERVAL, TimeUnit.MINUTES);
 | 
			
		||||
 | 
			
		||||
@@ -200,7 +198,7 @@ public class MainService extends Service {
 | 
			
		||||
     * @return 所有任务的拷贝列表
 | 
			
		||||
     */
 | 
			
		||||
    public ArrayList<PositionTaskModel> getAllTasks() {
 | 
			
		||||
        return new ArrayList<PositionTaskModel>(mAllTasks); // Java 7 集合拷贝方式
 | 
			
		||||
        return mAllTasks; // Java 7 集合拷贝方式
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public void updateTask(PositionTaskModel updatedTask) {
 | 
			
		||||
@@ -466,7 +464,7 @@ public class MainService extends Service {
 | 
			
		||||
     * @return 当前GPS定位模型(未获取时返回null)
 | 
			
		||||
     */
 | 
			
		||||
    public PositionModel getCurrentGpsPosition() {
 | 
			
		||||
        return mCurrentGpsPosition;
 | 
			
		||||
        return _mCurrentGpsPosition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -560,7 +558,7 @@ public class MainService extends Service {
 | 
			
		||||
    /**
 | 
			
		||||
     * 持久化任务数据(Java 7 静态方法调用,保持原逻辑)
 | 
			
		||||
     */
 | 
			
		||||
    void saveAllTasks() {
 | 
			
		||||
    public void saveAllTasks() {
 | 
			
		||||
        LogUtils.d(TAG, String.format("saveTaskList : size=%d", mAllTasks.size()));
 | 
			
		||||
        PositionTaskModel.saveBeanList(MainService.this, mAllTasks, PositionTaskModel.class);
 | 
			
		||||
    }
 | 
			
		||||
@@ -571,7 +569,7 @@ public class MainService extends Service {
 | 
			
		||||
    public void clearAllData() {
 | 
			
		||||
        mPositionList.clear();
 | 
			
		||||
        mAllTasks.clear();
 | 
			
		||||
        mCurrentGpsPosition = null;
 | 
			
		||||
        _mCurrentGpsPosition = null;
 | 
			
		||||
        LogUtils.d(TAG, "clearAllData:已清空所有数据");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -584,7 +582,7 @@ public class MainService extends Service {
 | 
			
		||||
            LogUtils.w(TAG, "syncCurrentGpsPosition:位置为空");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.mCurrentGpsPosition = position;
 | 
			
		||||
        this._mCurrentGpsPosition = position;
 | 
			
		||||
        LogUtils.d(TAG, "syncCurrentGpsPosition:成功(纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + ")");
 | 
			
		||||
        notifyAllGpsListeners(position);
 | 
			
		||||
 | 
			
		||||
@@ -598,14 +596,14 @@ public class MainService extends Service {
 | 
			
		||||
     * 同步GPS状态到前台通知(Java 7 匿名Runnable切换主线程,无Lambda)
 | 
			
		||||
     */
 | 
			
		||||
    private void syncGpsStatusToNotification() {
 | 
			
		||||
        if (!_mIsServiceRunning || mCurrentGpsPosition == null) {
 | 
			
		||||
        if (!_mIsServiceRunning || _mCurrentGpsPosition == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 格式化通知内容(Java 7 String.format,无String.join等Java 8+方法)
 | 
			
		||||
        final String gpsStatus = String.format(
 | 
			
		||||
			"GPS位置:北纬%.4f° 东经%.4f° | 可见位置:%d个",
 | 
			
		||||
			mCurrentGpsPosition.getLatitude(),
 | 
			
		||||
			mCurrentGpsPosition.getLongitude(),
 | 
			
		||||
			_mCurrentGpsPosition.getLatitude(),
 | 
			
		||||
			_mCurrentGpsPosition.getLongitude(),
 | 
			
		||||
			mVisiblePositionIds.size()
 | 
			
		||||
        );
 | 
			
		||||
        // 主线程判断+切换(Java 7 匿名内部类)
 | 
			
		||||
@@ -621,171 +619,7 @@ public class MainService extends Service {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 计算两点间距离(Haversine公式,纯Java 7 基础API,无数学工具类依赖)
 | 
			
		||||
     * @param gpsLat GPS纬度
 | 
			
		||||
     * @param gpsLon GPS经度
 | 
			
		||||
     * @param posLat 目标位置纬度
 | 
			
		||||
     * @param posLon 目标位置经度
 | 
			
		||||
     * @return 两点间距离(单位:米)
 | 
			
		||||
     */
 | 
			
		||||
    private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) {
 | 
			
		||||
        final double EARTH_RADIUS = 6371000; // 地球半径(米)
 | 
			
		||||
        double latDiff = Math.toRadians(posLat - gpsLat);
 | 
			
		||||
        double lonDiff = Math.toRadians(posLon - gpsLon);
 | 
			
		||||
        // Haversine公式核心计算(Java 7 基础数学方法)
 | 
			
		||||
        double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
 | 
			
		||||
			+ Math.cos(Math.toRadians(gpsLat)) * Math.cos(Math.toRadians(posLat))
 | 
			
		||||
			* Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
 | 
			
		||||
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
 | 
			
		||||
        return EARTH_RADIUS * c;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 强制刷新所有位置距离(GPS更新后调用,计算距离+校验任务触发条件)
 | 
			
		||||
     */
 | 
			
		||||
    public void forceRefreshDistance() {
 | 
			
		||||
        if (mCurrentGpsPosition == null || mPositionList.isEmpty()) {
 | 
			
		||||
            LogUtils.w(TAG, "forceRefreshDistance:GPS未获取或位置为空");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 遍历所有位置计算距离(Java 7 增强for循环,无Stream)
 | 
			
		||||
        for (PositionModel pos : mPositionList) {
 | 
			
		||||
            if (pos.isEnableRealPositionDistance()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    double distance = calculateHaversineDistance(
 | 
			
		||||
						mCurrentGpsPosition.getLatitude(),
 | 
			
		||||
						mCurrentGpsPosition.getLongitude(),
 | 
			
		||||
						pos.getLatitude(),
 | 
			
		||||
						pos.getLongitude()
 | 
			
		||||
                    );
 | 
			
		||||
                    pos.setRealPositionDistance(distance);
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    pos.setRealPositionDistance(-1); // 标记距离计算失败
 | 
			
		||||
                    LogUtils.e(TAG, "计算距离失败:" + e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                pos.setRealPositionDistance(-1); // 未启用距离计算,标记为无效
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 距离刷新后校验任务触发条件+通知GPS监听者
 | 
			
		||||
        //checkAllTaskTriggerCondition();
 | 
			
		||||
        notifyAllGpsListeners(mCurrentGpsPosition);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // 任务触发相关方法(Java 7 语法,全迭代器遍历,无Java 8+特性)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验所有任务触发条件(距离达标则触发任务通知)
 | 
			
		||||
     */
 | 
			
		||||
    private void checkAllTaskTriggerCondition() {
 | 
			
		||||
        if (mCurrentGpsPosition == null || mPositionList.isEmpty() || mAllTasks.isEmpty()) {
 | 
			
		||||
            LogUtils.d(TAG, "checkAllTaskTriggerCondition:跳过校验(GPS/位置/任务为空)");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LogUtils.d(TAG, "checkAllTaskTriggerCondition:开始校验(任务总数=" + mAllTasks.size() + ")");
 | 
			
		||||
        // 迭代器遍历任务(Java 7 安全遍历,避免并发修改异常)
 | 
			
		||||
        Iterator<PositionTaskModel> taskIter = mAllTasks.iterator();
 | 
			
		||||
        while (taskIter.hasNext()) {
 | 
			
		||||
            PositionTaskModel task = taskIter.next();
 | 
			
		||||
            // 仅校验“已启用”且“绑定有效位置”的任务
 | 
			
		||||
            if (!task.isEnable() || TextUtils.isEmpty(task.getPositionId())) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 查找任务绑定的位置(Java 7 迭代器遍历位置列表)
 | 
			
		||||
            PositionModel bindPos = null;
 | 
			
		||||
            Iterator<PositionModel> posIter = mPositionList.iterator();
 | 
			
		||||
            while (posIter.hasNext()) {
 | 
			
		||||
                PositionModel pos = posIter.next();
 | 
			
		||||
                if (task.getPositionId().equals(pos.getPositionId())) {
 | 
			
		||||
                    bindPos = pos;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (bindPos == null) {
 | 
			
		||||
                LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":绑定位置不存在,跳过");
 | 
			
		||||
                task.setIsBingo(false);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			// 校验任务开始时间
 | 
			
		||||
			if (task.getStartTime() > System.currentTimeMillis()) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            // 校验距离条件(判断是否满足任务触发阈值)
 | 
			
		||||
            double currentDistance = bindPos.getRealPositionDistance();
 | 
			
		||||
            if (currentDistance < 0) {
 | 
			
		||||
                LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":距离计算失败,跳过");
 | 
			
		||||
                task.setIsBingo(false);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            boolean isTriggered = false;
 | 
			
		||||
            int taskDistance = task.getDiscussDistance();
 | 
			
		||||
            // 任务触发条件:大于/小于指定距离(Java 7 基础判断,无三元运算符嵌套)
 | 
			
		||||
            if (task.isGreaterThan()) {
 | 
			
		||||
                isTriggered = currentDistance > taskDistance;
 | 
			
		||||
            } else if (task.isLessThan()) {
 | 
			
		||||
                isTriggered = currentDistance < taskDistance;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 更新任务触发状态+发送通知(状态变化时才处理)
 | 
			
		||||
            if (task.isBingo() != isTriggered) {
 | 
			
		||||
                task.setIsBingo(isTriggered);
 | 
			
		||||
                if (isTriggered) {
 | 
			
		||||
                    sendTaskTriggerNotification(task, bindPos, currentDistance);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        saveAllTasks(); // 持久化更新后的任务状态
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送任务触发通知(更新前台通知+显示Toast,Java 7 匿名Runnable切换主线程)
 | 
			
		||||
     * @param task 触发的任务
 | 
			
		||||
     * @param bindPos 任务绑定的位置
 | 
			
		||||
     * @param currentDistance 当前距离
 | 
			
		||||
     */
 | 
			
		||||
    private void sendTaskTriggerNotification(final PositionTaskModel task, PositionModel bindPos, double currentDistance) {
 | 
			
		||||
        if (!_mIsServiceRunning) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 格式化通知内容(Java 7 String.format,无TextBlock等Java 15+特性)
 | 
			
		||||
        final String triggerContent = String.format(
 | 
			
		||||
			"任务触发:%s\n位置:%s\n当前距离:%.1f米(条件:%s%d米)",
 | 
			
		||||
			task.getTaskDescription(),
 | 
			
		||||
			bindPos.getMemo(),
 | 
			
		||||
			currentDistance,
 | 
			
		||||
			task.isGreaterThan() ? ">" : "<",
 | 
			
		||||
			task.getDiscussDistance()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 更新前台通知(主线程判断+切换)
 | 
			
		||||
        updateNotificationGpsStatus(triggerContent);
 | 
			
		||||
 | 
			
		||||
        // 显示Toast(主线程安全调用,Java 7 匿名内部类)
 | 
			
		||||
        if (Looper.myLooper() == Looper.getMainLooper()) {
 | 
			
		||||
            ToastUtils.show(triggerContent);
 | 
			
		||||
			NotificationUtil.show(MainService.this, task.getTaskId(), task.getPositionId(), task.getTaskDescription());
 | 
			
		||||
        } else {
 | 
			
		||||
            new Handler(Looper.getMainLooper()).post(new Runnable() {
 | 
			
		||||
					@Override
 | 
			
		||||
					public void run() {
 | 
			
		||||
						ToastUtils.show(triggerContent);
 | 
			
		||||
						NotificationUtil.show(MainService.this, task.getTaskId(), task.getPositionId(), task.getTaskDescription());
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
        }
 | 
			
		||||
        LogUtils.i(TAG, "任务触发通知:" + triggerContent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
@@ -866,8 +700,7 @@ public class MainService extends Service {
 | 
			
		||||
 | 
			
		||||
                    // 同步GPS位置+刷新距离+日志(原逻辑保留)
 | 
			
		||||
                    syncCurrentGpsPosition(gpsPos);
 | 
			
		||||
                    forceRefreshDistance();
 | 
			
		||||
					checkAllTaskTriggerCondition();
 | 
			
		||||
					DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(gpsPos);
 | 
			
		||||
                    LogUtils.d(TAG, "GPS位置更新:纬度=" + location.getLatitude() + ",经度=" + location.getLongitude());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -913,7 +746,7 @@ public class MainService extends Service {
 | 
			
		||||
                // GPS禁用时清空状态+通知+提示(Java 7 基础逻辑)
 | 
			
		||||
                if (provider.equals(LocationManager.GPS_PROVIDER)) {
 | 
			
		||||
                    isGpsEnabled = false;
 | 
			
		||||
                    mCurrentGpsPosition = null;
 | 
			
		||||
                    _mCurrentGpsPosition = null;
 | 
			
		||||
                    String statusDesc = "GPS已关闭(用户手动关闭)";
 | 
			
		||||
                    LogUtils.w(TAG, statusDesc);
 | 
			
		||||
                    notifyAllGpsStatusListeners(statusDesc);
 | 
			
		||||
@@ -1032,11 +865,52 @@ public class MainService extends Service {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送任务触发通知(更新前台通知+显示Toast,Java 7 匿名Runnable切换主线程)
 | 
			
		||||
     * @param task 触发的任务
 | 
			
		||||
     * @param bindPos 任务绑定的位置
 | 
			
		||||
     * @param currentDistance 当前距离
 | 
			
		||||
     */
 | 
			
		||||
    public void sendTaskTriggerNotification(final PositionTaskModel task, PositionModel bindPos, double currentDistance) {
 | 
			
		||||
        /*if (!_mIsServiceRunning) {
 | 
			
		||||
		 return;
 | 
			
		||||
		 }*/
 | 
			
		||||
 | 
			
		||||
        // 格式化通知内容(Java 7 String.format,无TextBlock等Java 15+特性)
 | 
			
		||||
        final String triggerContent = String.format(
 | 
			
		||||
			"任务触发:%s\n位置:%s\n当前距离:%.1f米(条件:%s%d米)",
 | 
			
		||||
			task.getTaskDescription(),
 | 
			
		||||
			bindPos.getMemo(),
 | 
			
		||||
			currentDistance,
 | 
			
		||||
			task.isGreaterThan() ? ">" : "<",
 | 
			
		||||
			task.getDiscussDistance()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 更新前台通知(主线程判断+切换)
 | 
			
		||||
        updateNotificationGpsStatus(triggerContent);
 | 
			
		||||
 | 
			
		||||
        // 显示Toast(主线程安全调用,Java 7 匿名内部类)
 | 
			
		||||
        if (Looper.myLooper() == Looper.getMainLooper()) {
 | 
			
		||||
            ToastUtils.show(triggerContent);
 | 
			
		||||
			NotificationUtil.show(MainService.this, task.getTaskId(), task.getPositionId(), task.getTaskDescription());
 | 
			
		||||
        } else {
 | 
			
		||||
            new Handler(Looper.getMainLooper()).post(new Runnable() {
 | 
			
		||||
					@Override
 | 
			
		||||
					public void run() {
 | 
			
		||||
						ToastUtils.show(triggerContent);
 | 
			
		||||
						NotificationUtil.show(MainService.this, task.getTaskId(), task.getPositionId(), task.getTaskDescription());
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
        }
 | 
			
		||||
        LogUtils.i(TAG, "任务触发通知:" + triggerContent);
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新前台通知的GPS状态(Java 7 主线程切换,匿名Runnable实现)
 | 
			
		||||
     * @param statusText 通知显示的状态文本
 | 
			
		||||
     */
 | 
			
		||||
    private void updateNotificationGpsStatus(final String statusText) {
 | 
			
		||||
    void updateNotificationGpsStatus(final String statusText) {
 | 
			
		||||
        if (_mIsServiceRunning) {
 | 
			
		||||
            // 判断当前线程是否为主线程,避免UI操作在子线程
 | 
			
		||||
            if (Looper.myLooper() == Looper.getMainLooper()) {
 | 
			
		||||
@@ -1060,7 +934,7 @@ public class MainService extends Service {
 | 
			
		||||
     * 通知所有GPS监听者位置更新(Java 7 迭代器+弱引用管理,无Stream)
 | 
			
		||||
     * @param currentGpsPos 当前最新GPS位置
 | 
			
		||||
     */
 | 
			
		||||
    private void notifyAllGpsListeners(PositionModel currentGpsPos) {
 | 
			
		||||
    public void notifyAllGpsListeners(PositionModel currentGpsPos) {
 | 
			
		||||
        if (currentGpsPos == null || mGpsListeners.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -1141,8 +1015,8 @@ public class MainService extends Service {
 | 
			
		||||
            mGpsListeners.add(new WeakReference<GpsUpdateListener>(listener));
 | 
			
		||||
            LogUtils.d(TAG, "GPS监听注册成功,当前数量:" + mGpsListeners.size());
 | 
			
		||||
            // 注册后立即推送当前GPS位置(避免监听者错过初始数据)
 | 
			
		||||
            if (mCurrentGpsPosition != null) {
 | 
			
		||||
                notifySingleListener(listener, mCurrentGpsPosition);
 | 
			
		||||
            if (_mCurrentGpsPosition != null) {
 | 
			
		||||
                notifySingleListener(listener, _mCurrentGpsPosition);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,237 @@
 | 
			
		||||
package cc.winboll.studio.positions.utils;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.Looper;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.ToastUtils;
 | 
			
		||||
import cc.winboll.studio.positions.models.PositionModel;
 | 
			
		||||
import cc.winboll.studio.positions.models.PositionTaskModel;
 | 
			
		||||
import cc.winboll.studio.positions.services.MainService;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
 | 
			
		||||
 * @Date 2025/10/27 18:40
 | 
			
		||||
 * @Describe 距离计算工具集(单例模式)
 | 
			
		||||
 */
 | 
			
		||||
public class DistanceCalculatorUtil {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "DistanceCalculatorUtil";
 | 
			
		||||
 | 
			
		||||
    // 1. 私有静态 volatile 实例:保证多线程下实例可见性,避免指令重排序导致的空指针
 | 
			
		||||
    private static volatile DistanceCalculatorUtil sInstance;
 | 
			
		||||
	Context mContext;
 | 
			
		||||
	ArrayList<PositionModel> mPositionList;   // 位置数据列表
 | 
			
		||||
    ArrayList<PositionTaskModel> mAllTasks;// 任务数据列表
 | 
			
		||||
	PositionModel mGpsPositionCalculated;
 | 
			
		||||
	long mLastCalculatedTime = 0;
 | 
			
		||||
	long mMinCalculatedTimeBettween = 30000; // GPS数据更新时,两次计算之间的最小时间间隔
 | 
			
		||||
	double mMinjumpDistance = 10.0f; // GPS数据更新时,能跳跃距离最小有效值,达到有效值时,两次计算之间的最小时间间隔阀值将被忽略。
 | 
			
		||||
 | 
			
		||||
    // 2. 私有构造器:禁止外部通过 new 关键字创建实例,确保单例唯一性
 | 
			
		||||
    private DistanceCalculatorUtil(Context context) {
 | 
			
		||||
        // 可选:初始化工具类依赖的资源(如配置参数、缓存等)
 | 
			
		||||
		mContext = context;
 | 
			
		||||
 | 
			
		||||
        LogUtils.d(TAG, "DistanceCalculatorUtil 单例实例初始化");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 3. 公开静态方法:双重校验锁获取单例,兼顾线程安全与性能
 | 
			
		||||
    public static DistanceCalculatorUtil getInstance(Context context) {
 | 
			
		||||
        // 第一重校验:避免已创建实例时的频繁加锁,提升性能
 | 
			
		||||
        if (sInstance == null) {
 | 
			
		||||
            // 加锁:确保多线程下仅一个线程进入实例创建逻辑
 | 
			
		||||
            synchronized (DistanceCalculatorUtil.class) {
 | 
			
		||||
                // 第二重校验:防止多线程并发时重复创建实例
 | 
			
		||||
                if (sInstance == null) {
 | 
			
		||||
                    sInstance = new DistanceCalculatorUtil(context);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return sInstance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ---------------------- 以下可补充距离计算相关工具方法 ----------------------
 | 
			
		||||
    /**
 | 
			
		||||
     * 示例:Haversine 公式计算两点间距离(单位:米)
 | 
			
		||||
     * @param lat1 第一点纬度
 | 
			
		||||
     * @param lon1 第一点经度
 | 
			
		||||
     * @param lat2 第二点纬度
 | 
			
		||||
     * @param lon2 第二点经度
 | 
			
		||||
     * @return 两点间直线距离(米),计算失败返回 -1
 | 
			
		||||
     */
 | 
			
		||||
    public static double calculateHaversineDistance(double lat1, double lon1, double lat2, double lon2) {
 | 
			
		||||
        try {
 | 
			
		||||
            final double EARTH_RADIUS = 6371000; // 地球半径(米)
 | 
			
		||||
            // 角度转弧度
 | 
			
		||||
            double latDiff = Math.toRadians(lat2 - lat1);
 | 
			
		||||
            double lonDiff = Math.toRadians(lon2 - lon1);
 | 
			
		||||
 | 
			
		||||
            // Haversine 核心公式
 | 
			
		||||
            double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
 | 
			
		||||
				+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
 | 
			
		||||
				* Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
 | 
			
		||||
            double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
 | 
			
		||||
 | 
			
		||||
            return EARTH_RADIUS * c; // 返回距离(米)
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            LogUtils.d(TAG, "Haversine 距离计算失败:" + e.getMessage());
 | 
			
		||||
            return -1; // 标记计算失败
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 计算两点间距离(Haversine公式,纯Java 7 基础API,无数学工具类依赖)
 | 
			
		||||
     * @param gpsLat GPS纬度
 | 
			
		||||
     * @param gpsLon GPS经度
 | 
			
		||||
     * @param posLat 目标位置纬度
 | 
			
		||||
     * @param posLon 目标位置经度
 | 
			
		||||
     * @return 两点间距离(单位:米)
 | 
			
		||||
     */
 | 
			
		||||
    /*private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) {
 | 
			
		||||
	 final double EARTH_RADIUS = 6371000; // 地球半径(米)
 | 
			
		||||
	 double latDiff = Math.toRadians(posLat - gpsLat);
 | 
			
		||||
	 double lonDiff = Math.toRadians(posLon - gpsLon);
 | 
			
		||||
	 // Haversine公式核心计算(Java 7 基础数学方法)
 | 
			
		||||
	 double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
 | 
			
		||||
	 + Math.cos(Math.toRadians(gpsLat)) * Math.cos(Math.toRadians(posLat))
 | 
			
		||||
	 * Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
 | 
			
		||||
	 double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
 | 
			
		||||
	 return EARTH_RADIUS * c;
 | 
			
		||||
	 }*/
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
     * 校验所有任务触发条件(距离达标则触发任务通知)
 | 
			
		||||
     */
 | 
			
		||||
    public void checkAllTaskTriggerCondition(PositionModel currentGpsPosition) {
 | 
			
		||||
		if (currentGpsPosition == null) {
 | 
			
		||||
			LogUtils.d(TAG, "传入坐标参数为空,退出函数。");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (mGpsPositionCalculated == null) {
 | 
			
		||||
			mGpsPositionCalculated = currentGpsPosition;
 | 
			
		||||
			LogUtils.d(TAG, "最后计算位置记录为空,现在使用新坐标为初始化。");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 计算频率控制模块
 | 
			
		||||
		//
 | 
			
		||||
		// 计算与最近一次GPS计算的时间间隔
 | 
			
		||||
		long nCalculatedTimeBettween = System.currentTimeMillis() - mLastCalculatedTime;
 | 
			
		||||
		// 计算跳跃距离
 | 
			
		||||
		double jumpDistance = calculateHaversineDistance(mGpsPositionCalculated.getLatitude(), mGpsPositionCalculated.getLongitude(), currentGpsPosition.getLatitude(), currentGpsPosition.getLongitude());
 | 
			
		||||
		if (jumpDistance < mMinjumpDistance) {
 | 
			
		||||
			LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:跳跃距离%f,小于50米。", jumpDistance));
 | 
			
		||||
			// 跳跃距离小于最小有效跳跃值
 | 
			
		||||
			if (nCalculatedTimeBettween < mMinCalculatedTimeBettween) {
 | 
			
		||||
				//间隔小于最小时间间隔设定
 | 
			
		||||
				LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:与最近一次计算间隔时间%d,坐标变化忽略。", nCalculatedTimeBettween));
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:跳跃距离%f,与上次计算间隔%d,启动任务数据计算。", jumpDistance, nCalculatedTimeBettween));
 | 
			
		||||
 | 
			
		||||
		// 获取位置任务基础数据
 | 
			
		||||
		MainService mainService = MainService.getInstance(mContext);
 | 
			
		||||
		mPositionList = mainService.getPositionList();
 | 
			
		||||
		mAllTasks = mainService.getAllTasks();
 | 
			
		||||
 | 
			
		||||
		// 任务为空,跳过校验。
 | 
			
		||||
        if (mPositionList.isEmpty() || mAllTasks.isEmpty()) {
 | 
			
		||||
            LogUtils.d(TAG, "checkAllTaskTriggerCondition:任务数据为空,跳过校验。");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		// 更新所有位置点的位置距离数据
 | 
			
		||||
		refreshRealPositionDistance(currentGpsPosition);
 | 
			
		||||
 | 
			
		||||
        // 迭代器遍历任务(Java 7 安全遍历,避免并发修改异常)
 | 
			
		||||
        Iterator<PositionTaskModel> taskIter = mAllTasks.iterator();
 | 
			
		||||
        while (taskIter.hasNext()) {
 | 
			
		||||
            PositionTaskModel task = taskIter.next();
 | 
			
		||||
            // 仅校验“已启用”且“绑定有效位置”的任务
 | 
			
		||||
            if (!task.isEnable() || TextUtils.isEmpty(task.getPositionId())) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 查找任务绑定的位置(Java 7 迭代器遍历位置列表)
 | 
			
		||||
            PositionModel bindPos = null;
 | 
			
		||||
            Iterator<PositionModel> posIter = mPositionList.iterator();
 | 
			
		||||
            while (posIter.hasNext()) {
 | 
			
		||||
                PositionModel pos = posIter.next();
 | 
			
		||||
                if (task.getPositionId().equals(pos.getPositionId())) {
 | 
			
		||||
                    bindPos = pos;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (bindPos == null) {
 | 
			
		||||
                LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":绑定位置不存在,跳过");
 | 
			
		||||
                task.setIsBingo(false);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
			// 校验任务开始时间
 | 
			
		||||
			if (task.getStartTime() > System.currentTimeMillis()) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
            // 校验距离条件(判断是否满足任务触发阈值)
 | 
			
		||||
            double currentDistance = bindPos.getRealPositionDistance();
 | 
			
		||||
            if (currentDistance < 0) {
 | 
			
		||||
                LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":距离计算失败,跳过");
 | 
			
		||||
                task.setIsBingo(false);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            boolean isTriggered = false;
 | 
			
		||||
            int taskDistance = task.getDiscussDistance();
 | 
			
		||||
            // 任务触发条件:大于/小于指定距离(Java 7 基础判断,无三元运算符嵌套)
 | 
			
		||||
            if (task.isGreaterThan()) {
 | 
			
		||||
                isTriggered = currentDistance > taskDistance;
 | 
			
		||||
            } else if (task.isLessThan()) {
 | 
			
		||||
                isTriggered = currentDistance < taskDistance;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 更新任务触发状态+发送通知(状态变化时才处理)
 | 
			
		||||
            if (task.isBingo() != isTriggered) {
 | 
			
		||||
                task.setIsBingo(isTriggered);
 | 
			
		||||
                if (isTriggered) {
 | 
			
		||||
                    MainService.getInstance(mContext).sendTaskTriggerNotification(task, bindPos, currentDistance);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MainService.getInstance(mContext).saveAllTasks(); // 持久化更新后的任务状态
 | 
			
		||||
		// 记录最后坐标更新点
 | 
			
		||||
		mGpsPositionCalculated = currentGpsPosition;
 | 
			
		||||
		// 记录数据计算时间
 | 
			
		||||
		mLastCalculatedTime = System.currentTimeMillis();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 强制刷新所有位置距离(GPS更新后调用,计算距离+校验任务触发条件)
 | 
			
		||||
     */
 | 
			
		||||
    public void refreshRealPositionDistance(PositionModel currentGpsPosition) {
 | 
			
		||||
        // 遍历所有位置计算距离(Java 7 增强for循环,无Stream)
 | 
			
		||||
        for (PositionModel pos : mPositionList) {
 | 
			
		||||
            if (pos.isEnableRealPositionDistance()) {
 | 
			
		||||
				double distance = DistanceCalculatorUtil.calculateHaversineDistance(
 | 
			
		||||
					currentGpsPosition.getLatitude(),
 | 
			
		||||
					currentGpsPosition.getLongitude(),
 | 
			
		||||
					pos.getLatitude(),
 | 
			
		||||
					pos.getLongitude()
 | 
			
		||||
				);
 | 
			
		||||
				pos.setRealPositionDistance(distance);
 | 
			
		||||
            } else {
 | 
			
		||||
                pos.setRealPositionDistance(-1); // 未启用距离计算,标记为无效
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 距离刷新后通知GPS监听者
 | 
			
		||||
        MainService.getInstance(mContext).notifyAllGpsListeners(currentGpsPosition);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user