初步后台GPS获取实现

This commit is contained in:
ZhanGSKen
2025-10-01 16:50:14 +08:00
parent 894edd5fa4
commit 70a5405077
5 changed files with 542 additions and 926 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Oct 01 07:47:20 GMT 2025
#Wed Oct 01 08:48:18 GMT 2025
stageCount=4
libraryProject=
baseVersion=15.0
publishVersion=15.0.3
buildCount=55
buildCount=56
baseBetaVersion=15.0.4

View File

@@ -16,7 +16,6 @@ import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.activities.LocationActivity;
import cc.winboll.studio.positions.services.DistanceRefreshService;
import cc.winboll.studio.positions.services.MainService;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
import com.hjq.toast.ToastUtils;
@@ -36,31 +35,31 @@ public class MainActivity extends AppCompatActivity {
private Switch mServiceSwitch;
private Toolbar mToolbar;
// 服务相关:服务实例、绑定状态标记
private DistanceRefreshService mDistanceService;
//private DistanceRefreshService mDistanceService;
private boolean isServiceBound = false;
// ---------------------- 服务连接回调(仅用于获取服务状态,不依赖服务执行核心逻辑) ----------------------
private final ServiceConnection mServiceConn = new ServiceConnection() {
/**
* 服务绑定成功:获取服务实例,同步开关状态(以服务实际状态为准)
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Java 7 显式强转 Binder 实例确保类型匹配避免ClassCastException
DistanceRefreshService.DistanceBinder binder = (DistanceRefreshService.DistanceBinder) service;
mDistanceService = binder.getService();
isServiceBound = true;
}
/**
* 服务意外断开(如服务崩溃):重置服务实例和绑定状态
*/
@Override
public void onServiceDisconnected(ComponentName name) {
mDistanceService = null;
isServiceBound = false;
}
};
// private final ServiceConnection mServiceConn = new ServiceConnection() {
// /**
// * 服务绑定成功:获取服务实例,同步开关状态(以服务实际状态为准)
// */
// @Override
// public void onServiceConnected(ComponentName name, IBinder service) {
// // Java 7 显式强转 Binder 实例确保类型匹配避免ClassCastException
// DistanceRefreshService.DistanceBinder binder = (DistanceRefreshService.DistanceBinder) service;
// mDistanceService = binder.getService();
// isServiceBound = true;
// }
//
// /**
// * 服务意外断开(如服务崩溃):重置服务实例和绑定状态
// */
// @Override
// public void onServiceDisconnected(ComponentName name) {
// mDistanceService = null;
// isServiceBound = false;
// }
// };
// ---------------------- Activity 生命周期核心初始化UI、申请权限、绑定服务、释放资源 ----------------------
@Override
@@ -77,18 +76,18 @@ public class MainActivity extends AppCompatActivity {
requestLocationPermissions();
}
// 4. 绑定服务(仅用于获取服务实时状态,不影响服务独立运行)
bindDistanceService();
//bindDistanceService();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 页面销毁时解绑服务避免Activity与服务相互引用导致内存泄漏
if (isServiceBound) {
unbindService(mServiceConn);
isServiceBound = false;
mDistanceService = null;
}
// if (isServiceBound) {
// unbindService(mServiceConn);
// isServiceBound = false;
// mDistanceService = null;
// }
}
// ---------------------- 核心功能1初始化UI组件Toolbar + 服务开关) ----------------------
@@ -131,10 +130,10 @@ public class MainActivity extends AppCompatActivity {
LogUtils.d(TAG, "设置关闭服务");
AppConfigsUtil.getInstance(MainActivity.this).setIsEnableMainService(false);
// 停止服务前先解绑避免服务被Activity持有
if (isServiceBound) {
unbindService(mServiceConn);
isServiceBound = false;
}
// if (isServiceBound) {
// unbindService(mServiceConn);
// isServiceBound = false;
// }
stopService(new Intent(MainActivity.this, MainService.class));
}
}
@@ -145,11 +144,11 @@ public class MainActivity extends AppCompatActivity {
/**
* 绑定服务(仅用于获取服务状态,不启动服务)
*/
private void bindDistanceService() {
Intent serviceIntent = new Intent(this, MainService.class);
// BIND_AUTO_CREATE服务未启动则创建仅为获取状态启停由开关控制
bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}
// private void bindDistanceService() {
// Intent serviceIntent = new Intent(this, MainService.class);
// // BIND_AUTO_CREATE服务未启动则创建仅为获取状态启停由开关控制
// bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
// }
// ---------------------- 核心功能3页面跳转位置管理页+日志页) ----------------------
/**

View File

@@ -11,18 +11,15 @@ import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.adapters.PositionAdapter;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
import cc.winboll.studio.positions.services.DistanceRefreshService;
import cc.winboll.studio.positions.services.MainService;
import java.util.ArrayList;
/**
@@ -42,7 +39,7 @@ public class LocationActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private PositionAdapter mAdapter;
// 直连服务实例(替代绑定,通过单例/全局初始化获取)
private DistanceRefreshService mDistanceService;
private MainService mMainService;
// 缓存服务数据从服务PUBLIC方法获取避免重复调用
private ArrayList<PositionModel> mCachedPositionList;
private ArrayList<PositionTaskModel> mCachedTaskList;
@@ -74,7 +71,7 @@ public class LocationActivity extends AppCompatActivity {
mAdapter = null;
}
// 2. 置空服务实例+缓存数据帮助GC回收
mDistanceService = null;
mMainService = null;
mCachedPositionList = null;
mCachedTaskList = null;
mRecyclerView = null;
@@ -89,16 +86,15 @@ public class LocationActivity extends AppCompatActivity {
private void initServiceInstance() {
// 步骤1先启动Service确保Service已创建实例能被初始化
// 步骤2初始化Service实例延迟100-200ms确保Service onCreate执行完成
// 或在startService后通过ServiceConnection绑定获取实例更可靠
initServiceInstance();
//initServiceInstance();
// 方式通过服务PUBLIC单例方法获取实例不直接new避免私有数据初始化重复
mDistanceService = DistanceRefreshService.getInstance();
mMainService = MainService.getInstance();
// 容错:若单例未初始化(如首次启动),提示并处理
if (mDistanceService == null) {
if (mMainService == null) {
LogUtils.e(TAG, "服务实例初始化失败:单例未创建");
Toast.makeText(this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
finish();
@@ -110,16 +106,16 @@ public class LocationActivity extends AppCompatActivity {
*/
private void checkServiceStatus() {
// 1. 服务实例未初始化
if (mDistanceService == null) {
if (mMainService == null) {
return; // 已在initServiceInstance中处理此处避免重复finish
}
// 2. 检查服务运行状态通过服务PUBLIC方法isServiceRunning()获取,不访问私有字段)
if (!mDistanceService.isServiceRunning()) {
// 服务未运行手动触发启动调用服务PUBLIC方法run()
mDistanceService.run();
LogUtils.d(TAG, "服务未运行已通过PUBLIC方法触发启动");
}
// if (!mMainService.isServiceRunning()) {
// // 服务未运行手动触发启动调用服务PUBLIC方法run()
// mMainService.run();
// LogUtils.d(TAG, "服务未运行已通过PUBLIC方法触发启动");
// }
// 3. 检查SP中服务配置双重校验确保一致性
SharedPreferences sp = getSharedPreferences(SP_SERVICE_CONFIG, Context.MODE_PRIVATE);
@@ -133,7 +129,7 @@ public class LocationActivity extends AppCompatActivity {
* 缓存服务数据从服务PUBLIC方法获取解决私有字段未知问题
*/
private void cacheServiceData() {
if (mDistanceService == null) {
if (mMainService == null) {
LogUtils.e(TAG, "缓存数据失败:服务实例为空");
mCachedPositionList = new ArrayList<PositionModel>();
mCachedTaskList = new ArrayList<PositionTaskModel>();
@@ -141,8 +137,8 @@ public class LocationActivity extends AppCompatActivity {
}
// 从服务PUBLIC方法获取数据不访问mPositionList而是调用getPositionList()
mCachedPositionList = mDistanceService.getPositionList();
mCachedTaskList = mDistanceService.getPositionTasksList();
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 容错若服务返回null初始化空列表避免空指针
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
@@ -181,7 +177,7 @@ public class LocationActivity extends AppCompatActivity {
@Override
public void onDeleteClick(int position) {
// 校验:服务实例有效+索引合法
if (mDistanceService == null || mCachedPositionList == null) {
if (mMainService == null || mCachedPositionList == null) {
Toast.makeText(LocationActivity.this, "删除失败:服务未就绪", Toast.LENGTH_SHORT).show();
return;
}
@@ -196,7 +192,7 @@ public class LocationActivity extends AppCompatActivity {
String targetPosId = targetPos.getPositionId();
// 2. 调用服务PUBLIC方法删除服务内部处理mPositionList/mTaskList符合封装
mDistanceService.removePosition(targetPosId);
mMainService.removePosition(targetPosId);
LogUtils.d(TAG, "通过服务删除位置ID=" + targetPosId);
// 3. 刷新缓存+Adapter从服务重新获取数据确保与服务一致
@@ -210,7 +206,7 @@ public class LocationActivity extends AppCompatActivity {
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 校验:服务+数据+参数有效
if (mDistanceService == null || mCachedPositionList == null || updatedPos == null) {
if (mMainService == null || mCachedPositionList == null || updatedPos == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或数据异常", Toast.LENGTH_SHORT).show();
return;
}
@@ -220,7 +216,7 @@ public class LocationActivity extends AppCompatActivity {
}
// 1. 调用服务PUBLIC方法更新数据服务内部修改mPositionList
mDistanceService.updatePosition(updatedPos);
mMainService.updatePosition(updatedPos);
LogUtils.d(TAG, "通过服务保存位置ID=" + updatedPos.getPositionId());
// 2. 刷新缓存+Adapter确保本地显示与服务一致
@@ -233,7 +229,7 @@ public class LocationActivity extends AppCompatActivity {
mAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() {
@Override
public void onSavePositionTaskClick(PositionTaskModel newTask) {
if (mDistanceService == null || newTask == null) {
if (mMainService == null || newTask == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或任务数据为空", Toast.LENGTH_SHORT).show();
return;
}
@@ -252,7 +248,7 @@ public class LocationActivity extends AppCompatActivity {
newTaskList.add(newTask);
// 2. 调用服务PUBLIC方法同步任务服务内部更新mTaskList
mDistanceService.syncAllPositionTasks(newTaskList);
mMainService.syncAllPositionTasks(newTaskList);
LogUtils.d(TAG, "通过服务保存任务ID=" + newTask.getTaskId());
// 3. 刷新缓存+Adapter
@@ -274,7 +270,7 @@ public class LocationActivity extends AppCompatActivity {
}
// 2. 校验服务状态
if (mDistanceService == null) {
if (mMainService == null) {
Toast.makeText(this, "新增失败:服务未初始化", Toast.LENGTH_SHORT).show();
return;
}
@@ -289,7 +285,7 @@ public class LocationActivity extends AppCompatActivity {
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算
// 4. 调用服务PUBLIC方法新增服务内部处理mPositionList去重+添加)
mDistanceService.addPosition(newPos);
mMainService.addPosition(newPos);
LogUtils.d(TAG, "通过服务新增位置ID=" + newPos.getPositionId());
// 5. 刷新缓存+Adapter显示新增结果
@@ -301,15 +297,15 @@ public class LocationActivity extends AppCompatActivity {
* 同步GPS位置到服务调用服务syncCurrentGpsPosition(),不访问私有字段)
*/
public void syncGpsPositionToService(PositionModel gpsModel) {
if (mDistanceService == null || gpsModel == null) {
if (mMainService == null || gpsModel == null) {
LogUtils.w(TAG, "同步GPS失败服务未就绪或GPS数据无效");
return;
}
// 调用服务PUBLIC方法同步GPS服务内部更新mCurrentGpsPosition
mDistanceService.syncCurrentGpsPosition(gpsModel);
mMainService.syncCurrentGpsPosition(gpsModel);
// 强制刷新距离通过服务PUBLIC方法触发mPositionList距离计算
mDistanceService.forceRefreshDistance();
mMainService.forceRefreshDistance();
LogUtils.d(TAG, "GPS位置已同步通过服务触发距离计算");
// 刷新缓存+Adapter显示最新距离
@@ -321,14 +317,14 @@ public class LocationActivity extends AppCompatActivity {
* 刷新缓存数据+Adapter从服务重新获取数据避免本地缓存与服务不一致
*/
private void refreshCachedDataAndAdapter() {
if (mDistanceService == null) {
if (mMainService == null) {
LogUtils.w(TAG, "刷新失败:服务实例为空");
return;
}
// 1. 从服务重新获取数据(更新缓存,不访问私有字段)
mCachedPositionList = mDistanceService.getPositionList();
mCachedTaskList = mDistanceService.getPositionTasksList();
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 容错处理
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();

View File

@@ -1,839 +1 @@
package cc.winboll.studio.positions.services;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/30 19:53
* @Describe 位置距离服务:管理数据+定时计算距离+适配AdapterJava 7 兼容)+ GPS信号加载
*/
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.adapters.PositionAdapter;
import cc.winboll.studio.positions.models.AppConfigsModel;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
import cc.winboll.studio.positions.utils.NotificationUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 核心职责:
* 1. 实现 PositionAdapter.DistanceServiceInterface 接口解耦Adapter与服务
* 2. 单例式管理位置/任务数据,提供安全增删改查接口
* 3. 后台单线程定时计算可见位置距离主线程回调更新UI
* 4. 内置GPS信号加载通过LocationManager实时获取位置解决“等待GPS信号”问题
* 5. 服务启动时启动前台通知保活后台GPS功能符合系统规范
* 6. 严格Java 7语法无Lambda/Stream显式迭代器/匿名内部类
*/
public class DistanceRefreshService extends Service {
public static final String TAG = "DistanceRefreshService";
// 服务状态与配置
private boolean isServiceRunning = false;
private final ScheduledExecutorService distanceExecutor; // 定时计算线程池(单线程)
private static final int REFRESH_INTERVAL = 3; // 距离刷新间隔(秒)
// 前台通知相关记录是否已启动前台服务避免重复调用startForeground
private boolean isForegroundServiceStarted = false;
// ---------------------- 新增GPS信号加载核心变量 ----------------------
private LocationManager mLocationManager; // 系统GPS管理类核心
private LocationListener mGpsLocationListener; // GPS位置监听回调
private static final long GPS_UPDATE_INTERVAL = 2000; // GPS位置更新间隔2秒平衡耗电与实时性
private static final float GPS_UPDATE_DISTANCE = 1; // 位置变化超过1米时更新避免频繁回调
private boolean isGpsEnabled = false; // GPS是否已开启系统设置中
private boolean isGpsPermissionGranted = false; // 是否拥有GPS权限
// 核心数据存储(服务内唯一数据源,避免外部直接修改)
private final ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>();
private final ArrayList<PositionTaskModel> mTaskList = new ArrayList<PositionTaskModel>();
private final Set<String> mVisiblePositionIds = new HashSet<String>(); // 可见位置ID优化性能
private PositionModel mCurrentGpsPosition; // 当前GPS位置外部传入/内部GPS获取
// 服务绑定与UI回调
private final IBinder mBinder = new DistanceBinder();
// ---------------------- 构造初始化(线程池+GPS监听器提前创建 ----------------------
// 关键:添加/修改为 PUBLIC 无参构造函数
public DistanceRefreshService() {
// Java 7 显式初始化线程池(单线程,避免并发修改数据)
distanceExecutor = Executors.newSingleThreadScheduledExecutor();
// 初始化GPS位置监听器接收系统GPS位置更新
initGpsLocationListener();
}
// ---------------------- 新增GPS核心逻辑初始化监听+权限/状态检查+启动定位) ----------------------
/**
* 初始化GPS位置监听器系统GPS位置变化时触发实时更新当前位置
*/
private void initGpsLocationListener() {
// Java 7 匿名内部类实现 LocationListener接收GPS位置回调
mGpsLocationListener = new LocationListener() {
// GPS位置发生变化时回调核心更新当前GPS位置
@Override
public void onLocationChanged(Location location) {
if (location != null) {
// 将系统Location对象转为自定义PositionModel适配现有逻辑
PositionModel gpsPos = new PositionModel();
gpsPos.setLatitude(location.getLatitude()); // 纬度
gpsPos.setLongitude(location.getLongitude()); // 经度
gpsPos.setPositionId("CURRENT_GPS_POS"); // 标记为“当前GPS位置”自定义ID
gpsPos.setMemo("实时GPS位置");
// 同步GPS位置到服务触发距离计算+通知更新)
syncCurrentGpsPosition(gpsPos);
LogUtils.d(TAG, "GPS位置更新纬度=" + location.getLatitude() + ",经度=" + location.getLongitude());
}
}
// GPS状态变化时回调如从“搜索中”变为“可用”
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
switch (status) {
case LocationProvider.AVAILABLE:
LogUtils.d(TAG, "GPS状态已就绪可用");
updateNotificationGpsStatus("GPS已就绪正在获取位置...");
break;
case LocationProvider.OUT_OF_SERVICE:
LogUtils.w(TAG, "GPS状态无服务信号弱/无信号)");
updateNotificationGpsStatus("GPS无服务尝试重新连接...");
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
LogUtils.w(TAG, "GPS状态临时不可用如遮挡");
updateNotificationGpsStatus("GPS临时不可用稍后重试...");
break;
}
}
}
// GPS被开启时回调用户在设置中打开GPS
@Override
public void onProviderEnabled(String provider) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = true;
LogUtils.d(TAG, "GPS已开启用户手动打开");
updateNotificationGpsStatus("GPS已开启正在获取位置...");
// 重新启动GPS定位确保获取最新位置
startGpsLocation();
}
}
// GPS被关闭时回调用户在设置中关闭GPS
@Override
public void onProviderDisabled(String provider) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = false;
mCurrentGpsPosition = null; // 清空无效GPS位置
LogUtils.w(TAG, "GPS已关闭用户手动关闭");
updateNotificationGpsStatus("GPS已关闭请在设置中开启");
// 提示用户打开GPS主线程显示Toast
showToastOnMainThread("GPS已关闭无法获取位置请在设置中开启");
}
}
};
}
/**
* 检查GPS权限+系统状态解决“等待GPS信号”的前提权限+GPS开启
* @return true权限+状态都满足false缺少权限或GPS未开启
*/
private boolean checkGpsReady() {
// 1. 检查GPS权限前台精确定位权限必须拥有
isGpsPermissionGranted = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
// 2. 检查GPS是否开启系统设置中
if (mLocationManager == null) {
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
isGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 3. 异常场景处理(权限缺失/GPS未开启
if (!isGpsPermissionGranted) {
LogUtils.e(TAG, "GPS准备失败缺少精确定位权限");
updateNotificationGpsStatus("缺少定位权限无法获取GPS");
showToastOnMainThread("请授予定位权限否则无法获取GPS位置");
return false;
}
if (!isGpsEnabled) {
LogUtils.e(TAG, "GPS准备失败系统GPS未开启");
updateNotificationGpsStatus("GPS未开启请在设置中打开");
showToastOnMainThread("GPS已关闭请在设置中开启以获取位置");
return false;
}
// 权限+状态都满足
LogUtils.d(TAG, "GPS准备就绪权限已获取GPS已开启");
return true;
}
/**
* 启动GPS定位注册监听器开始接收系统GPS位置更新
*/
private void startGpsLocation() {
// 先检查GPS是否就绪避免无效调用
if (!checkGpsReady()) {
return;
}
try {
// 注册GPS监听器指定GPS provider、更新间隔、距离变化阈值
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
GPS_UPDATE_INTERVAL,
GPS_UPDATE_DISTANCE,
mGpsLocationListener,
Looper.getMainLooper() // 在主线程回调(避免跨线程问题)
);
// 额外优化立即获取最近一次缓存的GPS位置避免等待首次定位
Location lastKnownLocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnownLocation != null) {
PositionModel lastGpsPos = new PositionModel();
lastGpsPos.setLatitude(lastKnownLocation.getLatitude());
lastGpsPos.setLongitude(lastKnownLocation.getLongitude());
lastGpsPos.setPositionId("CURRENT_GPS_POS");
syncCurrentGpsPosition(lastGpsPos);
LogUtils.d(TAG, "已获取最近缓存的GPS位置纬度=" + lastKnownLocation.getLatitude());
} else {
LogUtils.d(TAG, "无缓存GPS位置等待实时定位...");
updateNotificationGpsStatus("GPS搜索中请移至开阔地带");
}
} catch (SecurityException e) {
// 异常防护:避免权限突然被回收导致崩溃
LogUtils.e(TAG, "启动GPS定位失败权限异常" + e.getMessage());
isGpsPermissionGranted = false;
updateNotificationGpsStatus("定位权限异常无法获取GPS");
} catch (Exception e) {
LogUtils.e(TAG, "启动GPS定位失败" + e.getMessage());
updateNotificationGpsStatus("GPS启动失败尝试重试...");
}
}
/**
* 停止GPS定位服务销毁/暂停时调用,避免耗电+内存泄漏)
*/
private void stopGpsLocation() {
if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) {
try {
mLocationManager.removeUpdates(mGpsLocationListener);
LogUtils.d(TAG, "GPS定位已停止移除监听器");
} catch (Exception e) {
LogUtils.e(TAG, "停止GPS定位失败" + e.getMessage());
}
}
}
// ---------------------- 新增:辅助方法(通知更新+主线程Toast ----------------------
/**
* 统一更新通知栏的GPS状态文本简化代码避免重复
*/
private void updateNotificationGpsStatus(String statusText) {
if (isForegroundServiceStarted) {
NotificationUtil.updateForegroundServiceStatus(this, statusText);
}
}
/**
* 在主线程显示Toast避免子线程无法显示Toast的问题
*/
private void showToastOnMainThread(final String message) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(DistanceRefreshService.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
// ---------------------- Binder 内部类(供外部绑定服务) ----------------------
public class DistanceBinder extends Binder {
/**
* 外部绑定后获取服务实例(安全暴露服务引用)
*/
public DistanceRefreshService getService() {
return DistanceRefreshService.this;
}
}
// ---------------------- 服务生命周期方法集成GPS启停逻辑 ----------------------
// 服务单例实例(私有静态)
// 1. 单例实例volatile确保多线程可见性
private static volatile DistanceRefreshService sInstance;
// 2. 单独持有ApplicationContext避免内存泄漏+确保非null
private static Context sAppContext;
// 单例PUBLIC方法供外部获取实例确保全局唯一
public static synchronized DistanceRefreshService getInstance() {
// 4. 修复后的单例方法:先判空,再返回(避免空指针)
// 第一步先判断实例是否存在未创建则返回null不强行调用Context
if (sInstance == null) {
// 可选若调用时Service未启动主动启动Service避免后续空指针
Intent intent = new Intent(sAppContext, DistanceRefreshService.class);
sAppContext.startService(intent);
return null; // 或抛明确异常(如"Service未启动"),不触发空指针
}
// 第二步实例存在时确保Context非null双重保险
if (sAppContext == null) {
sAppContext = sInstance.getApplicationContext();
}
return sInstance;
}
@Override
public void onCreate() {
super.onCreate();
sInstance = this; // 此时Service已创建Context非null
// 保存ApplicationContext全局唯一不会因Service销毁而失效
sAppContext = getApplicationContext();
// 初始化GPS管理器提前获取系统服务避免启动时延迟
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LogUtils.d(TAG, "服务 onCreate初始化完成等待启动命令");
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
run();
AppConfigsModel bean = AppConfigsModel.loadBean(DistanceRefreshService.this, AppConfigsModel.class);
boolean isEnableService = (bean == null) ? false : bean.isEnableMainService();
// 服务启用时返回START_STICKY被杀死后尝试重启禁用时返回默认值
return isEnableService ? Service.START_STICKY : super.onStartCommand(intent, flags, startId);
}
public void run() {
// 仅服务未运行时启动(避免重复启动)
if (!isServiceRunning) {
isServiceRunning = true;
mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
initGpsLocationListener();
startDistanceRefreshTask(); // 启动定时距离计算
startForegroundNotification(); // 启动前台通知
startGpsLocation(); // 新增启动GPS定位核心修复解决“等待GPS信号”
LogUtils.d(TAG, "服务 onStartCommand启动成功刷新间隔=" + REFRESH_INTERVAL + "秒,前台通知+GPS已启动");
} else {
LogUtils.w(TAG, "服务 onStartCommand已在运行无需重复启动前台通知" + (isForegroundServiceStarted ? "已启动" : "未启动") + " | GPS" + (isGpsEnabled ? "已开启" : "未开启") + "");
// 异常场景恢复:补全未启动的组件
if (!isForegroundServiceStarted) {
startForegroundNotification();
LogUtils.d(TAG, "服务 run前台通知未启动已恢复");
}
if (isServiceRunning && !isGpsEnabled) {
startGpsLocation();
LogUtils.d(TAG, "服务 runGPS未启动已恢复");
}
}
}
@Override
public IBinder onBind(Intent intent) {
return null; // 按你的业务逻辑返回无绑定需求则保留null
//LogUtils.d(TAG, "服务 onBind外部绑定成功运行状态" + (isServiceRunning ? "是" : "否") + " | GPS状态" + (isGpsEnabled ? "可用" : "不可用") + "");
//return mBinder; // 返回Binder实例供外部获取服务
}
/*@Override
public boolean onUnbind(Intent intent) {
LogUtils.d(TAG, "服务 onUnbind外部解绑清理回调与可见位置");
// 解绑后清理资源,避免内存泄漏
mDistanceReceiver = null;
mVisiblePositionIds.clear();
// 解绑时不停止GPS服务仍在后台运行需持续获取位置
return super.onUnbind(intent);
}*/
@Override
public void onDestroy() {
super.onDestroy();
sInstance = null; // 释放实例,避免内存泄漏
// sAppContext 不清空ApplicationContext全局有效
// 停止所有核心组件+释放资源(避免内存泄漏/耗电)
stopDistanceRefreshTask(); // 停止距离计算
stopGpsLocation(); // 新增停止GPS定位关键避免服务销毁后仍耗电
clearAllData(); // 清空数据
stopForeground(true); // 停止前台服务(移除通知)
// 重置状态标记
isForegroundServiceStarted = false;
isServiceRunning = false;
isGpsEnabled = false;
mLocationManager = null; // 释放GPS管理器引用
LogUtils.d(TAG, "服务 onDestroy销毁完成资源已释放GPS+前台通知+线程池)");
}
// ---------------------- 前台服务通知管理与GPS状态联动优化 ----------------------
/**
* 启动前台服务通知调用NotificationUtils创建通知确保仅启动一次
*/
private void startForegroundNotification() {
// 1. 校验避免重复调用startForeground系统不允许重复启动
if (isForegroundServiceStarted) {
LogUtils.w(TAG, "startForegroundNotification前台通知已启动无需重复执行");
return;
}
try {
// 2. 初始化通知状态文本根据GPS初始状态动态显示避免固定“等待”
String initialStatus;
if (isGpsPermissionGranted && isGpsEnabled) {
initialStatus = "GPS已就绪正在获取位置刷新间隔" + REFRESH_INTERVAL + "秒)";
} else if (!isGpsPermissionGranted) {
initialStatus = "缺少定位权限无法获取GPS位置";
} else {
initialStatus = "GPS未开启请在设置中打开";
}
// 5. 标记前台服务已启动
isForegroundServiceStarted = true;
LogUtils.d(TAG, "startForegroundNotification前台服务通知启动成功初始状态" + initialStatus);
} catch (Exception e) {
// 捕获异常(如上下文失效、通知渠道未创建)
isForegroundServiceStarted = false;
LogUtils.d(TAG, "startForegroundNotification前台通知启动失败" + e);
}
}
// ---------------------- 核心定时距离计算与GPS状态联动优化 ----------------------
/**
- 启动定时距离计算任务延迟1秒开始周期执行
*/
private void startDistanceRefreshTask() {
if (distanceExecutor == null || distanceExecutor.isShutdown()) {
LogUtils.e(TAG, "启动计算失败:线程池未初始化/已关闭");
return;
}distanceExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 优化判断逻辑先检查GPS核心状态再决定是否计算
if (!isGpsPermissionGranted) {
LogUtils.d(TAG, "跳过距离计算:缺少定位权限");
updateNotificationGpsStatus("缺少定位权限,无法计算距离");
return;
}
if (!isGpsEnabled) {
LogUtils.d(TAG, "跳过距离计算GPS未开启");
updateNotificationGpsStatus("GPS未开启无法计算距离");
return;
}// 原有逻辑:服务运行+GPS位置有效+有可见位置才计算
if (isServiceRunning && mCurrentGpsPosition != null && !mVisiblePositionIds.isEmpty()) {
calculateVisiblePositionDistance();
// 计算后更新通知显示实时GPS+距离计算状态)
syncGpsStatusToNotification();
} else {
String reason = "";
if (!isServiceRunning) reason = "服务未运行";
else if (mCurrentGpsPosition == null) reason = "GPS信号弱获取位置中";
else if (mVisiblePositionIds.isEmpty()) reason = "无可见位置(无需计算)";
LogUtils.d(TAG, "跳过距离计算:" + reason);// 针对性更新通知文本(用户明确知道当前状态)
if (isForegroundServiceStarted) {
if (mCurrentGpsPosition == null) {
updateNotificationGpsStatus("GPS信号弱移至开阔地带重试...");
} else if (mVisiblePositionIds.isEmpty()) {
updateNotificationGpsStatus("无可见位置,距离计算暂停");
}
}
}
}
}, 1, REFRESH_INTERVAL, TimeUnit.SECONDS);
}
/**
- 停止定时计算任务(强制关闭线程池)
*/
private void stopDistanceRefreshTask() {
if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
distanceExecutor.shutdownNow();
LogUtils.d(TAG, "距离计算任务已停止");
}
}
/**
- 计算可见位置与GPS的距离Haversine公式后台线程执行
*/
private void calculateVisiblePositionDistance() {
Set tempVisibleIds = new HashSet(mVisiblePositionIds);
if (tempVisibleIds.isEmpty()) return;Iterator posIter = mPositionList.iterator();
while (posIter.hasNext()) {
PositionModel pos = (PositionModel)posIter.next();
String posId = pos.getPositionId();if (tempVisibleIds.contains(posId) && pos.isEnableRealPositionDistance()) {
try {
double distanceM = calculateHaversineDistance(
mCurrentGpsPosition.getLatitude(), mCurrentGpsPosition.getLongitude(),
pos.getLatitude(), pos.getLongitude()
);
pos.setRealPositionDistance(distanceM);
LogUtils.d(TAG, "计算完成位置ID=" + posId + ",距离=" + String.format("%.1f", distanceM) + "");
} catch (Exception e) {
pos.setRealPositionDistance(-1);
LogUtils.e(TAG, "计算失败位置ID=" + posId + "" + e.getMessage());
}
}
}
}
/**
- 主线程回调Adapter更新UI避免跨线程操作UI异常
*/
/*private void notifyDistanceUpdateToUI(final String positionId) {
if (Looper.myLooper() == Looper.getMainLooper()) {
if (mDistanceReceiver != null) {
mDistanceReceiver.onDistanceUpdate(positionId);
}
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (mDistanceReceiver != null) {
mDistanceReceiver.onDistanceUpdate(positionId);
}
}
});
}
}*/
/**
- Haversine公式计算两点间直线距离经纬度→米
*/
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);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状态到前台通知显示实时经纬度+计算状态)
*/
private void syncGpsStatusToNotification() {
if (!isForegroundServiceStarted || mCurrentGpsPosition == null) {
return;
}// 构造详细状态文本(包含经纬度+可见位置数量,用户感知更清晰)
final String gpsStatus = String.format(
"GPS位置北纬%.4f° 东经%.4f° | 计算中(可见位置:%d个",
mCurrentGpsPosition.getLatitude(),
mCurrentGpsPosition.getLongitude(),
mVisiblePositionIds.size()
);if (Looper.myLooper() == Looper.getMainLooper()) {
NotificationUtil.updateForegroundServiceStatus(this, gpsStatus);
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
NotificationUtil.updateForegroundServiceStatus(DistanceRefreshService.this, gpsStatus);
}
});
}
}
// ---------------------- 实现 PositionAdapter.DistanceServiceInterface 接口 ----------------------
public ArrayList getPositionList() {
if (!isServiceRunning) {
LogUtils.w(TAG, "getPositionList服务未运行返回空列表");
return new ArrayList();
}
return new ArrayList(mPositionList);
}
public ArrayList getPositionTasksList() {
if (!isServiceRunning) {
LogUtils.w(TAG, "getPositionTasksList服务未运行返回空列表");
return new ArrayList();
}
return new ArrayList(mTaskList);
}
public void syncCurrentGpsPosition(PositionModel position) {
if (position == null) {
LogUtils.w(TAG, "syncCurrentGpsPositionGPS位置为空同步失败");
return;
}
this.mCurrentGpsPosition = position;
LogUtils.d(TAG, "syncCurrentGpsPosition同步成功纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + "");
// GPS位置同步后立即更新通知避免延迟
if (isForegroundServiceStarted) {
syncGpsStatusToNotification();
}
}
/*public void setOnDistanceUpdateReceiver(PositionAdapter.OnDistanceUpdateReceiver receiver) {
this.mDistanceReceiver = receiver;
LogUtils.d(TAG, "setOnDistanceUpdateReceiver回调接收器已设置" + (receiver != null ? "有效" : "无效") + "");
}*/
public void addVisibleDistanceView(String positionId) {
if (!isServiceRunning || positionId == null) {
LogUtils.w(TAG, "addVisibleDistanceView服务未运行/位置ID无效添加失败");
return;
}
if (mVisiblePositionIds.add(positionId)) {
LogUtils.d(TAG, "addVisibleDistanceView添加成功位置ID=" + positionId + ",当前可见数=" + mVisiblePositionIds.size() + "");
// 新增:添加可见位置后,立即更新通知(显示最新可见数量)
if (isForegroundServiceStarted && mCurrentGpsPosition != null) {
syncGpsStatusToNotification();
}
}
}
public void removeVisibleDistanceView(String positionId) {
if (positionId == null) {
LogUtils.w(TAG, "removeVisibleDistanceView位置ID为空移除失败");
return;
}
if (mVisiblePositionIds.remove(positionId)) {
int remainingCount = mVisiblePositionIds.size();
LogUtils.d(TAG, "removeVisibleDistanceView移除成功位置ID=" + positionId + ",当前可见数=" + remainingCount + "");
// 新增:移除可见位置后,更新通知(同步数量变化)
if (isForegroundServiceStarted && mCurrentGpsPosition != null) {
syncGpsStatusToNotification();
}
}
}
public void clearVisibleDistanceViews() {
mVisiblePositionIds.clear();
LogUtils.d(TAG, "clearVisibleDistanceViews所有可见位置已清空");
// 新增:清空可见位置后,更新通知(提示计算暂停)
if (isForegroundServiceStarted) {
updateNotificationGpsStatus("无可见位置,距离计算暂停");
}
}
// ---------------------- 数据管理接口(修复原有语法错误+优化逻辑) ----------------------
/**
- 获取服务运行状态
*/
public boolean isServiceRunning() {
return isServiceRunning;
}
/**
- 添加位置(修复迭代器泛型缺失问题)
*/
public void addPosition(PositionModel position) {
if (!isServiceRunning || position == null || position.getPositionId() == null) {
LogUtils.w(TAG, "addPosition服务未运行/数据无效,添加失败");
return;
}// 修复显式声明PositionModel泛型避免类型转换警告
boolean isDuplicate = false;
Iterator posIter = mPositionList.iterator();
while (posIter.hasNext()) {
PositionModel existingPos = (PositionModel)posIter.next();
if (position.getPositionId().equals(existingPos.getPositionId())) {
isDuplicate = true;
break;
}
}if (!isDuplicate) {
mPositionList.add(position);
LogUtils.d(TAG, "addPosition添加成功位置ID=" + position.getPositionId() + ",总数=" + mPositionList.size() + "");
} else {
LogUtils.w(TAG, "addPosition位置ID=" + position.getPositionId() + "已存在,添加失败");
}
}
/**
- 删除位置(修复任务删除时的类型转换错误)
*/
public void removePosition(String positionId) {
if (!isServiceRunning || positionId == null) {
LogUtils.w(TAG, "removePosition服务未运行/位置ID无效删除失败");
return;
}// 1. 删除位置
boolean isRemoved = false;
Iterator posIter = mPositionList.iterator();
while (posIter.hasNext()) {
PositionModel pos = (PositionModel)posIter.next();
if (positionId.equals(pos.getPositionId())) {
posIter.remove();
isRemoved = true;
break;
}
}if (isRemoved) {
// 修复任务列表迭代时用PositionTaskModel泛型原错误用PositionModel导致转换失败
Iterator taskIter = mTaskList.iterator();
while (taskIter.hasNext()) {
PositionTaskModel task = (PositionTaskModel)taskIter.next();
if (positionId.equals(task.getPositionId())) {
taskIter.remove();
}
}// 3. 移除可见位置
mVisiblePositionIds.remove(positionId);
LogUtils.d(TAG, "removePosition删除成功位置ID=" + positionId + ",剩余位置数=" + mPositionList.size() + ",剩余任务数=" + mTaskList.size() + "");
} else {
LogUtils.w(TAG, "removePosition位置ID=" + positionId + "不存在,删除失败");
}
}
/**
- 更新位置信息(修复代码格式+迭代器泛型)
*/
public void updatePosition(PositionModel updatedPosition) {
if (!isServiceRunning || updatedPosition == null || updatedPosition.getPositionId() == null) {
LogUtils.w(TAG, "updatePosition服务未运行/数据无效,更新失败");
return;
}boolean isUpdated = false;
Iterator posIter = mPositionList.iterator();
while (posIter.hasNext()) {
PositionModel pos = (PositionModel)posIter.next();
if (updatedPosition.getPositionId().equals(pos.getPositionId())) {
pos.setMemo(updatedPosition.getMemo());
pos.setIsEnableRealPositionDistance(updatedPosition.isEnableRealPositionDistance());
if (!updatedPosition.isEnableRealPositionDistance()) {
pos.setRealPositionDistance(-1);
//notifyDistanceUpdateToUI(pos.getPositionId());
}
isUpdated = true;
break;
}
}if (isUpdated) {
LogUtils.d(TAG, "updatePosition更新成功位置ID=" + updatedPosition.getPositionId() + "");
} else {
LogUtils.w(TAG, "updatePosition位置ID=" + updatedPosition.getPositionId() + "不存在,更新失败");
}
}
/**
- 同步任务列表(修复泛型缺失+代码格式)
*/
public void syncAllPositionTasks(ArrayList tasks) {
if (!isServiceRunning || tasks == null) {
LogUtils.w(TAG, "syncAllPositionTasks服务未运行/任务列表为空,同步失败");
return;
}// 1. 清空旧任务
mTaskList.clear();
// 2. 添加新任务(修复泛型+去重逻辑)
Set taskIdSet = new HashSet();
Iterator taskIter = tasks.iterator();
while (taskIter.hasNext()) {
PositionTaskModel task = (PositionTaskModel)taskIter.next();
if (task != null && task.getTaskId() != null && !taskIdSet.contains(task.getTaskId())) {
taskIdSet.add(task.getTaskId());
mTaskList.add(task);
}
}LogUtils.d(TAG, "syncAllPositionTasks同步成功接收任务数=" + tasks.size() + ",去重后=" + mTaskList.size() + "");
}
/**
- 清空所有数据
*/
public void clearAllData() {
mPositionList.clear();
mTaskList.clear();
mVisiblePositionIds.clear();
mCurrentGpsPosition = null;
LogUtils.d(TAG, "clearAllData所有数据已清空位置/任务/GPS/可见位置)");
}
/**
- 强制刷新距离(优化:刷新后同步通知状态)
*/
public void forceRefreshDistance() {
if (!isServiceRunning) {
LogUtils.w(TAG, "forceRefreshDistance服务未运行刷新失败");
return;
}
if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
// 提交即时任务(不等待定时周期)
distanceExecutor.submit(new Runnable() {
@Override
public void run() {
// 强制刷新前先检查GPS状态避免无效计算
if (isGpsPermissionGranted && isGpsEnabled && mCurrentGpsPosition != null) {
calculateVisiblePositionDistance();
syncGpsStatusToNotification(); // 刷新后同步通知
LogUtils.d(TAG, "forceRefreshDistance即时距离计算完成通知已更新");
} else {
String reason = !isGpsPermissionGranted ? "缺少定位权限" :
(!isGpsEnabled ? "GPS未开启" : "未获取到GPS位置");
LogUtils.w(TAG, "forceRefreshDistance刷新失败原因" + reason);
updateNotificationGpsStatus("强制刷新失败:" + reason);
}
}
});
LogUtils.d(TAG, "forceRefreshDistance已触发即时距离计算请求");
} else {
LogUtils.e(TAG, "forceRefreshDistance线程池已关闭无法触发刷新");
updateNotificationGpsStatus("线程池已关闭,无法执行强制刷新");
}
}
// ---------------------- 补充修复LocationProvider引用缺失问题避免编译错误 ----------------------
// 注原代码中onStatusChanged使用LocationProvider枚举需补充静态导入或显式声明
// 此处通过内部静态类定义解决系统API引用问题兼容Java 7语法
private static class LocationProvider {
public static final int AVAILABLE = 2;
public static final int OUT_OF_SERVICE = 0;
public static final int TEMPORARILY_UNAVAILABLE = 1;
}
// ---------------------- 补充Context引用工具避免服务销毁后Context失效 ----------------------
/*private Context getSafeContext() {
// 服务未销毁时返回自身Context已销毁时返回应用Context避免内存泄漏
if (isDestroyed()) {
return getApplicationContext();
}
return this;
}*/
// 注isDestroyed()为API 17+方法,若需兼容更低版本,可添加版本判断
/*private boolean isDestroyed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return super.isDestroyed();
}
// 低版本通过状态标记间接判断服务销毁时会置为false
return !isServiceRunning && !isForegroundServiceStarted;
}*/
}

View File

@@ -5,30 +5,80 @@ package cc.winboll.studio.positions.services;
* @Date 2024/07/19 14:30:57
* @Describe 应用主要服务组件类
*/
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.R;
import com.hjq.toast.ToastUtils;
import cc.winboll.studio.positions.models.AppConfigsModel;
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.ServiceUtil;
import cc.winboll.studio.positions.utils.NotificationUtil;
import cc.winboll.studio.positions.utils.ServiceUtil;
import com.hjq.toast.ToastUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class MainService extends Service {
public static final String TAG = "MainService";
//MyBinder mMyBinder;
// ---------------------- 新增GPS信号加载核心变量 ----------------------
private LocationManager mLocationManager; // 系统GPS管理类核心
private LocationListener mGpsLocationListener; // GPS位置监听回调
private static final long GPS_UPDATE_INTERVAL = 2000; // GPS位置更新间隔2秒平衡耗电与实时性
private static final float GPS_UPDATE_DISTANCE = 1; // 位置变化超过1米时更新避免频繁回调
private boolean isGpsEnabled = false; // GPS是否已开启系统设置中
private boolean isGpsPermissionGranted = false; // 是否拥有GPS权限
// 核心数据存储(服务内唯一数据源,避免外部直接修改)
private final ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>();
private final ArrayList<PositionTaskModel> mTaskList = new ArrayList<PositionTaskModel>();
private PositionModel mCurrentGpsPosition; // 当前GPS位置外部传入/内部GPS获取
MyServiceConnection mMyServiceConnection;
volatile static boolean _mIsServiceAlive;
volatile static boolean _mIsServiceRunning;
AppConfigsUtil mAppConfigsUtil;
private final ScheduledExecutorService distanceExecutor; // 定时计算线程池(单线程)
private final Set<String> mVisiblePositionIds = new HashSet<String>(); // 可见位置ID优化性能
// ---------------------- 服务生命周期方法集成GPS启停逻辑 ----------------------
// 服务单例实例(私有静态)
// 1. 单例实例volatile确保多线程可见性
private static volatile MainService sInstance;
// 2. 单独持有ApplicationContext避免内存泄漏+确保非null
private static Context sAppContext;
// 单例PUBLIC方法供外部获取实例确保全局唯一
public static synchronized MainService getInstance() {
// 4. 修复后的单例方法:先判空,再返回(避免空指针)
// 第一步先判断实例是否存在未创建则返回null不强行调用Context
if (sInstance == null) {
// 可选若调用时Service未启动主动启动Service避免后续空指针
Intent intent = new Intent(sAppContext, MainService.class);
sAppContext.startService(intent);
return null; // 或抛明确异常(如"Service未启动"),不触发空指针
}
// 第二步实例存在时确保Context非null双重保险
if (sAppContext == null) {
sAppContext = sInstance.getApplicationContext();
}
return sInstance;
}
@Override
public IBinder onBind(Intent intent) {
@@ -40,7 +90,11 @@ public class MainService extends Service {
public void onCreate() {
LogUtils.d(TAG, "onCreate");
super.onCreate();
_mIsServiceAlive = false;
sInstance = this; // 此时Service已创建Context非null
// 保存ApplicationContext全局唯一不会因Service销毁而失效
sAppContext = getApplicationContext();
_mIsServiceRunning = false;
mAppConfigsUtil = AppConfigsUtil.getInstance(this);
//mMyBinder = new MyBinder();
@@ -55,16 +109,14 @@ public class MainService extends Service {
private void run() {
if (mAppConfigsUtil.isEnableMainService(true)) {
if (_mIsServiceAlive == false) {
if (_mIsServiceRunning == false) {
// 设置运行状态
_mIsServiceAlive = true;
_mIsServiceRunning = true;
//LogUtils.d(TAG, "_mIsServiceAlive set to true.");
// 唤醒守护进程
wakeupAndBindAssistant();
// 运行其它服务内容
// 显示前台通知栏
String initialStatus = "[ Positions ] is in Service.";
// 调用NotificationUtils创建前台通知传入动态状态文本
@@ -73,6 +125,11 @@ public class MainService extends Service {
startForeground(NotificationUtil.FOREGROUND_SERVICE_NOTIFICATION_ID,
NotificationUtil.createForegroundServiceNotification(this, initialStatus));
// 运行其它服务内容
mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
initGpsLocationListener();
startGpsLocation(); // 新增启动GPS定位核心修复解决“等待GPS信号”
ToastUtils.show(initialStatus);
LogUtils.i(TAG, initialStatus);
}
@@ -86,8 +143,223 @@ public class MainService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
sInstance = null; // 释放实例,避免内存泄漏
// sAppContext 不清空ApplicationContext全局有效
// 停止所有核心组件+释放资源(避免内存泄漏/耗电)
//stopDistanceRefreshTask(); // 停止距离计算
stopGpsLocation(); // 新增停止GPS定位关键避免服务销毁后仍耗电
clearAllData(); // 清空数据
stopForeground(true); // 停止前台服务(移除通知)
// 重置状态标记
_mIsServiceRunning = false;
isGpsEnabled = false;
mLocationManager = null; // 释放GPS管理器引用
}
public ArrayList<PositionModel> getPositionList() {
return mPositionList;
}
public ArrayList<PositionTaskModel> getPositionTasksList() {
return mTaskList;
}
public void removePosition(String targetPosId) {
LogUtils.d(TAG, "removePosition 未实现");
}
public void updatePosition(PositionModel updatedPos) {
LogUtils.d(TAG, "updatePosition 未实现");
}
public void syncAllPositionTasks(ArrayList<PositionTaskModel> newTaskList) {
LogUtils.d(TAG, "syncAllPositionTasks 未实现");
}
public void addPosition(PositionModel newPos) {
LogUtils.d(TAG, "addPosition 未实现");
}
/**
* 清空所有数据
*/
public void clearAllData() {
mPositionList.clear();
mTaskList.clear();
//mVisiblePositionIds.clear();
mCurrentGpsPosition = null;
LogUtils.d(TAG, "clearAllData所有数据已清空位置/任务/GPS/可见位置)");
}
public void syncCurrentGpsPosition(PositionModel position) {
if (position == null) {
LogUtils.w(TAG, "syncCurrentGpsPositionGPS位置为空同步失败");
return;
}
this.mCurrentGpsPosition = position;
LogUtils.d(TAG, "syncCurrentGpsPosition同步成功纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + "");
// GPS位置同步后立即更新通知避免延迟
if (_mIsServiceRunning) {
syncGpsStatusToNotification();
}
}
/**
- 同步GPS状态到前台通知显示实时经纬度+计算状态)
*/
private void syncGpsStatusToNotification() {
if (!_mIsServiceRunning || mCurrentGpsPosition == null) {
return;
}// 构造详细状态文本(包含经纬度+可见位置数量,用户感知更清晰)
final String gpsStatus = String.format(
"GPS位置北纬%.4f° 东经%.4f° | 计算中(可见位置:%d个",
mCurrentGpsPosition.getLatitude(),
mCurrentGpsPosition.getLongitude(),
666//mVisiblePositionIds.size()
);if (Looper.myLooper() == Looper.getMainLooper()) {
NotificationUtil.updateForegroundServiceStatus(this, gpsStatus);
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
NotificationUtil.updateForegroundServiceStatus(MainService.this, gpsStatus);
}
});
}
}
// ---------------------- 核心定时距离计算与GPS状态联动优化 ----------------------
/**
- 启动定时距离计算任务延迟1秒开始周期执行
*/
// private void startDistanceRefreshTask() {
// if (distanceExecutor == null || distanceExecutor.isShutdown()) {
// LogUtils.e(TAG, "启动计算失败:线程池未初始化/已关闭");
// return;
// }distanceExecutor.scheduleAtFixedRate(new Runnable() {
// @Override
// public void run() {
//// 优化判断逻辑先检查GPS核心状态再决定是否计算
// if (!isGpsPermissionGranted) {
// LogUtils.d(TAG, "跳过距离计算:缺少定位权限");
// updateNotificationGpsStatus("缺少定位权限,无法计算距离");
// return;
// }
// if (!isGpsEnabled) {
// LogUtils.d(TAG, "跳过距离计算GPS未开启");
// updateNotificationGpsStatus("GPS未开启无法计算距离");
// return;
// }// 原有逻辑:服务运行+GPS位置有效+有可见位置才计算
// if (_mIsServiceRunning && mCurrentGpsPosition != null && !mVisiblePositionIds.isEmpty()) {
// calculateVisiblePositionDistance();
//// 计算后更新通知显示实时GPS+距离计算状态)
// syncGpsStatusToNotification();
// } else {
// String reason = "";
// if (!_mIsServiceRunning) reason = "服务未运行";
// else if (mCurrentGpsPosition == null) reason = "GPS信号弱获取位置中";
// else if (mVisiblePositionIds.isEmpty()) reason = "无可见位置(无需计算)";
// LogUtils.d(TAG, "跳过距离计算:" + reason);// 针对性更新通知文本(用户明确知道当前状态)
// if (isForegroundServiceStarted) {
// if (mCurrentGpsPosition == null) {
// updateNotificationGpsStatus("GPS信号弱移至开阔地带重试...");
// } else if (mVisiblePositionIds.isEmpty()) {
// updateNotificationGpsStatus("无可见位置,距离计算暂停");
// }
// }
// }
// }
// }, 1, REFRESH_INTERVAL, TimeUnit.SECONDS);
// }
//
// /**
// - 停止定时计算任务(强制关闭线程池)
// */
// private void stopDistanceRefreshTask() {
// if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
// distanceExecutor.shutdownNow();
// LogUtils.d(TAG, "距离计算任务已停止");
// }
// }
/**
- 计算可见位置与GPS的距离Haversine公式后台线程执行
*/
private void calculateVisiblePositionDistance() {
Set tempVisibleIds = new HashSet(mVisiblePositionIds);
if (tempVisibleIds.isEmpty()) return;Iterator posIter = mPositionList.iterator();
while (posIter.hasNext()) {
PositionModel pos = (PositionModel)posIter.next();
String posId = pos.getPositionId();if (tempVisibleIds.contains(posId) && pos.isEnableRealPositionDistance()) {
try {
double distanceM = calculateHaversineDistance(
mCurrentGpsPosition.getLatitude(), mCurrentGpsPosition.getLongitude(),
pos.getLatitude(), pos.getLongitude()
);
pos.setRealPositionDistance(distanceM);
LogUtils.d(TAG, "计算完成位置ID=" + posId + ",距离=" + String.format("%.1f", distanceM) + "");
} catch (Exception e) {
pos.setRealPositionDistance(-1);
LogUtils.e(TAG, "计算失败位置ID=" + posId + "" + e.getMessage());
}
}
}
}
/**
- Haversine公式计算两点间直线距离经纬度→米
*/
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);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 forceRefreshDistance() {
if (!_mIsServiceRunning) {
LogUtils.w(TAG, "forceRefreshDistance服务未运行刷新失败");
return;
}
if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
// 提交即时任务(不等待定时周期)
distanceExecutor.submit(new Runnable() {
@Override
public void run() {
// 强制刷新前先检查GPS状态避免无效计算
if (isGpsPermissionGranted && isGpsEnabled && mCurrentGpsPosition != null) {
calculateVisiblePositionDistance();
syncGpsStatusToNotification(); // 刷新后同步通知
LogUtils.d(TAG, "forceRefreshDistance即时距离计算完成通知已更新");
} else {
String reason = !isGpsPermissionGranted ? "缺少定位权限" :
(!isGpsEnabled ? "GPS未开启" : "未获取到GPS位置");
LogUtils.w(TAG, "forceRefreshDistance刷新失败原因" + reason);
updateNotificationGpsStatus("强制刷新失败:" + reason);
}
}
});
LogUtils.d(TAG, "forceRefreshDistance已触发即时距离计算请求");
} else {
LogUtils.e(TAG, "forceRefreshDistance线程池已关闭无法触发刷新");
updateNotificationGpsStatus("线程池已关闭,无法执行强制刷新");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//return super.onStartCommand(intent, flags, startId);
@@ -122,4 +394,191 @@ public class MainService extends Service {
bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
}
// ---------------------- 构造初始化(线程池+GPS监听器提前创建 ----------------------
// 关键:添加/修改为 PUBLIC 无参构造函数
public MainService() {
// Java 7 显式初始化线程池(单线程,避免并发修改数据)
distanceExecutor = Executors.newSingleThreadScheduledExecutor();
// 初始化GPS位置监听器接收系统GPS位置更新
initGpsLocationListener();
}
// ---------------------- 新增GPS核心逻辑初始化监听+权限/状态检查+启动定位) ----------------------
/**
* 初始化GPS位置监听器系统GPS位置变化时触发实时更新当前位置
*/
private void initGpsLocationListener() {
// Java 7 匿名内部类实现 LocationListener接收GPS位置回调
mGpsLocationListener = new LocationListener() {
// GPS位置发生变化时回调核心更新当前GPS位置
@Override
public void onLocationChanged(Location location) {
if (location != null) {
// 将系统Location对象转为自定义PositionModel适配现有逻辑
PositionModel gpsPos = new PositionModel();
gpsPos.setLatitude(location.getLatitude()); // 纬度
gpsPos.setLongitude(location.getLongitude()); // 经度
gpsPos.setPositionId("CURRENT_GPS_POS"); // 标记为“当前GPS位置”自定义ID
gpsPos.setMemo("实时GPS位置");
// 同步GPS位置到服务触发距离计算+通知更新)
syncCurrentGpsPosition(gpsPos);
LogUtils.d(TAG, "GPS位置更新纬度=" + location.getLatitude() + ",经度=" + location.getLongitude());
}
}
// GPS状态变化时回调如从“搜索中”变为“可用”
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
switch (status) {
case LocationProvider.AVAILABLE:
LogUtils.d(TAG, "GPS状态已就绪可用");
updateNotificationGpsStatus("GPS已就绪正在获取位置...");
break;
case LocationProvider.OUT_OF_SERVICE:
LogUtils.w(TAG, "GPS状态无服务信号弱/无信号)");
updateNotificationGpsStatus("GPS无服务尝试重新连接...");
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
LogUtils.w(TAG, "GPS状态临时不可用如遮挡");
updateNotificationGpsStatus("GPS临时不可用稍后重试...");
break;
}
}
}
// GPS被开启时回调用户在设置中打开GPS
@Override
public void onProviderEnabled(String provider) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = true;
LogUtils.d(TAG, "GPS已开启用户手动打开");
updateNotificationGpsStatus("GPS已开启正在获取位置...");
// 重新启动GPS定位确保获取最新位置
startGpsLocation();
}
}
// GPS被关闭时回调用户在设置中关闭GPS
@Override
public void onProviderDisabled(String provider) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = false;
mCurrentGpsPosition = null; // 清空无效GPS位置
LogUtils.w(TAG, "GPS已关闭用户手动关闭");
updateNotificationGpsStatus("GPS已关闭请在设置中开启");
// 提示用户打开GPS主线程显示Toast
//showToastOnMainThread("GPS已关闭无法获取位置请在设置中开启");
}
}
};
}
/**
* 检查GPS权限+系统状态解决“等待GPS信号”的前提权限+GPS开启
* @return true权限+状态都满足false缺少权限或GPS未开启
*/
private boolean checkGpsReady() {
// 1. 检查GPS权限前台精确定位权限必须拥有
isGpsPermissionGranted = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
// 2. 检查GPS是否开启系统设置中
if (mLocationManager == null) {
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
isGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
// 3. 异常场景处理(权限缺失/GPS未开启
if (!isGpsPermissionGranted) {
LogUtils.e(TAG, "GPS准备失败缺少精确定位权限");
updateNotificationGpsStatus("缺少定位权限无法获取GPS");
ToastUtils.show("请授予定位权限否则无法获取GPS位置");
//showToastOnMainThread("请授予定位权限否则无法获取GPS位置");
return false;
}
if (!isGpsEnabled) {
LogUtils.e(TAG, "GPS准备失败系统GPS未开启");
ToastUtils.show("GPS已关闭请在设置中开启以获取位置");
updateNotificationGpsStatus("GPS未开启请在设置中打开");
//showToastOnMainThread("GPS已关闭请在设置中开启以获取位置");
return false;
}
// 权限+状态都满足
LogUtils.d(TAG, "GPS准备就绪权限已获取GPS已开启");
return true;
}
/**
* 启动GPS定位注册监听器开始接收系统GPS位置更新
*/
private void startGpsLocation() {
// 先检查GPS是否就绪避免无效调用
if (!checkGpsReady()) {
return;
}
try {
// 注册GPS监听器指定GPS provider、更新间隔、距离变化阈值
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
GPS_UPDATE_INTERVAL,
GPS_UPDATE_DISTANCE,
mGpsLocationListener,
Looper.getMainLooper() // 在主线程回调(避免跨线程问题)
);
// 额外优化立即获取最近一次缓存的GPS位置避免等待首次定位
Location lastKnownLocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnownLocation != null) {
PositionModel lastGpsPos = new PositionModel();
lastGpsPos.setLatitude(lastKnownLocation.getLatitude());
lastGpsPos.setLongitude(lastKnownLocation.getLongitude());
lastGpsPos.setPositionId("CURRENT_GPS_POS");
syncCurrentGpsPosition(lastGpsPos);
LogUtils.d(TAG, "已获取最近缓存的GPS位置纬度=" + lastKnownLocation.getLatitude());
} else {
LogUtils.d(TAG, "无缓存GPS位置等待实时定位...");
updateNotificationGpsStatus("GPS搜索中请移至开阔地带");
}
} catch (SecurityException e) {
// 异常防护:避免权限突然被回收导致崩溃
LogUtils.e(TAG, "启动GPS定位失败权限异常" + e.getMessage());
isGpsPermissionGranted = false;
updateNotificationGpsStatus("定位权限异常无法获取GPS");
} catch (Exception e) {
LogUtils.e(TAG, "启动GPS定位失败" + e.getMessage());
updateNotificationGpsStatus("GPS启动失败尝试重试...");
}
}
/**
* 停止GPS定位服务销毁/暂停时调用,避免耗电+内存泄漏)
*/
private void stopGpsLocation() {
if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) {
try {
mLocationManager.removeUpdates(mGpsLocationListener);
LogUtils.d(TAG, "GPS定位已停止移除监听器");
} catch (Exception e) {
LogUtils.e(TAG, "停止GPS定位失败" + e.getMessage());
}
}
}
// ---------------------- 新增:辅助方法(通知更新+主线程Toast ----------------------
/**
* 统一更新通知栏的GPS状态文本简化代码避免重复
*/
private void updateNotificationGpsStatus(String statusText) {
if (_mIsServiceRunning) {
NotificationUtil.updateForegroundServiceStatus(this, statusText);
}
}
}