Compare commits
	
		
			9 Commits
		
	
	
		
			5646b589e0
			...
			positions-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6376ff4ccf | |||
|   | dd20060754 | ||
|   | 298b337392 | ||
| 377ec4de09 | |||
|   | 033da4d544 | ||
|   | 1398ffc064 | ||
| 247f3f9b49 | |||
|   | cec2e82550 | ||
|   | ac5f425624 | 
| @@ -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 | ||||
| #Sat Oct 25 18:29:48 HKT 2025 | ||||
| stageCount=14 | ||||
| #Tue Oct 28 14:17:09 HKT 2025 | ||||
| stageCount=17 | ||||
| libraryProject= | ||||
| baseVersion=15.0 | ||||
| publishVersion=15.0.13 | ||||
| publishVersion=15.0.16 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.0.14 | ||||
| baseBetaVersion=15.0.17 | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -21,6 +21,7 @@ import cc.winboll.studio.positions.activities.LocationActivity; | ||||
| import cc.winboll.studio.positions.activities.WinBoLLActivity; | ||||
| import cc.winboll.studio.positions.services.MainService; | ||||
| import cc.winboll.studio.positions.utils.AppConfigsUtil; | ||||
| import cc.winboll.studio.positions.utils.ServiceUtil; | ||||
|  | ||||
| /** | ||||
|  * 主页面:仅负责 | ||||
| @@ -141,18 +142,11 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
| 					// 权限就绪:执行服务启停逻辑 | ||||
| 					if (isChecked) { | ||||
| 						LogUtils.d(TAG, "设置启动服务"); | ||||
| 						AppConfigsUtil.getInstance(MainActivity.this).setIsEnableMainService(true); | ||||
| 						// 启动服务(startService确保服务独立运行,不受Activity绑定影响) | ||||
| 						startService(new Intent(MainActivity.this, MainService.class)); | ||||
| 						ServiceUtil.startAutoService(MainActivity.this); | ||||
| 					} else { | ||||
| 						LogUtils.d(TAG, "设置关闭服务"); | ||||
| 						AppConfigsUtil.getInstance(MainActivity.this).setIsEnableMainService(false); | ||||
| 						// 停止服务前先解绑,避免服务被Activity持有 | ||||
| //						if (isServiceBound) { | ||||
| //							unbindService(mServiceConn); | ||||
| //							isServiceBound = false; | ||||
| //						} | ||||
| 						stopService(new Intent(MainActivity.this, MainService.class)); | ||||
|  | ||||
| 						ServiceUtil.stopAutoService(MainActivity.this); | ||||
| 					} | ||||
|  | ||||
| 					mManagePositionsButton.setEnabled(isChecked); | ||||
|   | ||||
| @@ -18,7 +18,8 @@ import cc.winboll.studio.positions.utils.ServiceUtil; | ||||
| public class AssistantService extends Service { | ||||
|  | ||||
|     public final static String TAG = "AssistantService"; | ||||
|  | ||||
| 	public static final String EXTRA_IS_SETTING_TO_ENABLE = "EXTRA_IS_SETTING_TO_ENABLE"; | ||||
| 	 | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     volatile boolean mIsServiceRunning; | ||||
|     AppConfigsUtil mAppConfigsUtil; | ||||
| @@ -37,13 +38,18 @@ public class AssistantService extends Service { | ||||
|         } | ||||
|         // 设置运行参数 | ||||
|         mIsServiceRunning = false; | ||||
|         run(); | ||||
|         if (mAppConfigsUtil.isEnableMainService(true)) { | ||||
| 			run(); | ||||
| 		} | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         run(); | ||||
|         return START_STICKY; | ||||
| 		if (mAppConfigsUtil.isEnableMainService(true)) { | ||||
| 			run(); | ||||
| 		} | ||||
|  | ||||
|         return  mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -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; | ||||
| @@ -43,6 +41,8 @@ public class MainService extends Service { | ||||
|  | ||||
|     public static final String TAG = "MainService"; | ||||
|  | ||||
| 	public static final String EXTRA_IS_SETTING_TO_ENABLE = "EXTRA_IS_SETTING_TO_ENABLE"; | ||||
|  | ||||
|     // ---------------------- 新增:定时器相关变量 ---------------------- | ||||
|     private ScheduledExecutorService taskCheckTimer; // 任务校验定时器 | ||||
|     private static final long TASK_CHECK_INTERVAL = 1; // 定时间隔(1分钟) | ||||
| @@ -76,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; | ||||
| @@ -107,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); | ||||
|  | ||||
| @@ -198,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) { | ||||
| @@ -323,8 +323,10 @@ public class MainService extends Service { | ||||
|      */ | ||||
|     public static synchronized MainService getInstance(Context context) { | ||||
|         if (sInstance == null) { | ||||
|             Intent intent = new Intent(context.getApplicationContext(), MainService.class); | ||||
|             context.getApplicationContext().startService(intent); | ||||
| 			if (AppConfigsUtil.getInstance(context).isEnableMainService(true)) { | ||||
| 				Intent intent = new Intent(context.getApplicationContext(), MainService.class); | ||||
| 				context.getApplicationContext().startService(intent); | ||||
| 			} | ||||
|             return null; | ||||
|         } | ||||
|         if (sAppContext == null) { | ||||
| @@ -363,7 +365,9 @@ public class MainService extends Service { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|  | ||||
|         run(); // 启动服务核心逻辑 | ||||
| 		if (mAppConfigsUtil.isEnableMainService(true)) { | ||||
| 			run(); // 启动服务核心逻辑 | ||||
| 		} | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -460,7 +464,7 @@ public class MainService extends Service { | ||||
|      * @return 当前GPS定位模型(未获取时返回null) | ||||
|      */ | ||||
|     public PositionModel getCurrentGpsPosition() { | ||||
|         return mCurrentGpsPosition; | ||||
|         return _mCurrentGpsPosition; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -554,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); | ||||
|     } | ||||
| @@ -565,7 +569,7 @@ public class MainService extends Service { | ||||
|     public void clearAllData() { | ||||
|         mPositionList.clear(); | ||||
|         mAllTasks.clear(); | ||||
|         mCurrentGpsPosition = null; | ||||
|         _mCurrentGpsPosition = null; | ||||
|         LogUtils.d(TAG, "clearAllData:已清空所有数据"); | ||||
|     } | ||||
|  | ||||
| @@ -578,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); | ||||
|  | ||||
| @@ -592,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 匿名内部类) | ||||
| @@ -615,179 +619,7 @@ public class MainService extends Service { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算可见位置距离(原逻辑保留,Java 7 语法兼容,无Stream/并行流) | ||||
|      */ | ||||
|     private void calculateVisiblePositionDistance() { | ||||
|         // 原有逻辑(Java 7 语法适配:用迭代器/基础循环,无Lambda/forEach) | ||||
|         // 注:原代码标注“略”,此处保持空实现,实际使用时补充具体逻辑 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算两点间距离(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); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // ========================================================================= | ||||
| @@ -798,9 +630,17 @@ public class MainService extends Service { | ||||
|      */ | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         run(); // 重启服务核心逻辑(保证服务启动后进入运行状态) | ||||
|         // 返回START_STICKY:服务被异常杀死后,系统会尝试重启(原逻辑保留) | ||||
|         return mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
| 		boolean isSettingToEnable = false; | ||||
| 		if (intent != null) { | ||||
| 			isSettingToEnable = intent.getBooleanExtra(EXTRA_IS_SETTING_TO_ENABLE, false); | ||||
| 			if (isSettingToEnable) { | ||||
| 				run(); // 重启服务核心逻辑(保证服务启动后进入运行状态) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|         // 如果被设置为自启动就返回START_STICKY:服务被异常杀死后,系统会尝试重启(原逻辑保留) | ||||
| 		// 否则就启动默认参数 | ||||
|         return isSettingToEnable ? Service.START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -863,8 +703,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()); | ||||
|                 } | ||||
|             } | ||||
| @@ -910,7 +749,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); | ||||
| @@ -1029,11 +868,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()) { | ||||
| @@ -1057,7 +937,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; | ||||
|         } | ||||
| @@ -1138,8 +1018,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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -41,11 +41,11 @@ public class AppConfigsUtil { | ||||
|         return sInstance; | ||||
|     } | ||||
|  | ||||
| 	void loadConfigs() { | ||||
| 	public void loadConfigs() { | ||||
| 		mAppConfigsModel = AppConfigsModel.loadBean(mContext, AppConfigsModel.class); | ||||
| 	} | ||||
|  | ||||
| 	void saveConfigs() { | ||||
| 	public void saveConfigs() { | ||||
| 		AppConfigsModel.saveBean(mContext, mAppConfigsModel); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,246 @@ | ||||
| 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; | ||||
| 		} | ||||
|  | ||||
| 		// 计算频率控制模块 | ||||
| 		// | ||||
| 		// 计算与最近一次GPS计算的时间间隔 | ||||
| 		long nCalculatedTimeBettween = System.currentTimeMillis() - mLastCalculatedTime; | ||||
| 		// 计算跳跃距离 | ||||
| 		double gpsPositionCalculatedLatitude = mGpsPositionCalculated == null ?0.0f: mGpsPositionCalculated.getLatitude(); | ||||
| 		double gpsPositionCalculatedLongitude = mGpsPositionCalculated == null ?0.0f: mGpsPositionCalculated.getLongitude(); | ||||
| 		double jumpDistance = calculateHaversineDistance(gpsPositionCalculatedLatitude, gpsPositionCalculatedLongitude, 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; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (mGpsPositionCalculated == null) { | ||||
| 			mGpsPositionCalculated = currentGpsPosition; | ||||
| 			LogUtils.d(TAG, "最后计算位置记录为空,现在使用新坐标为初始化。"); | ||||
| 		} | ||||
|  | ||||
| 		LogUtils.d(TAG, String.format("checkAllTaskTriggerCondition:跳跃距离%f,与上次计算间隔%d,现在启动任务数据计算。", jumpDistance, nCalculatedTimeBettween)); | ||||
|  | ||||
| 		// 获取位置任务基础数据 | ||||
| 		MainService mainService = MainService.getInstance(mContext); | ||||
| 		mPositionList = mainService.getPositionList(); | ||||
| 		mAllTasks = mainService.getAllTasks(); | ||||
|  | ||||
| 		// 位置数据为空,跳过校验。 | ||||
|         if (mPositionList.isEmpty()) { | ||||
|             LogUtils.d(TAG, "checkAllTaskTriggerCondition:位置数据为空,跳过距离计算。"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| 		// 更新所有位置点的位置距离数据 | ||||
| 		refreshRealPositionDistance(currentGpsPosition); | ||||
|  | ||||
| 		// 任务数据为空,跳过校验。 | ||||
|         if (mAllTasks.isEmpty()) { | ||||
|             LogUtils.d(TAG, "checkAllTaskTriggerCondition:任务数据为空,跳过任务提醒检查计算。"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 迭代器遍历任务(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); | ||||
|     } | ||||
| } | ||||
| @@ -7,6 +7,10 @@ package cc.winboll.studio.positions.utils; | ||||
|  */ | ||||
| import android.app.ActivityManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.positions.services.AssistantService; | ||||
| import cc.winboll.studio.positions.services.MainService; | ||||
| import java.util.List; | ||||
|  | ||||
| public class ServiceUtil { | ||||
| @@ -31,4 +35,35 @@ public class ServiceUtil { | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| 	public static void stopAutoService(Context context) { | ||||
| 		AppConfigsUtil appConfigsUtil = AppConfigsUtil.getInstance(context); | ||||
| 		appConfigsUtil.setIsEnableMainService(false); | ||||
| 		appConfigsUtil.saveConfigs(); | ||||
| 		// 关闭并设置主服务 | ||||
| 		Intent intent1 = new Intent(context, MainService.class); | ||||
| 		intent1.putExtra(MainService.EXTRA_IS_SETTING_TO_ENABLE, false); | ||||
| 		context.stopService(intent1); // 先停止旧服务 | ||||
| 		context.startService(intent1); // 传入新的启动标志位,返回给系统 | ||||
| 		// 关闭并设置主服务守护进程 | ||||
| 		Intent intent2 = new Intent(context, AssistantService.class); | ||||
| 		intent2.putExtra(AssistantService.EXTRA_IS_SETTING_TO_ENABLE, false); | ||||
| 		context.stopService(intent2); // 先停止旧服务 | ||||
| 		context.startService(intent2); // 传入新的启动标志位,返回给系统 | ||||
| 		// 再次关闭所有服务 | ||||
| 		context.stopService(intent1); | ||||
| 		context.stopService(intent2); | ||||
|  | ||||
| 		LogUtils.d(TAG, "stopAutoService"); | ||||
| 	} | ||||
|  | ||||
| 	public static void startAutoService(Context context) { | ||||
| 		AppConfigsUtil appConfigsUtil = AppConfigsUtil.getInstance(context); | ||||
| 		appConfigsUtil.setIsEnableMainService(true); | ||||
| 		appConfigsUtil.saveConfigs(); | ||||
| 		Intent intent = new Intent(context, MainService.class); | ||||
| 		intent.putExtra(MainService.EXTRA_IS_SETTING_TO_ENABLE, true); | ||||
| 		context.startService(intent); | ||||
| 		LogUtils.d(TAG, "startAutoService"); | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user