feat: 实现应用空转状态持久化与MainService动态数据源切换

1. App.java:
   - 添加SharedPreferences持久化空转状态,App重启后自动恢复
   - setAppIdleRunning()中增加MainService服务重启逻辑,同步空转状态

2. MainService.java:
   - onCreate/onStartCommand: 检测空转状态,自动启动IdleGpsService
   - 重构GPS监听逻辑,新增mIdleGpsListener实例与中央处理方法
   - startGpsLocation/stopGpsLocation: 根据空转状态自动切换系统GPS与IdleGpsService数据源
   - 前台通知栏显示 [IDLE RUNNING] 标识及全精度(%.15f)经纬度数据
   - 通知内容区分"空转GPS"与真实"GPS位置"前缀

3. IdleGpsService.java:
   - 添加服务启动时的Toast提示反馈
   - 修复MainService中IdleGpsService类型不匹配问题

4. MainActivity.java:
   - 简化空转切换逻辑,移除冗余的IdleGpsService手动启停代码
This commit is contained in:
2026-05-04 09:41:42 +08:00
parent 9d868f216c
commit 84c616cbda
5 changed files with 169 additions and 47 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon May 04 03:36:37 CST 2026
#Mon May 04 09:35:11 CST 2026
stageCount=20
libraryProject=
baseVersion=15.12
publishVersion=15.12.19
buildCount=23
buildCount=29
baseBetaVersion=15.12.20

View File

@@ -5,6 +5,7 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
@@ -25,6 +26,7 @@ import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.services.MainService;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -59,9 +61,13 @@ public class App extends GlobalApplication {
//===================== 全局静态常量与变量 =====================
public static final String TAG = "App";
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
private static App sInstance;
private static final String SP_NAME = "app_idle_config";
private static final String SP_KEY_IDLE_RUNNING = "is_idle_running";
// 应用全局空转状态标记
public static boolean isAppIdleRunning = false;
private static boolean isAppIdleRunning = false;
//===================== 空转状态对外方法 =====================
/**
@@ -83,23 +89,67 @@ public class App extends GlobalApplication {
LogUtils.d(TAG, "setAppIdleRunning -> 传入参数 idleRunning = " + idleRunning);
if (isDebugging()) {
isAppIdleRunning = idleRunning;
LogUtils.i(TAG, "setAppIdleRunning -> 调试模式,空转状态设置生效");
saveIdleRunningToSp(idleRunning);
LogUtils.i(TAG, "setAppIdleRunning -> 调试模式,空转状态设置生效并持久化");
// 重启MainService服务以同步新的空转状态stopService对未运行服务无效故无需判断状态
if (sInstance != null) {
LogUtils.i(TAG, "setAppIdleRunning -> 重启MainService服务以同步空转状态");
Intent serviceIntent = new Intent(sInstance, MainService.class);
sInstance.stopService(serviceIntent);
sInstance.startService(serviceIntent);
}
} else {
LogUtils.i(TAG, "setAppIdleRunning -> 非调试模式,空转设置无效");
LogUtils.i(TAG, "Non-debug state, app idle setting is meaningless.");
}
}
/**
* 从SharedPreferences加载空转状态
*/
private static void loadIdleRunningFromSp() {
try {
if (sInstance != null) {
SharedPreferences sp = sInstance.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
isAppIdleRunning = sp.getBoolean(SP_KEY_IDLE_RUNNING, false);
LogUtils.i(TAG, "loadIdleRunningFromSp -> 从SP加载空转状态" + isAppIdleRunning);
}
} catch (Exception e) {
LogUtils.e(TAG, "loadIdleRunningFromSp -> 加载空转状态失败:" + e.getMessage());
isAppIdleRunning = false;
}
}
/**
* 保存空转状态到SharedPreferences
* @param idleRunning 空转状态
*/
private static void saveIdleRunningToSp(boolean idleRunning) {
try {
if (sInstance != null) {
SharedPreferences sp = sInstance.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
sp.edit().putBoolean(SP_KEY_IDLE_RUNNING, idleRunning).apply();
LogUtils.i(TAG, "saveIdleRunningToSp -> 空转状态已保存:" + idleRunning);
}
} catch (Exception e) {
LogUtils.e(TAG, "saveIdleRunningToSp -> 保存空转状态失败:" + e.getMessage());
}
}
//===================== 应用生命周期 =====================
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
LogUtils.i(TAG, "onCreate -> 全局Application初始化开始");
setIsDebugging(BuildConfig.DEBUG);
WinBoLLActivityManager.init(this);
ToastUtils.init(this);
loadIdleRunningFromSp();
LogUtils.i(TAG, "onCreate -> 全局组件初始化全部完成");
}

