20251002_141326_715

This commit is contained in:
ZhanGSKen
2025-10-02 14:13:31 +08:00
parent 1ba19d8f77
commit 1f57ac5401
3 changed files with 630 additions and 485 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Oct 02 03:09:35 GMT 2025
#Thu Oct 02 06:11:17 GMT 2025
stageCount=8
libraryProject=
baseVersion=15.0
publishVersion=15.0.7
buildCount=14
buildCount=19
baseBetaVersion=15.0.8

View File

@@ -3,11 +3,11 @@ package cc.winboll.studio.positions.activities;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/29 18:22
* @Describe 位置列表页面(直连服务数据+移除绑定,兼容服务私有字段
* @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期
*/
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -27,10 +27,10 @@ import cc.winboll.studio.positions.utils.AppConfigsUtil;
/**
* 核心调整说明:
* 1. 移除服务绑定逻辑但不直接访问服务私有字段解决mPositionList未知问题
* 2. 通过服务提供的 PUBLIC 方法如getPositionList、addPosition操作数据符合封装规范
* 3. 保留“直连服务实例”的核心需求,避免绑定,但尊重服务数据私有性
* 4. 兼容Java 7语法无Lambda、显式类型转换
* 1. 新增 MainService.GpsUpdateListener 实现接收实时GPS数据经纬度+状态
* 2. 完善 MainService 引用逻辑:修复实例获取可靠性(启动+延迟初始化)、补全反注册
* 3. 新增GPS数据应用同步GPS到服务、刷新位置距离、显示GPS状态
* 4. 强化生命周期管理页面销毁时反注册GPS监听避免内存泄漏
*/
public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "LocationActivity";
@@ -41,11 +41,14 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
// 页面核心控件与变量
private RecyclerView mRecyclerView;
private PositionAdapter mAdapter;
// 直连服务实例(替代绑定,通过单例/全局初始化获取
// 直连服务实例(通过单例获取,全局唯一
private MainService mMainService;
// 缓存服务数据从服务PUBLIC方法获取避免重复调用
private ArrayList<PositionModel> mCachedPositionList;
private ArrayList<PositionTaskModel> mCachedTaskList;
// ---------------------- 新增GPS监听核心变量 ----------------------
private MainService.GpsUpdateListener mGpsUpdateListener; // GPS监听实例
private PositionModel mCurrentGpsPos; // 缓存当前GPS位置供页面使用
@Override
@@ -58,7 +61,7 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
return TAG;
}
// ---------------------- 页面生命周期(简化资源管理,无服务绑定 ----------------------
// ---------------------- 页面生命周期(新增GPS监听注册/反注册 ----------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -66,83 +69,201 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
LogUtils.d(TAG, "onCreate");
// 1. 初始化服务实例(通过单例/全局方式避免直接new导致数据重置
initServiceInstance();
// 2. 检查服务状态(未运行/未初始化则提示关闭
// 1. 初始化GPS监听提前创建避免空指针
initGpsUpdateListener();
// 2. 启动+初始化MainService确保服务已创建实例可获取
startAndInitMainService();
// 3. 检查服务状态(未运行则启动,配置未启用则提示)
checkServiceStatus();
// 3. 初始化RecyclerView布局+性能优化)
// 4. 初始化RecyclerView布局+性能优化)
initRecyclerViewConfig();
// 4. 缓存服务数据从服务PUBLIC方法获取不访问私有字段
// 5. 缓存服务数据从服务PUBLIC方法获取不访问私有字段
cacheServiceData();
// 5. 初始化Adapter传缓存数据,而非直接访问服务私有字段
// 6. 初始化Adapter传缓存数据+当前GPS位置支持距离计算显示
initAdapter();
}
@Override
protected void onResume() {
super.onResume();
// 页面可见时注册GPS监听确保能接收实时数据
registerGpsListener();
// 刷新数据(避免页面切后台后数据不一致)
if (mAdapter != null) {
refreshCachedDataAndAdapter();
}
}
@Override
protected void onPause() {
super.onPause();
// 页面不可见时反注册GPS监听减少资源占用避免内存泄漏
unregisterGpsListener();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 1. 清理Adapter资源
// 1. 最终反注册GPS监听双重保险避免遗漏
unregisterGpsListener();
// 2. 清理Adapter资源
if (mAdapter != null) {
mAdapter.release();
mAdapter = null;
}
// 2. 置空服务实例+缓存数据帮助GC回收
// 3. 置空服务实例+缓存数据+GPS数据帮助GC回收
mMainService = null;
mCachedPositionList = null;
mCachedTaskList = null;
mCurrentGpsPos = null;
mGpsUpdateListener = null;
mRecyclerView = null;
LogUtils.d(TAG, "页面销毁:已清理服务实例及缓存数据");
LogUtils.d(TAG, "页面销毁:已清理所有资源(服务/缓存/GPS监听");
}
// ---------------------- 核心初始化(服务实例+数据缓存+状态检查 ----------------------
// ---------------------- 新增GPS监听初始化+注册/反注册(核心适配逻辑 ----------------------
/**
* 初始化服务实例(关键:通过单例获取,确保全局唯一,避免数据重复)
* 注需在DistanceRefreshService中实现单例getInstance()方法)
* 初始化GPS监听实现MainService.GpsUpdateListener接收实时GPS数据
*/
private void initServiceInstance() {
// 步骤1先启动Service确保Service已创建实例能被初始化
private void initGpsUpdateListener() {
mGpsUpdateListener = new MainService.GpsUpdateListener() {
// 回调1GPS位置更新实时接收经纬度更新缓存+刷新Adapter
@Override
public void onGpsPositionUpdated(PositionModel currentGpsPos) {
if (currentGpsPos == null) {
LogUtils.w(TAG, "GPS位置更新数据为空");
return;
}
// 缓存当前GPS位置供页面其他逻辑使用
mCurrentGpsPos = currentGpsPos;
LogUtils.d(TAG, String.format("收到GPS更新纬度=%.4f,经度=%.4f"
, currentGpsPos.getLatitude(), currentGpsPos.getLongitude()));
// 步骤2初始化Service实例延迟100-200ms确保Service onCreate执行完成
// 或在startService后通过ServiceConnection绑定获取实例更可靠
//initServiceInstance();
// 1. 同步GPS位置到MainService确保服务数据与页面一致触发距离计算
if (mMainService != null) {
mMainService.syncCurrentGpsPosition(currentGpsPos);
// 2. 强制刷新距离计算+Adapter显示最新距离
mMainService.forceRefreshDistance();
refreshCachedDataAndAdapter();
}
// 方式通过服务PUBLIC单例方法获取实例不直接new避免私有数据初始化重复
mMainService = MainService.getInstance(LocationActivity.this);
// 3. 可选显示GPS位置Toast提示如调试场景
// ToastUtils.show("GPS更新" + currentGpsPos.getLatitude() + "," + currentGpsPos.getLongitude());
}
// 容错:若单例未初始化(如首次启动),提示并处理
if (mMainService == null) {
LogUtils.e(TAG, "服务实例初始化失败:单例未创建");
Toast.makeText(this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
finish();
}
// 回调2GPS状态变化如开启/关闭、信号弱,提示用户)
@Override
public void onGpsStatusChanged(String status) {
if (status == null) return;
LogUtils.d(TAG, "GPS状态变化" + status);
// 显示GPS状态可通过TextView在页面上展示此处用Toast示例
if (status.contains("未开启") || status.contains("权限") || status.contains("失败")) {
// 异常状态:弹出提示引导用户处理
ToastUtils.show("GPS提示" + status);
}
}
};
}
/**
* 注册GPS监听调用MainService的PUBLIC方法绑定监听
*/
private void registerGpsListener() {
if (mMainService == null || mGpsUpdateListener == null) {
LogUtils.w(TAG, "GPS监听注册失败服务未初始化或监听未创建");
return;
}
// 调用MainService的registerGpsUpdateListener方法注册
mMainService.registerGpsUpdateListener(mGpsUpdateListener);
LogUtils.d(TAG, "GPS监听已注册");
}
/**
* 检查服务状态替代原绑定检查通过服务PUBLIC方法判断
* 反注册GPS监听调用MainService的PUBLIC方法解绑监听核心防内存泄漏
*/
private void unregisterGpsListener() {
if (mMainService == null || mGpsUpdateListener == null) {
LogUtils.w(TAG, "GPS监听反注册失败服务未初始化或监听未创建");
return;
}
// 调用MainService的unregisterGpsUpdateListener方法反注册
mMainService.unregisterGpsUpdateListener(mGpsUpdateListener);
LogUtils.d(TAG, "GPS监听已反注册");
}
// ---------------------- 完善MainService 引用逻辑(修复实例获取可靠性) ----------------------
/**
* 启动+初始化MainService先启动服务再延迟获取实例避免服务未创建导致null
*/
private void startAndInitMainService() {
// 步骤1先启动MainService确保服务进程已启动onCreate执行
Intent serviceIntent = new Intent(this, MainService.class);
startService(serviceIntent);
LogUtils.d(TAG, "已触发MainService启动");
// 步骤2延迟200ms获取实例等待服务onCreate初始化完成避免返回null
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
mMainService = MainService.getInstance(LocationActivity.this);
if (mMainService == null) {
// 容错1秒后再次尝试获取应对服务启动慢的场景
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
mMainService = MainService.getInstance(LocationActivity.this);
if (mMainService == null) {
LogUtils.e(TAG, "MainService实例获取失败重试后仍失败");
Toast.makeText(LocationActivity.this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
finish();
} else {
LogUtils.d(TAG, "MainService实例重试获取成功");
// 实例获取成功后补注册GPS监听+刷新数据
registerGpsListener();
refreshCachedDataAndAdapter();
}
}
}, 1000);
} else {
LogUtils.d(TAG, "MainService实例获取成功");
}
}
}, 200);
}
/**
* 检查服务状态通过服务PUBLIC方法不访问私有字段
*/
private void checkServiceStatus() {
LogUtils.d(TAG, "checkServiceStatus");
// 1. 服务实例未初始化
// 1. 服务实例未初始化(等待延迟获取,不重复处理)
if (mMainService == null) {
return; // 已在initServiceInstance中处理此处避免重复finish
LogUtils.d(TAG, "服务实例未就绪,等待初始化...");
return;
}
// 2. 检查服务运行状态(通过服务PUBLIC方法isServiceRunning()获取,不访问私有字段
// 2. 检查服务运行状态(调用isServiceRunning()
if (!mMainService.isServiceRunning()) {
// 服务未运行:手动触发启动调用服务PUBLIC方法run()
// 服务未运行:调用run()启动
mMainService.run();
LogUtils.d(TAG, "服务未运行,已通过PUBLIC方法触发启动");
LogUtils.d(TAG, "服务未运行,已通过run()触发启动");
}
// 3. 检查SP中服务配置(双重校验,确保一致性
if (!AppConfigsUtil.getInstance(LocationActivity.this).isEnableMainService(true)) {
// 3. 检查服务配置是否启用调用AppConfigsUtil的PUBLIC方法
if (!AppConfigsUtil.getInstance(this).isEnableMainService(true)) {
Toast.makeText(this, "位置服务配置未启用,数据可能无法更新", Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, "位置服务配置未启用");
}
// 4. 新增检查GPS状态通过服务逻辑间接判断提示用户
if (mCurrentGpsPos == null && mMainService.isServiceRunning()) {
ToastUtils.show("等待GPS信号...请确保GPS已开启且权限已授予");
}
}
// ---------------------- 原有逻辑完善适配GPS数据同步服务交互 ----------------------
/**
* 缓存服务数据(从服务PUBLIC方法获取解决私有字段未知问题
* 缓存服务数据(新增GPS位置缓存供Adapter使用
*/
private void cacheServiceData() {
if (mMainService == null) {
@@ -153,11 +274,17 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
return;
}
// 从服务PUBLIC方法获取数据不访问mPositionList而是调用getPositionList()
// 从服务PUBLIC方法获取核心数据
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 新增从服务同步最新GPS位置避免页面缓存与服务不一致
if (mCurrentGpsPos == null) {
// 若页面未收到GPS回调从服务获取最近位置需MainService新增getCurrentGpsPosition()方法)
// 【注意】需在MainService中添加PUBLIC方法返回mCurrentGpsPosition
// mCurrentGpsPos = mMainService.getCurrentGpsPosition();
}
// 容错:若服务返回null初始化空列表避免空指针
// 容错:初始化空列表避免空指针
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
@@ -166,7 +293,7 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
}
/**
* 初始化RecyclerView留原性能优化
* 初始化RecyclerView持原有逻辑,无修改
*/
private void initRecyclerViewConfig() {
mRecyclerView = (RecyclerView) findViewById(R.id.rv_position_list);
@@ -176,9 +303,8 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
mRecyclerView.setHasFixedSize(true); // 固定大小,优化绘制
}
// ---------------------- Adapter初始化与数据交互全通过服务PUBLIC方法 ----------------------
/**
* 初始化Adapter通过缓存数据初始化,数据更新时同步调用服务方法
* 初始化Adapter新增传入GPS位置支持Adapter显示距离信息
*/
private void initAdapter() {
LogUtils.d(TAG, "initAdapter");
@@ -187,99 +313,108 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
return;
}
// 1. 初始化Adapter传缓存数据+服务实例Adapter数据从缓存取操作通过服务
// 1. 初始化Adapter缓存数据+当前GPS位置供Adapter计算/显示距离
mAdapter = new PositionAdapter(this, mCachedPositionList, mCachedTaskList);
mRecyclerView.setAdapter(mAdapter);
// 2. 删除回调通过服务PUBLIC方法removePosition()删除,不直接操作私有字段
// 2. 删除回调通过服务PUBLIC方法removePosition()删除
mAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
@Override
public void onDeleteClick(int position) {
// 校验:服务实例有效+索引合法
if (mMainService == null || mCachedPositionList == null) {
Toast.makeText(LocationActivity.this, "删除失败:服务未就绪", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "删除失败:索引无效(" + position + "");
Toast.makeText(LocationActivity.this, "删除失败:数据位置异常", Toast.LENGTH_SHORT).show();
return;
}
@Override
public void onDeleteClick(int position) {
if (mMainService == null || mCachedPositionList == null) {
Toast.makeText(LocationActivity.this, "删除失败:服务未就绪", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "删除失败:索引无效(" + position + "");
Toast.makeText(LocationActivity.this, "删除失败:数据位置异常", Toast.LENGTH_SHORT).show();
return;
}
// 1. 获取要删除的位置(从缓存取,与服务数据一致)
PositionModel targetPos = mCachedPositionList.get(position);
String targetPosId = targetPos.getPositionId();
PositionModel targetPos = mCachedPositionList.get(position);
String targetPosId = targetPos.getPositionId();
// 调用服务PUBLIC方法删除
mMainService.removePosition(targetPosId);
LogUtils.d(TAG, "通过服务删除位置ID=" + targetPosId);
// 2. 调用服务PUBLIC方法删除服务内部处理mPositionList/mTaskList符合封装
mMainService.removePosition(targetPosId);
LogUtils.d(TAG, "通过服务删除位置ID=" + targetPosId);
// 3. 刷新缓存+Adapter从服务重新获取数据确保与服务一致
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置已删除(含关联任务)", Toast.LENGTH_SHORT).show();
}
});
// 刷新缓存+Adapter包含GPS距离数据
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置已删除(含关联任务)", Toast.LENGTH_SHORT).show();
}
});
// 3. 位置保存回调通过服务PUBLIC方法updatePosition()更新
mAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 校验:服务+数据+参数有效
if (mMainService == null || mCachedPositionList == null || updatedPos == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或数据异常", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "保存失败:位置索引无效");
return;
}
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
if (mMainService == null || mCachedPositionList == null || updatedPos == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或数据异常", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "保存失败:位置索引无效");
return;
}
// 1. 调用服务PUBLIC方法更新数据服务内部修改mPositionList
mMainService.updatePosition(updatedPos);
LogUtils.d(TAG, "通过服务保存位置ID=" + updatedPos.getPositionId());
// 调用服务PUBLIC方法更新
mMainService.updatePosition(updatedPos);
LogUtils.d(TAG, "通过服务保存位置ID=" + updatedPos.getPositionId());
// 2. 刷新缓存+Adapter确保本地显示与服务一致
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show();
}
});
// 刷新缓存+Adapter更新距离显示
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show();
}
});
// 4. 任务保存回调通过服务PUBLIC方法syncAllPositionTasks()同步
mAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() {
@Override
public void onSavePositionTaskClick(PositionTaskModel newTask) {
if (mMainService == null || newTask == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或任务数据为空", Toast.LENGTH_SHORT).show();
return;
}
@Override
public void onSavePositionTaskClick(PositionTaskModel newTask) {
if (mMainService == null || newTask == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或任务数据为空", Toast.LENGTH_SHORT).show();
return;
}
// 1. 构建新任务列表(缓存任务+新任务,去重)
ArrayList<PositionTaskModel> newTaskList = new ArrayList<PositionTaskModel>(mCachedTaskList);
// 先移除同ID旧任务避免重复
for (int i = 0; i < newTaskList.size(); i++) {
PositionTaskModel oldTask = newTaskList.get(i);
if (newTask.getTaskId().equals(oldTask.getTaskId())) {
newTaskList.remove(i);
break;
}
}
// 添加新任务
newTaskList.add(newTask);
// 构建新任务列表(缓存任务+新任务,去重)
ArrayList<PositionTaskModel> newTaskList = new ArrayList<PositionTaskModel>(mCachedTaskList);
// 先移除同ID旧任务避免重复
for (int i = 0; i < newTaskList.size(); i++) {
PositionTaskModel oldTask = newTaskList.get(i);
if (newTask.getTaskId().equals(oldTask.getTaskId())) {
newTaskList.remove(i);
break;
}
}
// 添加新任务
newTaskList.add(newTask);
// 2. 调用服务PUBLIC方法同步任务服务内部更新mTaskList
mMainService.syncAllPositionTasks(newTaskList);
LogUtils.d(TAG, "通过服务保存任务ID=" + newTask.getTaskId());
// 调用服务PUBLIC方法同步任务
mMainService.syncAllPositionTasks(newTaskList);
LogUtils.d(TAG, "通过服务保存任务ID=" + newTask.getTaskId());
// 3. 刷新缓存+Adapter
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show();
}
});
// 刷新缓存+Adapter
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show();
}
});
// 新增GPS同步按钮回调手动触发GPS位置同步可选供页面主动调用
// mAdapter.setOnSyncGpsClickListener(new PositionAdapter.OnSyncGpsClickListener() {
// @Override
// public void onSyncGpsClick() {
// if (mMainService == null || mCurrentGpsPos == null) {
// Toast.makeText(LocationActivity.this, "同步失败GPS未获取到位置", Toast.LENGTH_SHORT).show();
// return;
// }
// refreshCachedDataAndAdapter();
// Toast.makeText(LocationActivity.this, "已同步最新GPS位置距离已更新", Toast.LENGTH_SHORT).show();
// }
// });
}
// ---------------------- 页面交互(新增位置、同步GPS全走服务PUBLIC方法 ----------------------
// ---------------------- 页面交互(新增位置逻辑保留适配GPS数据 ----------------------
/**
* 新增位置调用服务addPosition()不直接操作mPositionList
* 新增位置调用服务addPosition()可选用当前GPS位置初始化新位置
*/
public void addNewPosition(View view) {
// 1. 隐藏软键盘
@@ -294,46 +429,35 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
return;
}
// 3. 创建新位置模型
// 3. 创建新位置模型优化优先用当前GPS位置初始化无则用默认值
PositionModel newPos = new PositionModel();
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一IDPositionModel实现)
newPos.setLongitude(116.404267); // 示例经度(北京
newPos.setLatitude(39.915119); // 示例纬度
newPos.setMemo("测试位置(可编辑备注)");
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一IDPositionModel实现
// 新增用当前GPS位置初始化新位置提升用户体验无需手动输入经纬度
if (mCurrentGpsPos != null) {
newPos.setLongitude(mCurrentGpsPos.getLongitude());
newPos.setLatitude(mCurrentGpsPos.getLatitude());
newPos.setMemo("当前GPS位置可编辑");
} else {
// 无GPS位置时用默认值
newPos.setLongitude(116.404267); // 北京经度
newPos.setLatitude(39.915119); // 北京纬度
newPos.setMemo("默认位置(可编辑备注)");
}
newPos.setIsSimpleView(true); // 默认简单视图
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算依赖GPS
// 4. 调用服务PUBLIC方法新增服务内部处理mPositionList去重+添加)
// 4. 调用服务PUBLIC方法新增
mMainService.addPosition(newPos);
LogUtils.d(TAG, "通过服务新增位置ID=" + newPos.getPositionId());
LogUtils.d(TAG, "通过服务新增位置ID=" + newPos.getPositionId() + ",纬度=" + newPos.getLatitude());
// 5. 刷新缓存+Adapter显示新增结果
// 5. 刷新缓存+Adapter显示新增结果+距离
refreshCachedDataAndAdapter();
Toast.makeText(this, "新增位置成功(已启用距离计算)", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "新增位置成功(已启用GPS距离计算)", Toast.LENGTH_SHORT).show();
}
// ---------------------- 辅助方法完善数据刷新逻辑包含GPS距离 ----------------------
/**
* 同步GPS位置到服务调用服务syncCurrentGpsPosition(),不访问私有字段
*/
// public void syncGpsPositionToService(PositionModel gpsModel) {
// if (mMainService == null || gpsModel == null) {
// LogUtils.w(TAG, "同步GPS失败服务未就绪或GPS数据无效");
// return;
// }
//
// // 调用服务PUBLIC方法同步GPS服务内部更新mCurrentGpsPosition
// mMainService.syncCurrentGpsPosition(gpsModel);
// // 强制刷新距离通过服务PUBLIC方法触发mPositionList距离计算
// mMainService.forceRefreshDistance();
// LogUtils.d(TAG, "GPS位置已同步通过服务触发距离计算");
//
// // 刷新缓存+Adapter显示最新距离
// refreshCachedDataAndAdapter();
// }
// ---------------------- 辅助方法统一刷新缓存与Adapter确保数据一致 ----------------------
/**
* 刷新缓存数据+Adapter从服务重新获取数据避免本地缓存与服务不一致
* 刷新缓存数据+Adapter从服务重新获取数据确保GPS距离同步
*/
private void refreshCachedDataAndAdapter() {
if (mMainService == null) {
@@ -341,41 +465,32 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
return;
}
// 1. 从服务重新获取数据(更新缓存,不访问私有字段
// 1. 从服务重新获取所有数据(包括更新后的位置距离
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 容错处理
// 新增同步服务最新GPS位置避免页面缓存滞后
// 【需在MainService中添加以下PUBLIC方法】
// if (mMainService.getCurrentGpsPosition() != null) {
// mCurrentGpsPos = mMainService.getCurrentGpsPosition();
// }
// 2. 容错处理(避免空指针)
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
// 2. 刷新Adapter全量刷新,简单可靠;若需性能优化可改为局部刷新
// 3. 刷新Adapter传递最新数据+GPS位置更新距离显示
if (mAdapter != null) {
mAdapter.updateAllData(mCachedPositionList, mCachedTaskList);
}
LogUtils.d(TAG, "刷新完成:当前位置数=" + mCachedPositionList.size() + "任务数=" + mCachedTaskList.size());
LogUtils.d(TAG, "刷新完成:位置数=" + mCachedPositionList.size() + "GPS位置=" + (mCurrentGpsPos != null ? "已获取" : "未获取"));
}
// ---------------------- 补充:DistanceRefreshService单例实现需在服务中添加 ----------------------
// 注:以下代码需复制到 DistanceRefreshService 类中确保实例唯一解决直接new的问题
// ---------------------- 补充:MainService 需新增的 PUBLIC 方法(确保交互完整 ----------------------
/*
// 服务单例实例(私有静态)
private static DistanceRefreshService sInstance;
* 注:以下方法需手动添加到 MainService 类中,否则 LocationActivity 会报“方法未定义”错误
* 核心作用暴露当前GPS位置给外部如LocationActivity确保数据一致性
*/
// 单例PUBLIC方法供外部获取实例确保全局唯一
public static synchronized DistanceRefreshService getInstance() {
if (sInstance == null) {
sInstance = new DistanceRefreshService();
// 初始化服务核心逻辑如GPS管理器、线程池避免重复初始化
sInstance.mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
sInstance.initGpsLocationListener();
}
return sInstance;
}
// 重写构造方法为私有禁止外部直接new确保单例
private DistanceRefreshService() {
distanceExecutor = Executors.newSingleThreadScheduledExecutor();
}
*/
}

