From 1398ffc064ad7b629579bcb7e057df3dfc003f72 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 27 Oct 2025 20:57:51 +0800 Subject: [PATCH] 20251027_205747_537 --- positions/build.gradle | 2 +- positions/build.properties | 4 +- .../java/cc/winboll/studio/positions/App.java | 9 +- .../positions/services/MainService.java | 190 +++++----------- .../utils/DistanceCalculatorUtil.java | 204 ++++++++++++++++++ 5 files changed, 260 insertions(+), 149 deletions(-) create mode 100644 positions/src/main/java/cc/winboll/studio/positions/utils/DistanceCalculatorUtil.java diff --git a/positions/build.gradle b/positions/build.gradle index 00f8e5e..5b4ea14 100644 --- a/positions/build.gradle +++ b/positions/build.gradle @@ -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 类库 diff --git a/positions/build.properties b/positions/build.properties index d37046a..794462b 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Oct 27 10:11:58 HKT 2025 +#Mon Oct 27 12:55:27 GMT 2025 stageCount=15 libraryProject= baseVersion=15.0 publishVersion=15.0.14 -buildCount=0 +buildCount=2 baseBetaVersion=15.0.15 diff --git a/positions/src/main/java/cc/winboll/studio/positions/App.java b/positions/src/main/java/cc/winboll/studio/positions/App.java index 9ca6fd7..c99a5d8 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/App.java +++ b/positions/src/main/java/cc/winboll/studio/positions/App.java @@ -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); diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java index c0471b6..79be839 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java +++ b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java @@ -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; @@ -109,7 +107,7 @@ public class MainService extends Service { public void run() { LogUtils.d(TAG, "定时任务触发:开始校验任务(间隔1分钟)"); // 调用任务校验核心方法(与GPS位置变化时逻辑一致) - checkAllTaskTriggerCondition(); + DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(0.0f, 0.0f); } }, TASK_CHECK_INIT_DELAY, TASK_CHECK_INTERVAL, TimeUnit.MINUTES); @@ -200,7 +198,7 @@ public class MainService extends Service { * @return 所有任务的拷贝列表 */ public ArrayList getAllTasks() { - return new ArrayList(mAllTasks); // Java 7 集合拷贝方式 + return mAllTasks; // Java 7 集合拷贝方式 } public void updateTask(PositionTaskModel updatedTask) { @@ -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); } @@ -621,25 +619,6 @@ 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更新后调用,计算距离+校验任务触发条件) @@ -654,7 +633,7 @@ public class MainService extends Service { for (PositionModel pos : mPositionList) { if (pos.isEnableRealPositionDistance()) { try { - double distance = calculateHaversineDistance( + double distance = DistanceCalculatorUtil.calculateHaversineDistance( mCurrentGpsPosition.getLatitude(), mCurrentGpsPosition.getLongitude(), pos.getLatitude(), @@ -676,118 +655,6 @@ public class MainService extends Service { } - // ========================================================================= - // 任务触发相关方法(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 taskIter = mAllTasks.iterator(); - while (taskIter.hasNext()) { - PositionTaskModel task = taskIter.next(); - // 仅校验“已启用”且“绑定有效位置”的任务 - if (!task.isEnable() || TextUtils.isEmpty(task.getPositionId())) { - continue; - } - - // 查找任务绑定的位置(Java 7 迭代器遍历位置列表) - PositionModel bindPos = null; - Iterator 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); - } - - // ========================================================================= // 服务生命周期+辅助服务相关(Java 7 语法,无Lambda/方法引用) // ========================================================================= @@ -867,7 +734,7 @@ public class MainService extends Service { // 同步GPS位置+刷新距离+日志(原逻辑保留) syncCurrentGpsPosition(gpsPos); forceRefreshDistance(); - checkAllTaskTriggerCondition(); + DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(location.getLatitude(), location.getLongitude()); LogUtils.d(TAG, "GPS位置更新:纬度=" + location.getLatitude() + ",经度=" + location.getLongitude()); } } @@ -1032,11 +899,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()) { diff --git a/positions/src/main/java/cc/winboll/studio/positions/utils/DistanceCalculatorUtil.java b/positions/src/main/java/cc/winboll/studio/positions/utils/DistanceCalculatorUtil.java new file mode 100644 index 0000000..c4f8b0d --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/utils/DistanceCalculatorUtil.java @@ -0,0 +1,204 @@ +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&豆包大模型 + * @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; + private ArrayList mPositionList; // 位置数据列表 + private ArrayList mAllTasks;// 任务数据列表 + double mLatitudeCalculated = 0.0f; + double mLongitudeCalculated = 0.0f; + long mLastCalculatedTime = 0; + long mMinCalculatedTimeBettween = 30000; // GPS数据更新时,两次计算之间的最小时间间隔 + double mMinjumpDistance = 10.0f; // GPS数据更新时,能跳跃距离最小有效值,达到有效值时,两次计算之间的最小时间间隔阀值将被忽略。 + + // 2. 私有构造器:禁止外部通过 new 关键字创建实例,确保单例唯一性 + private DistanceCalculatorUtil(Context context) { + // 可选:初始化工具类依赖的资源(如配置参数、缓存等) + mContext = context; + MainService mainService = MainService.getInstance(mContext); + mPositionList = mainService.getPositionList(); + mAllTasks = mainService.getAllTasks(); + 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(double nLatitudeNew, double nLongitudeNew) { + if (mLatitudeCalculated == 0.0f || mLongitudeCalculated == 0.0f) { + mLatitudeCalculated = nLatitudeNew; + mLongitudeCalculated = nLongitudeNew; + LogUtils.d(TAG, "checkAllTaskTriggerCondition:初始计算位置为空,现在使用新坐标为初始位置。"); + } + + // 计算频率控制模块 + // + // 计算与最近一次GPS计算的时间间隔 + long nCalculatedTimeBettween = System.currentTimeMillis() - mLastCalculatedTime; + // 计算跳跃距离 + double jumpDistance = calculateHaversineDistance(mLatitudeCalculated, mLongitudeCalculated, nLatitudeNew, nLongitudeNew); + if (jumpDistance < mMinjumpDistance) { + LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:跳跃距离%f,小于50米。", jumpDistance)); + // 跳跃距离小于最小有效跳跃值 + if (nCalculatedTimeBettween < mMinCalculatedTimeBettween) { + //间隔小于最小时间间隔设定 + LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:与最近一次计算间隔时间%d,坐标变化忽略。", nCalculatedTimeBettween)); + return; + } + } + + // 任务为空,跳过校验。 + if (mPositionList.isEmpty() || mAllTasks.isEmpty()) { + LogUtils.d(TAG, "checkAllTaskTriggerCondition:任务数据为空,跳过校验。"); + return; + } + + LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:跳跃距离%f,与上次计算间隔%d,启动任务数据计算。", jumpDistance, nCalculatedTimeBettween)); + + // 迭代器遍历任务(Java 7 安全遍历,避免并发修改异常) + Iterator taskIter = mAllTasks.iterator(); + while (taskIter.hasNext()) { + PositionTaskModel task = taskIter.next(); + // 仅校验“已启用”且“绑定有效位置”的任务 + if (!task.isEnable() || TextUtils.isEmpty(task.getPositionId())) { + continue; + } + + // 查找任务绑定的位置(Java 7 迭代器遍历位置列表) + PositionModel bindPos = null; + Iterator 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(); // 持久化更新后的任务状态 + // 记录数据计算时间 + mLastCalculatedTime = System.currentTimeMillis(); + } + + +}