View File

@@ -36,7 +36,15 @@ import cc.winboll.studio.positions.handlers.AppIdleRunningModeHandler;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
import cc.winboll.studio.positions.utils.ServiceUtil;
import cc.winboll.studio.positions.services.IdleGpsService;
import cc.winboll.studio.positions.services.MainService;
import cc.winboll.studio.positions.R;
import android.os.Handler;
import android.os.Looper;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.content.Intent;
import android.os.Handler;
/**
* 主页面控制器
@@ -345,11 +353,6 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
boolean idleNow = App.isAppIdleRunning();
boolean idleNew = !idleNow;
App.setAppIdleRunning(idleNew);
if (idleNew) {
IdleGpsService.getInstance().start();
} else {
IdleGpsService.getInstance().stop();
}
AppIdleRunningModeHandler.sendIdleSwitch(idleNew);
AppIdleRunningModeHandler.sendIdleLog("菜单手动切换空转状态:" + idleNew);
LogUtils.d(TAG, "onOptionsItemSelected -> 空转状态已切换,当前:" + idleNew);

View File

@@ -3,6 +3,7 @@ package cc.winboll.studio.positions.services;
import android.os.Handler;
import android.os.Looper;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.handlers.AppIdleRunningModeHandler;
import cc.winboll.studio.positions.models.PositionModel;
@@ -100,6 +101,7 @@ public class IdleGpsService {
currentBearing = 1;
AppIdleRunningModeHandler.sendIdleLog("空转GPS服务已启动");
notifyStatusChange("空转GPS服务已启动");
ToastUtils.show("空转GPS服务已启动");
handler.post(updateRunnable);
handler.post(bearingRunnable);
}

View File

@@ -22,6 +22,7 @@ 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.App;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
@@ -54,6 +55,35 @@ public class MainService extends Service {
void onGpsStatusChanged(String status);
}
// 空转GPS监听实例用于注册到IdleGpsService
private final GpsUpdateListener mIdleGpsListener = new GpsUpdateListener() {
@Override
public void onGpsPositionUpdated(PositionModel currentGpsPos) {
handleGpsPositionUpdate(currentGpsPos);
}
@Override
public void onGpsStatusChanged(String status) {
handleGpsStatusChange(status);
}
};
// 中央处理GPS 位置更新
private void handleGpsPositionUpdate(PositionModel pos) {
if (pos == null) return;
syncCurrentGpsPosition(pos);
DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(pos);
String src = App.isAppIdleRunning() ? " (空转)" : "";
LogUtils.d(TAG, "GPS位置更新纬度=" + pos.getLatitude() + ",经度=" + pos.getLongitude() + src);
}
// 中央处理GPS 状态变化
private void handleGpsStatusChange(String status) {
LogUtils.d(TAG, "GPS状态变化" + status);
notifyAllGpsStatusListeners(status);
updateNotificationGpsStatus(status);
}
// 任务更新监听接口Java 7 风格供Adapter监听任务变化
public interface TaskUpdateListener {
void onTaskUpdated();
@@ -77,6 +107,7 @@ public class MainService extends Service {
private final ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>(); // 位置数据列表
private final ArrayList<PositionTaskModel> mAllTasks = new ArrayList<PositionTaskModel>();// 任务数据列表
private static PositionModel _mCurrentGpsPosition; // 当前GPS定位数据
private boolean isListeningToIdleGps = false; // 当前是否监听空转GPS
// 服务相关变量Java 7 显式声明,保持原逻辑)
MyServiceConnection mMyServiceConnection;
@@ -366,6 +397,9 @@ public class MainService extends Service {
}
if (mAppConfigsUtil.isEnableMainService(true)) {
if (App.isAppIdleRunning()) {
IdleGpsService.getInstance().start();
}
run(); // 启动服务核心逻辑
}
}
@@ -382,6 +416,9 @@ public class MainService extends Service {
// 启动前台服务Java 7 显式调用,无方法引用)
String initialStatus = "[ Positions ] is in Service.";
if (App.isAppIdleRunning()) {
initialStatus += " [IDLE RUNNING]";
}
NotificationUtil.createForegroundServiceNotification(this, initialStatus);
startForeground(NotificationUtil.FOREGROUND_SERVICE_NOTIFICATION_ID,
NotificationUtil.createForegroundServiceNotification(this, initialStatus));
@@ -599,21 +636,28 @@ public class MainService extends Service {
if (!_mIsServiceRunning || _mCurrentGpsPosition == null) {
return;
}
// 格式化通知内容Java 7 String.format无String.join等Java 8+方法
final String gpsStatus = String.format(
"GPS位置北纬%.4f° 东经%.4f° | 可见位置:%d个",
// 根据空转状态决定通知前缀区分空转GPS与真实GPS
String prefix = App.isAppIdleRunning() ? "空转GPS" : "GPS位置";
// 格式化通知内容Java 7 String.format使用%.15f显示全部GPS精度
String gpsStatus = String.format(
"%s北纬%.15f° 东经%.15f° | 可见位置:%d个",
prefix,
_mCurrentGpsPosition.getLatitude(),
_mCurrentGpsPosition.getLongitude(),
mVisiblePositionIds.size()
);
if (App.isAppIdleRunning()) {
gpsStatus += " [IDLE RUNNING]";
}
final String finalGpsStatus = gpsStatus;
// 主线程判断+切换Java 7 匿名内部类)
if (Looper.myLooper() == Looper.getMainLooper()) {
NotificationUtil.updateForegroundServiceStatus(this, gpsStatus);
NotificationUtil.updateForegroundServiceStatus(this, finalGpsStatus);
} else {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
NotificationUtil.updateForegroundServiceStatus(MainService.this, gpsStatus);
NotificationUtil.updateForegroundServiceStatus(MainService.this, finalGpsStatus);
}
});
}
@@ -634,6 +678,9 @@ public class MainService extends Service {
if (intent != null) {
isSettingToEnable = intent.getBooleanExtra(EXTRA_IS_SETTING_TO_ENABLE, false);
if (isSettingToEnable) {
if (App.isAppIdleRunning()) {
IdleGpsService.getInstance().start();
}
run(); // 重启服务核心逻辑(保证服务启动后进入运行状态)
}
}
@@ -701,10 +748,8 @@ public class MainService extends Service {
gpsPos.setPositionId("CURRENT_GPS_POS");
gpsPos.setMemo("实时GPS位置");
// 同步GPS位置+刷新距离+日志(原逻辑保留)
syncCurrentGpsPosition(gpsPos);
DistanceCalculatorUtil.getInstance(MainService.this).checkAllTaskTriggerCondition(gpsPos);
LogUtils.d(TAG, "GPS位置更新纬度=" + location.getLatitude() + ",经度=" + location.getLongitude());
// 调用中央处理方法
handleGpsPositionUpdate(gpsPos);
}
}
@@ -725,9 +770,7 @@ public class MainService extends Service {
statusDesc = "GPS状态临时不可用遮挡";
break;
}
LogUtils.d(TAG, statusDesc);
notifyAllGpsStatusListeners(statusDesc);
updateNotificationGpsStatus(statusDesc);
handleGpsStatusChange(statusDesc);
}
}
@@ -736,10 +779,7 @@ public class MainService extends Service {
// GPS启用时更新状态+通知+重启定位Java 7 基础逻辑)
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = true;
String statusDesc = "GPS已开启用户手动打开";
LogUtils.d(TAG, statusDesc);
notifyAllGpsStatusListeners(statusDesc);
updateNotificationGpsStatus("GPS已开启正在获取位置...");
handleGpsStatusChange("GPS已开启用户手动打开");
startGpsLocation();
}
}
@@ -750,10 +790,7 @@ public class MainService extends Service {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = false;
_mCurrentGpsPosition = null;
String statusDesc = "GPS已关闭用户手动关闭";
LogUtils.w(TAG, statusDesc);
notifyAllGpsStatusListeners(statusDesc);
updateNotificationGpsStatus("GPS已关闭请在设置中开启");
handleGpsStatusChange("GPS已关闭用户手动关闭");
ToastUtils.show("GPS已关闭无法获取位置请在设置中开启");
}
}
@@ -801,8 +838,27 @@ public class MainService extends Service {
/**
* 启动GPS定位Java 7 异常处理无try-with-resources显式捕获SecurityException
* 【关键修改】根据应用空转状态切换数据源IdleGpsService 或 系统GPS
*/
private void startGpsLocation() {
// 检查空转状态:如果处于空转,使用 IdleGpsService
if (App.isAppIdleRunning()) {
if (isListeningToIdleGps) return; // 已在监听空转GPS无需重复注册
stopGpsLocation(); // 停止系统GPS监听如果正在运行
IdleGpsService.getInstance().registerGpsUpdateListener(mIdleGpsListener);
isListeningToIdleGps = true;
LogUtils.d(TAG, "启动GPS定位使用空转模拟数据");
handleGpsStatusChange("空转GPS监听中...");
return;
}
// 系统GPS逻辑
if (isListeningToIdleGps) {
// 之前是空转现在切换到系统GPS
IdleGpsService.getInstance().unregisterGpsUpdateListener(mIdleGpsListener);
isListeningToIdleGps = false;
}
if (!checkGpsReady()) {
return;
}
@@ -824,12 +880,11 @@ public class MainService extends Service {
lastGpsPos.setLatitude(lastKnownLocation.getLatitude());
lastGpsPos.setLongitude(lastKnownLocation.getLongitude());
lastGpsPos.setPositionId("CURRENT_GPS_POS");
syncCurrentGpsPosition(lastGpsPos);
handleGpsPositionUpdate(lastGpsPos);
LogUtils.d(TAG, "已获取缓存GPS位置纬度=" + lastKnownLocation.getLatitude());
} else {
String tip = "无缓存GPS位置等待实时定位...";
LogUtils.d(TAG, tip);
notifyAllGpsStatusListeners(tip);
handleGpsStatusChange(tip);
updateNotificationGpsStatus("GPS搜索中请移至开阔地带");
}
@@ -837,33 +892,40 @@ public class MainService extends Service {
// 定位权限异常Java 7 显式捕获无Lambda异常处理
String error = "启动GPS失败权限异常" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error);
handleGpsStatusChange(error);
isGpsPermissionGranted = false;
updateNotificationGpsStatus("定位权限异常无法获取GPS");
} catch (Exception e) {
// 其他异常如LocationManager为空、系统服务异常等
String error = "启动GPS失败" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error);
handleGpsStatusChange(error);
updateNotificationGpsStatus("GPS启动失败尝试重试...");
}
}
/**
* 停止GPS定位Java 7 异常处理,移除监听器避免内存泄漏)
* 【关键修改】根据当前监听源停止对应的服务
*/
private void stopGpsLocation() {
// 校验参数:避免空指针+权限未授予时调用
if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) {
try {
mLocationManager.removeUpdates(mGpsLocationListener);
String tip = "GPS定位已停止移除监听器";
LogUtils.d(TAG, tip);
notifyAllGpsStatusListeners(tip);
} catch (Exception e) {
String error = "停止GPS失败" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error);
if (isListeningToIdleGps) {
IdleGpsService.getInstance().unregisterGpsUpdateListener(mIdleGpsListener);
isListeningToIdleGps = false;
LogUtils.d(TAG, "停止GPS定位已注销空转GPS监听");
} else {
// 校验参数:避免空指针+权限未授予时调用
if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) {
try {
mLocationManager.removeUpdates(mGpsLocationListener);
String tip = "GPS定位已停止移除监听器";
LogUtils.d(TAG, tip);
handleGpsStatusChange(tip);
} catch (Exception e) {
String error = "停止GPS失败" + e.getMessage();
LogUtils.e(TAG, error);
handleGpsStatusChange(error);
}
}
}
}
@@ -915,14 +977,19 @@ public class MainService extends Service {
*/
void updateNotificationGpsStatus(final String statusText) {
if (_mIsServiceRunning) {
String text = statusText;
if (App.isAppIdleRunning()) {
text += " [IDLE RUNNING]";
}
final String finalText = text;
// 判断当前线程是否为主线程避免UI操作在子线程
if (Looper.myLooper() == Looper.getMainLooper()) {
NotificationUtil.updateForegroundServiceStatus(this, statusText);
NotificationUtil.updateForegroundServiceStatus(this, finalText);
} else {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
NotificationUtil.updateForegroundServiceStatus(MainService.this, statusText);
NotificationUtil.updateForegroundServiceStatus(MainService.this, finalText);
}
});
}