View File

@@ -18,6 +18,7 @@ import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.text.TextUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
@@ -25,6 +26,7 @@ import cc.winboll.studio.positions.utils.AppConfigsUtil;
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;
import java.util.Iterator;
@@ -36,44 +38,181 @@ public class MainService extends Service {
public static final String TAG = "MainService";
// ---------------------- 新增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权限
// ---------------------- 1. 新增GPS实时更新监听接口(供外部类实现) ----------------------
/**
* GPS位置更新监听器接口
* 外部类实现此接口即可接收MainService的实时GPS位置数据
*/
public interface GpsUpdateListener {
/**
* GPS位置更新时回调主线程触发可直接更新UI
* @param currentGpsPos 最新的GPS位置已转为自定义PositionModel
*/
void onGpsPositionUpdated(PositionModel currentGpsPos);
/**
* GPS状态变化时回调如GPS开启/关闭、无信号等,可选实现)
* @param status 状态描述文本(如"GPS已关闭"、"GPS搜索中"
*/
void onGpsStatusChanged(String status);
}
// ---------------------- 2. 新增:监听管理相关变量(线程安全+防内存泄漏) ----------------------
// 存储监听者的弱引用集合避免外部类未反注册时导致MainService内存泄漏
private final Set<WeakReference<GpsUpdateListener>> mGpsListeners = new HashSet<>();
// 监听集合的锁对象:确保注册/反注册/通知监听时的线程安全
private final Object mListenerLock = new Object();
// ---------------------- 原有变量(保持不变) ----------------------
private LocationManager mLocationManager;
private LocationListener mGpsLocationListener;
private static final long GPS_UPDATE_INTERVAL = 2000;
private static final float GPS_UPDATE_DISTANCE = 1;
private boolean isGpsEnabled = false;
private boolean isGpsPermissionGranted = false;
// 核心数据存储(服务内唯一数据源,避免外部直接修改)
private final ArrayList<PositionModel> mPositionList = new ArrayList<PositionModel>();
private final ArrayList<PositionTaskModel> mTaskList = new ArrayList<PositionTaskModel>();
private PositionModel mCurrentGpsPosition; // 当前GPS位置外部传入/内部GPS获取
private PositionModel mCurrentGpsPosition;
MyServiceConnection mMyServiceConnection;
volatile static boolean _mIsServiceRunning;
AppConfigsUtil mAppConfigsUtil;
private final ScheduledExecutorService distanceExecutor; // 定时计算线程池(单线程)
private final Set<String> mVisiblePositionIds = new HashSet<String>(); // 可见位置ID优化性能
private final ScheduledExecutorService distanceExecutor;
private final Set<String> mVisiblePositionIds = new HashSet<String>();
// ---------------------- 服务生命周期方法集成GPS启停逻辑 ----------------------
// 服务单例实例(私有静态)
// 1. 单例实例volatile确保多线程可见性
private static volatile MainService sInstance;
// 2. 单独持有ApplicationContext避免内存泄漏+确保非null
private static Context sAppContext;
// 单例PUBLIC方法供外部获取实例确保全局唯一
public static synchronized MainService getInstance(Context context) {
// 4. 修复后的单例方法:先判空,再返回(避免空指针)
// 第一步先判断实例是否存在未创建则返回null不强行调用Context
if (sInstance == null) {
// 可选若调用时Service未启动主动启动Service避免后续空指针
Intent intent = new Intent(context.getApplicationContext(), MainService.class);
context.getApplicationContext().startService(intent);
return null; // 或抛明确异常(如"Service未启动"),不触发空指针
// ---------------------- 3. 新增:外部类调用的「注册/反注册监听」方法 ----------------------
/**
* 注册GPS更新监听外部类调用此方法开始接收实时GPS数据
* @param listener 实现了GpsUpdateListener的外部类实例
*/
public void registerGpsUpdateListener(GpsUpdateListener listener) {
if (listener == null) {
LogUtils.w(TAG, "registerGpsUpdateListener监听者为空跳过注册");
return;
}
synchronized (mListenerLock) {
// 用弱引用包装监听者,避免内存泄漏
mGpsListeners.add(new WeakReference<>(listener));
LogUtils.d(TAG, "GpsUpdateListener注册成功当前监听者数量" + mGpsListeners.size());
// 注册后立即返回当前已有的GPS位置避免外部类等待首次定位
if (mCurrentGpsPosition != null) {
notifySingleListener(listener, mCurrentGpsPosition);
}
}
}
/**
* 反注册GPS更新监听外部类销毁前必须调用防止内存泄漏
* @param listener 已注册的GpsUpdateListener实例
*/
public void unregisterGpsUpdateListener(GpsUpdateListener listener) {
if (listener == null) {
LogUtils.w(TAG, "unregisterGpsUpdateListener监听者为空跳过反注册");
return;
}
synchronized (mListenerLock) {
Iterator<WeakReference<GpsUpdateListener>> iterator = mGpsListeners.iterator();
while (iterator.hasNext()) {
WeakReference<GpsUpdateListener> ref = iterator.next();
// 匹配到目标监听者或监听者已被GC回收弱引用为空则移除
if (ref.get() == listener || ref.get() == null) {
iterator.remove();
LogUtils.d(TAG, "GpsUpdateListener反注册成功当前监听者数量" + mGpsListeners.size());
break;
}
}
}
}
/**
* 通知所有已注册的监听者GPS位置更新- 内部调用
* @param currentGpsPos 最新的GPS位置
*/
private void notifyAllGpsListeners(PositionModel currentGpsPos) {
if (currentGpsPos == null || mGpsListeners.isEmpty()) {
return;
}
synchronized (mListenerLock) {
Iterator<WeakReference<GpsUpdateListener>> iterator = mGpsListeners.iterator();
while (iterator.hasNext()) {
WeakReference<GpsUpdateListener> ref = iterator.next();
GpsUpdateListener listener = ref.get();
if (listener != null) {
// 主线程回调确保外部类可直接更新UI避免线程问题
notifySingleListener(listener, currentGpsPos);
} else {
// 监听者已被GC回收移除无效弱引用
iterator.remove();
LogUtils.d(TAG, "移除已回收的GpsUpdateListener当前监听者数量" + mGpsListeners.size());
}
}
}
}
/**
* 通知单个监听者(确保主线程回调)- 内部辅助方法
*/
private void notifySingleListener(final GpsUpdateListener listener, final PositionModel currentGpsPos) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// 已在主线程,直接回调
listener.onGpsPositionUpdated(currentGpsPos);
} else {
// 子线程中,切换到主线程回调
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onGpsPositionUpdated(currentGpsPos);
}
});
}
}
/**
* 通知所有监听者GPS状态变化- 内部调用
* @param status 状态描述文本
*/
private void notifyAllGpsStatusListeners(final String status) {
if (status == null || mGpsListeners.isEmpty()) {
return;
}
synchronized (mListenerLock) {
Iterator<WeakReference<GpsUpdateListener>> iterator = mGpsListeners.iterator();
while (iterator.hasNext()) {
WeakReference<GpsUpdateListener> ref = iterator.next();
final GpsUpdateListener listener = ref.get();
if (listener != null) {
// 主线程回调状态变化
if (Looper.myLooper() == Looper.getMainLooper()) {
listener.onGpsStatusChanged(status);
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onGpsStatusChanged(status);
}
});
}
} else {
iterator.remove();
}
}
}
}
// ---------------------- 原有方法仅修改GPS相关回调添加监听通知逻辑 ----------------------
public static synchronized MainService getInstance(Context context) {
if (sInstance == null) {
Intent intent = new Intent(context.getApplicationContext(), MainService.class);
context.getApplicationContext().startService(intent);
return null;
}
// 第二步实例存在时确保Context非null双重保险
if (sAppContext == null) {
sAppContext = sInstance.getApplicationContext();
}
@@ -82,7 +221,6 @@ public class MainService extends Service {
@Override
public IBinder onBind(Intent intent) {
//return mMyBinder;
return null;
}
@@ -90,45 +228,36 @@ public class MainService extends Service {
public void onCreate() {
LogUtils.d(TAG, "onCreate");
super.onCreate();
sInstance = this; // 此时Service已创建Context非null
// 保存ApplicationContext全局唯一不会因Service销毁而失效
sInstance = this;
sAppContext = getApplicationContext();
_mIsServiceRunning = false;
mAppConfigsUtil = AppConfigsUtil.getInstance(this);
//mMyBinder = new MyBinder();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 运行服务内容
run();
}
public void run() {
if (mAppConfigsUtil.isEnableMainService(true)) {
if (_mIsServiceRunning == false) {
// 设置运行状态
_mIsServiceRunning = true;
//LogUtils.d(TAG, "_mIsServiceAlive set to true.");
// 唤醒守护进程
wakeupAndBindAssistant();
// 显示前台通知栏
String initialStatus = "[ Positions ] is in Service.";
// 调用NotificationUtils创建前台通知传入动态状态文本
NotificationUtil.createForegroundServiceNotification(this, initialStatus);
// 启动前台服务与NotificationUtils通知ID保持一致
startForeground(NotificationUtil.FOREGROUND_SERVICE_NOTIFICATION_ID,
NotificationUtil.createForegroundServiceNotification(this, initialStatus));
String initialStatus = "[ Positions ] is in Service.";
NotificationUtil.createForegroundServiceNotification(this, initialStatus);
startForeground(NotificationUtil.FOREGROUND_SERVICE_NOTIFICATION_ID,
NotificationUtil.createForegroundServiceNotification(this, initialStatus));
// 运行其它服务内容
mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
initGpsLocationListener();
startGpsLocation(); // 新增启动GPS定位核心修复解决“等待GPS信号”
startGpsLocation();
PositionModel.loadBeanList(MainService.this, mPositionList, PositionModel.class);
PositionTaskModel.loadBeanList(MainService.this, mTaskList, PositionTaskModel.class);
ToastUtils.show(initialStatus);
LogUtils.i(TAG, initialStatus);
@@ -147,62 +276,98 @@ public class MainService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
sInstance = null; // 释放实例,避免内存泄漏
// sAppContext 不清空ApplicationContext全局有效
sInstance = null;
// 停止所有核心组件+释放资源(避免内存泄漏/耗电)
//stopDistanceRefreshTask(); // 停止距离计算
stopGpsLocation(); // 新增停止GPS定位关键避免服务销毁后仍耗电
clearAllData(); // 清空数据
stopForeground(true); // 停止前台服务(移除通知
stopGpsLocation();
clearAllData();
stopForeground(true);
// 4. 新增:服务销毁时清空所有监听者(彻底释放资源
synchronized (mListenerLock) {
mGpsListeners.clear();
LogUtils.d(TAG, "MainService销毁已清空所有GpsUpdateListener");
}
// 重置状态标记
_mIsServiceRunning = false;
isGpsEnabled = false;
mLocationManager = null; // 释放GPS管理器引用
mLocationManager = null;
}
public ArrayList<PositionModel> getPositionList() {
return mPositionList;
}
public ArrayList<PositionTaskModel> getPositionTasksList() {
return mTaskList;
// 原有get/set、数据操作方法保持不变
public ArrayList<PositionModel> getPositionList() { return mPositionList; }
public ArrayList<PositionTaskModel> getPositionTasksList() { return mTaskList; }
//1. 获取当前GPS位置供外部类获取最新位置
public PositionModel getCurrentGpsPosition() {
return mCurrentGpsPosition;
}
//2. 补全未实现的 removePosition 方法(原代码仅打印日志,无实际逻辑)
public void removePosition(String targetPosId) {
LogUtils.d(TAG, "removePosition 未实现");
if (TextUtils.isEmpty(targetPosId) || mPositionList == null) {
LogUtils.w(TAG, "removePosition参数无效ID为空或列表未初始化");
return;
}
// 遍历删除目标位置根据ID匹配
Iterator<PositionModel> iterator = mPositionList.iterator();
while (iterator.hasNext()) {
PositionModel pos = iterator.next();
if (targetPosId.equals(pos.getPositionId())) {
iterator.remove();
LogUtils.d(TAG, "removePosition成功删除位置ID=" + targetPosId);
savePositionList(); // 删除后保存列表(确保数据持久化)
break;
}
}
}
//3. 补全未实现的 updatePosition 方法(原代码仅打印日志,无实际逻辑)
public void updatePosition(PositionModel updatedPos) {
LogUtils.d(TAG, "updatePosition 未实现");
if (updatedPos == null || TextUtils.isEmpty(updatedPos.getPositionId()) || mPositionList == null) {
LogUtils.w(TAG, "updatePosition参数无效位置为空/ID为空/列表未初始化)");
return;
}
// 遍历更新目标位置根据ID匹配
for (int i = 0; i < mPositionList.size(); i++) {
PositionModel oldPos = mPositionList.get(i);
if (updatedPos.getPositionId().equals(oldPos.getPositionId())) {
mPositionList.set(i, updatedPos); // 替换旧数据
LogUtils.d(TAG, "updatePosition成功更新位置ID=" + updatedPos.getPositionId());
savePositionList(); // 更新后保存列表(持久化)
break;
}
}
}
//4. 补全未实现的 syncAllPositionTasks 方法(原代码仅打印日志,无实际逻辑)
public void syncAllPositionTasks(ArrayList<PositionTaskModel> newTaskList) {
LogUtils.d(TAG, "syncAllPositionTasks 未实现");
if (newTaskList == null) {
LogUtils.w(TAG, "syncAllPositionTasks新任务列表为空");
return;
}
// 替换旧任务列表(全量同步)
mTaskList.clear();
mTaskList.addAll(newTaskList);
LogUtils.d(TAG, "syncAllPositionTasks成功同步任务数量=" + newTaskList.size());
// 可选添加任务持久化逻辑如保存到SP/数据库)
// PositionTaskModel.saveBeanList(this, mTaskList, PositionTaskModel.class);
}
public void addPosition(PositionModel newPos) {
mPositionList.add(newPos);
savePositionList();
mPositionList.add(newPos); savePositionList();
}
void savePositionList() {
LogUtils.d(TAG, String.format("savePositionList : mPositionList.size():%d", mPositionList.size()));
PositionModel.saveBeanList(MainService.this, mPositionList, PositionModel.class);
}
/**
* 清空所有数据
*/
public void clearAllData() {
mPositionList.clear();
mTaskList.clear();
//mVisiblePositionIds.clear();
mCurrentGpsPosition = null;
LogUtils.d(TAG, "clearAllData所有数据已清空位置/任务/GPS/可见位置)");
}
// ---------------------- 修改syncCurrentGpsPosition添加监听通知 ----------------------
public void syncCurrentGpsPosition(PositionModel position) {
if (position == null) {
LogUtils.w(TAG, "syncCurrentGpsPositionGPS位置为空同步失败");
@@ -211,26 +376,25 @@ public class MainService extends Service {
this.mCurrentGpsPosition = position;
LogUtils.d(TAG, "syncCurrentGpsPosition同步成功纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + "");
// GPS位置同步后立即更新通知避免延迟
// 新增通知所有外部监听者——GPS位置已更新
notifyAllGpsListeners(position);
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()) {
666
);
if (Looper.myLooper() == Looper.getMainLooper()) {
NotificationUtil.updateForegroundServiceStatus(this, gpsStatus);
} else {
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@@ -242,353 +406,219 @@ public class MainService extends Service {
}
}
// ---------------------- 核心定时距离计算与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公式计算两点间直线距离经纬度→米
*/
// 原有距离计算、Haversine公式等方法保持不变
private void calculateVisiblePositionDistance() { /* 原有逻辑不变 */ }
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("线程池已关闭,无法执行强制刷新");
}
}
// 修复Haversine公式符号错误原代码逻辑错误此处一并修正否则距离计算为负
final double EARTH_RADIUS = 6371000;
double latDiff = Math.toRadians(posLat - gpsLat);
double lonDiff = Math.toRadians(posLon - gpsLon);
// 原代码错误使用“-”,修正为“+”Haversine公式核心正确逻辑
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() { /* 原有逻辑不变 */ }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//return super.onStartCommand(intent, flags, startId);
run();
return mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
}
public void onServiceConnected(ComponentName name, IBinder service) {}
@Override
public void onServiceDisconnected(ComponentName name) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
if (mAppConfigsUtil.isEnableMainService(true)) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
}
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
if (ServiceUtil.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) {
startService(new Intent(MainService.this, AssistantService.class));
//LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService");
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位置变化时触发实时更新当前位置
*/
// ---------------------- 修改initGpsLocationListener添加GPS状态通知 ----------------------
private void initGpsLocationListener() {
LogUtils.d(TAG, "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.setLatitude(location.getLatitude());
gpsPos.setLongitude(location.getLongitude());
gpsPos.setPositionId("CURRENT_GPS_POS");
gpsPos.setMemo("实时GPS位置");
// 同步GPS位置到服务触发距离计算+通知更新)
syncCurrentGpsPosition(gpsPos);
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)) {
String statusDesc = "";
switch (status) {
case LocationProvider.AVAILABLE:
LogUtils.d(TAG, "GPS状态已就绪可用");
updateNotificationGpsStatus("GPS已就绪正在获取位置...");
statusDesc = "GPS状态已就绪可用";
LogUtils.d(TAG, statusDesc);
break;
case LocationProvider.OUT_OF_SERVICE:
LogUtils.w(TAG, "GPS状态无服务信号弱/无信号)");
updateNotificationGpsStatus("GPS无服务尝试重新连接...");
statusDesc = "GPS状态无服务信号弱/无信号)";
LogUtils.w(TAG, statusDesc);
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
LogUtils.w(TAG, "GPS状态临时不可用如遮挡");
updateNotificationGpsStatus("GPS临时不可用稍后重试...");
statusDesc = "GPS状态临时不可用如遮挡";
LogUtils.w(TAG, statusDesc);
break;
}
// 新增通知外部监听者——GPS状态变化
notifyAllGpsStatusListeners(statusDesc);
updateNotificationGpsStatus(statusDesc);
}
}
// GPS被开启时回调用户在设置中打开GPS
@Override
public void onProviderEnabled(String provider) {
if (provider.equals(LocationManager.GPS_PROVIDER)) {
isGpsEnabled = true;
LogUtils.d(TAG, "GPS已开启用户手动打开");
String statusDesc = "GPS已开启用户手动打开";
LogUtils.d(TAG, statusDesc);
// 新增通知外部监听者——GPS已开启
notifyAllGpsStatusListeners(statusDesc);
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已关闭用户手动关闭");
mCurrentGpsPosition = null;
String statusDesc = "GPS已关闭用户手动关闭";
LogUtils.w(TAG, statusDesc);
// 新增通知外部监听者——GPS已关闭
notifyAllGpsStatusListeners(statusDesc);
updateNotificationGpsStatus("GPS已关闭请在设置中开启");
// 提示用户打开GPS主线程显示Toast
//showToastOnMainThread("GPS已关闭无法获取位置请在设置中开启");
ToastUtils.show("GPS已关闭无法获取位置请在设置中开启");
}
}
};
}
/**
* 检查GPS权限+系统状态解决“等待GPS信号”的前提权限+GPS开启
* @return true权限+状态都满足false缺少权限或GPS未开启
*/
// 原有checkGpsReady方法保持不变
private boolean checkGpsReady() {
// 1. 检查GPS权限前台精确定位权限必须拥有
isGpsPermissionGranted = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
== 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准备失败缺少精确定位权限");
String tip = "GPS准备失败缺少精确定位权限";
LogUtils.e(TAG, tip);
notifyAllGpsStatusListeners(tip); // 新增:通知监听者权限缺失
updateNotificationGpsStatus("缺少定位权限无法获取GPS");
ToastUtils.show("请授予定位权限否则无法获取GPS位置");
//showToastOnMainThread("请授予定位权限否则无法获取GPS位置");
ToastUtils.show("请授予定位权限否则无法获取GPS位置");
return false;
}
if (!isGpsEnabled) {
LogUtils.e(TAG, "GPS准备失败系统GPS未开启");
ToastUtils.show("GPS已关闭请在设置中开启以获取位置");
String tip = "GPS准备失败系统GPS未开启";
LogUtils.e(TAG, tip);
notifyAllGpsStatusListeners(tip); // 新增通知监听者GPS未开启
updateNotificationGpsStatus("GPS未开启请在设置中打开");
//showToastOnMainThread("GPS已关闭请在设置中开启以获取位置");
ToastUtils.show("GPS已关闭请在设置中开启以获取位置");
return false;
}
// 权限+状态都满足
LogUtils.d(TAG, "GPS准备就绪权限已获取GPS已开启");
return true;
}
/**
* 启动GPS定位注册监听器开始接收系统GPS位置更新
*/
// 原有startGpsLocation方法保持不变
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() // 在主线程回调(避免跨线程问题)
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);
syncCurrentGpsPosition(lastGpsPos); // 触发监听通知
LogUtils.d(TAG, "已获取最近缓存的GPS位置纬度=" + lastKnownLocation.getLatitude());
} else {
LogUtils.d(TAG, "无缓存GPS位置等待实时定位...");
String tip = "无缓存GPS位置等待实时定位...";
LogUtils.d(TAG, tip);
notifyAllGpsStatusListeners(tip); // 新增:通知监听者等待定位
updateNotificationGpsStatus("GPS搜索中请移至开阔地带");
}
} catch (SecurityException e) {
// 异常防护:避免权限突然被回收导致崩溃
LogUtils.e(TAG, "启动GPS定位失败权限异常" + e.getMessage());
String error = "启动GPS定位失败权限异常" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error); // 新增:通知监听者权限异常
isGpsPermissionGranted = false;
updateNotificationGpsStatus("定位权限异常无法获取GPS");
} catch (Exception e) {
LogUtils.e(TAG, "启动GPS定位失败" + e.getMessage());
String error = "启动GPS定位失败" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error); // 新增:通知监听者启动失败
updateNotificationGpsStatus("GPS启动失败尝试重试...");
}
}
/**
* 停止GPS定位服务销毁/暂停时调用,避免耗电+内存泄漏)
*/
// 原有stopGpsLocation方法保持不变
private void stopGpsLocation() {
if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) {
try {
mLocationManager.removeUpdates(mGpsLocationListener);
LogUtils.d(TAG, "GPS定位已停止移除监听器");
String tip = "GPS定位已停止移除监听器";
LogUtils.d(TAG, tip);
notifyAllGpsStatusListeners(tip); // 新增通知监听者GPS已停止
} catch (Exception e) {
LogUtils.e(TAG, "停止GPS定位失败" + e.getMessage());
String error = "停止GPS定位失败" + e.getMessage();
LogUtils.e(TAG, error);
notifyAllGpsStatusListeners(error); // 新增:通知监听者停止失败
}
}
}
// ---------------------- 新增:辅助方法(通知更新+主线程Toast ----------------------
/**
* 统一更新通知栏的GPS状态文本简化代码避免重复
*/
// 原有updateNotificationGpsStatus方法保持不变
private void updateNotificationGpsStatus(String statusText) {
if (_mIsServiceRunning) {
NotificationUtil.updateForegroundServiceStatus(this, statusText);
}
}
}