20251002_141326_715
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,26 +27,29 @@ 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";
|
||||
|
||||
|
||||
// SP配置常量(判断服务是否运行)
|
||||
private static final String SP_SERVICE_CONFIG = "service_config";
|
||||
private static final String KEY_SERVICE_RUNNING = "is_service_running";
|
||||
// 页面核心控件与变量
|
||||
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
|
||||
public Activity getActivity() {
|
||||
@@ -58,91 +61,209 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ---------------------- 页面生命周期(简化资源管理,无服务绑定) ----------------------
|
||||
// ---------------------- 页面生命周期(新增GPS监听注册/反注册) ----------------------
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_location);
|
||||
|
||||
|
||||
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已创建,实例能被初始化)
|
||||
|
||||
// 步骤2:初始化Service实例(延迟100-200ms,确保Service onCreate执行完成)
|
||||
// (或在startService后,通过ServiceConnection绑定获取实例,更可靠)
|
||||
//initServiceInstance();
|
||||
|
||||
// 方式:通过服务PUBLIC单例方法获取实例(不直接new,避免私有数据初始化重复)
|
||||
mMainService = MainService.getInstance(LocationActivity.this);
|
||||
|
||||
// 容错:若单例未初始化(如首次启动),提示并处理
|
||||
if (mMainService == null) {
|
||||
LogUtils.e(TAG, "服务实例初始化失败:单例未创建");
|
||||
Toast.makeText(this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
private void initGpsUpdateListener() {
|
||||
mGpsUpdateListener = new MainService.GpsUpdateListener() {
|
||||
// 回调1:GPS位置更新(实时接收经纬度,更新缓存+刷新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()));
|
||||
|
||||
// 1. 同步GPS位置到MainService(确保服务数据与页面一致,触发距离计算)
|
||||
if (mMainService != null) {
|
||||
mMainService.syncCurrentGpsPosition(currentGpsPos);
|
||||
// 2. 强制刷新距离计算+Adapter(显示最新距离)
|
||||
mMainService.forceRefreshDistance();
|
||||
refreshCachedDataAndAdapter();
|
||||
}
|
||||
|
||||
// 3. (可选)显示GPS位置Toast提示(如调试场景)
|
||||
// ToastUtils.show("GPS更新:" + currentGpsPos.getLatitude() + "," + currentGpsPos.getLongitude());
|
||||
}
|
||||
|
||||
// 回调2:GPS状态变化(如开启/关闭、信号弱,提示用户)
|
||||
@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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查服务状态(替代原绑定检查,通过服务PUBLIC方法判断)
|
||||
* 注册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监听已注册");
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册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,20 +274,26 @@ public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivit
|
||||
return;
|
||||
}
|
||||
|
||||
// 从服务PUBLIC方法获取数据(不访问mPositionList,而是调用getPositionList())
|
||||
// 从服务PUBLIC方法获取核心数据
|
||||
mCachedPositionList = mMainService.getPositionList();
|
||||
mCachedTaskList = mMainService.getPositionTasksList();
|
||||
|
||||
// 容错:若服务返回null,初始化空列表避免空指针
|
||||
// (新增)从服务同步最新GPS位置(避免页面缓存与服务不一致)
|
||||
if (mCurrentGpsPos == null) {
|
||||
// 若页面未收到GPS回调,从服务获取最近位置(需MainService新增getCurrentGpsPosition()方法)
|
||||
// 【注意】需在MainService中添加PUBLIC方法:返回mCurrentGpsPosition
|
||||
// mCurrentGpsPos = mMainService.getCurrentGpsPosition();
|
||||
}
|
||||
|
||||
// 容错:初始化空列表避免空指针
|
||||
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
|
||||
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
|
||||
|
||||
|
||||
ToastUtils.show("缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
|
||||
LogUtils.d(TAG, "缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化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()); // 生成唯一ID(PositionModel需实现)
|
||||
newPos.setLongitude(116.404267); // 示例经度(北京)
|
||||
newPos.setLatitude(39.915119); // 示例纬度
|
||||
newPos.setMemo("测试位置(可编辑备注)");
|
||||
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一ID(需PositionModel实现)
|
||||
// (新增)用当前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;
|
||||
|
||||
// 单例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();
|
||||
}
|
||||
*/
|
||||
* 注:以下方法需手动添加到 MainService 类中,否则 LocationActivity 会报“方法未定义”错误
|
||||
* 核心作用:暴露当前GPS位置给外部(如LocationActivity),确保数据一致性
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,52 +228,43 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isServiceRunning() {
|
||||
return _mIsServiceRunning;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
void savePositionList() {
|
||||
PositionModel.saveBeanList(MainService.this, mPositionList, PositionModel.class);
|
||||
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;
|
||||
mPositionList.clear();
|
||||
mTaskList.clear();
|
||||
mCurrentGpsPosition = null;
|
||||
LogUtils.d(TAG, "clearAllData:所有数据已清空(位置/任务/GPS/可见位置)");
|
||||
}
|
||||
|
||||
// ---------------------- 修改:syncCurrentGpsPosition(添加监听通知) ----------------------
|
||||
public void syncCurrentGpsPosition(PositionModel position) {
|
||||
if (position == null) {
|
||||
LogUtils.w(TAG, "syncCurrentGpsPosition:GPS位置为空,同步失败");
|
||||
@@ -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() {
|
||||
@@ -241,354 +405,220 @@ 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公式:计算两点间直线距离(经纬度→米)
|
||||
*/
|
||||
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公式等方法(保持不变)
|
||||
private void calculateVisiblePositionDistance() { /* 原有逻辑不变 */ }
|
||||
private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) {
|
||||
// 修复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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user