From ca953adec2d6abd914b99787d0fc9909796eefa3 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 2 Oct 2025 21:22:41 +0800 Subject: [PATCH] 20251002_212237_535 --- positions/build.properties | 4 +- .../activities/LocationActivity.java | 630 +++------- .../positions/adapters/PositionAdapter.java | 645 ++++++----- .../positions/services/MainService.java | 1025 ++++++++++++----- 4 files changed, 1247 insertions(+), 1057 deletions(-) diff --git a/positions/build.properties b/positions/build.properties index 8938134..1ff47f9 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Oct 02 08:02:02 GMT 2025 +#Thu Oct 02 13:16:14 GMT 2025 stageCount=8 libraryProject= baseVersion=15.0 publishVersion=15.0.7 -buildCount=27 +buildCount=29 baseBetaVersion=15.0.8 diff --git a/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java b/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java index 72aead7..be3e501 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java +++ b/positions/src/main/java/cc/winboll/studio/positions/activities/LocationActivity.java @@ -5,495 +5,223 @@ package cc.winboll.studio.positions.activities; * @Date 2025/09/29 18:22 * @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期) */ -import android.app.Activity; -import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.view.View; -import android.view.inputmethod.InputMethodManager; +import android.os.IBinder; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; import android.widget.Toast; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; + import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.positions.R; import cc.winboll.studio.positions.adapters.PositionAdapter; import cc.winboll.studio.positions.models.PositionModel; import cc.winboll.studio.positions.models.PositionTaskModel; import cc.winboll.studio.positions.services.MainService; +import cc.winboll.studio.positions.R; import java.util.ArrayList; -import cc.winboll.studio.libappbase.ToastUtils; -import cc.winboll.studio.positions.utils.AppConfigsUtil; /** - * 核心调整说明: - * 1. 新增 MainService.GpsUpdateListener 实现,接收实时GPS数据(经纬度+状态) - * 2. 完善 MainService 引用逻辑:修复实例获取可靠性(启动+延迟初始化)、补全反注册 - * 3. 新增GPS数据应用:同步GPS到服务、刷新位置距离、显示GPS状态 - * 4. 强化生命周期管理:页面销毁时反注册GPS监听,避免内存泄漏 + * Java 7 语法适配: + * 1. 服务绑定用匿名内部类实现 ServiceConnection + * 2. Adapter 初始化传入 MainService 实例,确保数据来源唯一 + * 3. 所有位置/任务操作通过 MainService 接口执行 */ -public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity { - public static final String TAG = "LocationActivity"; +public class LocationActivity extends Activity { + private 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 mCachedPositionList; - private ArrayList mCachedTaskList; - // ---------------------- 新增:GPS监听核心变量 ---------------------- - private MainService.GpsUpdateListener mGpsUpdateListener; // GPS监听实例 - private PositionModel mCurrentGpsPos; // 缓存当前GPS位置(供页面使用) + private RecyclerView mRvPosition; + private PositionAdapter mPositionAdapter; + private ArrayList mLocalPosCache; // 本地位置缓存(与MainService同步) + // MainService 引用+绑定状态 + private MainService mMainService; + private boolean isServiceBound = false; - @Override - public Activity getActivity() { - return this; - } + // 服务连接(Java 7 匿名内部类实现) + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + // 假设 MainService 用 LocalBinder 暴露实例(Java 7 强转) + MainService.LocalBinder binder = (MainService.LocalBinder) service; + mMainService = binder.getService(); + isServiceBound = true; - @Override - public String getTag() { - return TAG; - } + LogUtils.d(TAG, "MainService绑定成功,开始同步数据"); + // 从MainService同步初始数据(位置+任务) + syncDataFromMainService(); + // 初始化Adapter(传入MainService实例,确保任务数据从服务获取) + initPositionAdapter(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.w(TAG, "MainService断开连接,清空引用"); + mMainService = null; + isServiceBound = false; + } + }; - // ---------------------- 页面生命周期(新增GPS监听注册/反注册) ---------------------- @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_location); - LogUtils.d(TAG, "onCreate"); + // 初始化视图+本地缓存 + initView(); + mLocalPosCache = new ArrayList(); - // 初始化GPS监听(提前创建,避免空指针) - initGpsUpdateListener(); - // 启动+初始化MainService(确保服务已创建,实例可获取) - startAndInitMainService(); - // 初始化RecyclerView(布局+性能优化) - initRecyclerViewConfig(); - // 检查服务状态(未运行则启动,配置未启用则提示) - //checkServiceStatus(); - // 缓存服务数据(从服务PUBLIC方法获取,不访问私有字段) - cacheServiceData(); - // 初始化Adapter(传缓存数据+当前GPS位置,支持距离计算显示) - initAdapter(); + // 绑定MainService(确保Activity启动时就拿到服务实例) + bindMainService(); } - @Override - protected void onResume() { - super.onResume(); - // 页面可见时:注册GPS监听(确保能接收实时数据) - registerGpsListener(); - // 刷新数据(避免页面切后台后数据不一致) - if (mAdapter != null) { - refreshCachedDataAndAdapter(); + /** + * 初始化视图(RecyclerView) + */ + private void initView() { + mRvPosition = (RecyclerView) findViewById(R.id.rv_position_list); + // Java 7 显式设置布局管理器(LinearLayoutManager) + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mRvPosition.setLayoutManager(layoutManager); + } + + /** + * 绑定MainService(Java 7 显式Intent) + */ + private void bindMainService() { + Intent serviceIntent = new Intent(this, MainService.class); + // 绑定服务(BIND_AUTO_CREATE:服务不存在时自动创建) + bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE); + LogUtils.d(TAG, "发起MainService绑定请求"); + } + + /** + * 从MainService同步数据(位置+任务) + */ + private void syncDataFromMainService() { + if (!isServiceBound || mMainService == null) { + LogUtils.w(TAG, "同步数据失败:MainService未绑定"); + showToast("服务未就绪,无法加载数据"); + return; } + + // 同步位置数据(从服务获取最新列表) + ArrayList servicePosList = mMainService.getPositionList(); + if (servicePosList != null && !servicePosList.isEmpty()) { + mLocalPosCache.clear(); + mLocalPosCache.addAll(servicePosList); + LogUtils.d(TAG, "从MainService同步位置数据完成:数量=" + mLocalPosCache.size()); + } + + // 同步任务数据(无需本地缓存,Adapter直接从服务获取) + ArrayList serviceTaskList = mMainService.getAllTasks(); + LogUtils.d(TAG, "从MainService同步任务数据完成:数量=" + serviceTaskList.size()); } - @Override - protected void onPause() { - super.onPause(); - // 页面不可见时:反注册GPS监听(减少资源占用,避免内存泄漏) - unregisterGpsListener(); + /** + * 初始化PositionAdapter(核心:传入MainService实例) + */ + private void initPositionAdapter() { + if (mMainService == null) { + LogUtils.e(TAG, "初始化Adapter失败:MainService为空"); + return; + } + + // Java 7 显式初始化Adapter,传入上下文+本地位置缓存+MainService实例 + mPositionAdapter = new PositionAdapter(this, mLocalPosCache, mMainService); + + // 设置Adapter回调(处理位置删除/保存,最终同步到MainService) + mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() { + @Override + public void onDeleteClick(int position) { + // 删除逻辑:先删本地缓存,再调用MainService接口删服务数据 + if (position < 0 || position >= mLocalPosCache.size()) { + LogUtils.w(TAG, "删除位置失败:无效索引=" + position); + return; + } + PositionModel deletePos = mLocalPosCache.get(position); + if (deletePos != null && !deletePos.getPositionId().isEmpty()) { + // 1. 调用MainService接口删除服务端数据 + mMainService.removePosition(deletePos.getPositionId()); + // 2. 删除本地缓存数据 + mLocalPosCache.remove(position); + // 3. 通知Adapter刷新 + mPositionAdapter.notifyItemRemoved(position); + showToast("删除位置成功:" + deletePos.getMemo()); + LogUtils.d(TAG, "删除位置完成:ID=" + deletePos.getPositionId() + "(已同步MainService)"); + } + } + }); + + mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() { + @Override + public void onSavePositionClick(int position, PositionModel updatedPos) { + // 保存逻辑:先更本地缓存,再调用MainService接口更新服务数据 + if (!isServiceBound || mMainService == null) { + LogUtils.w(TAG, "保存位置失败:MainService未绑定"); + showToast("服务未就绪,保存失败"); + return; + } + if (position < 0 || position >= mLocalPosCache.size()) { + LogUtils.w(TAG, "保存位置失败:无效索引=" + position); + return; + } + + // 1. 调用MainService接口更新服务端数据 + mMainService.updatePosition(updatedPos); + // 2. 更新本地缓存数据 + mLocalPosCache.set(position, updatedPos); + // 3. 通知Adapter刷新(可选,Adapter已本地同步) + mPositionAdapter.notifyItemChanged(position); + showToast("保存位置成功:" + updatedPos.getMemo()); + LogUtils.d(TAG, "保存位置完成:ID=" + updatedPos.getPositionId() + "(已同步MainService)"); + } + }); + + // 设置Adapter到RecyclerView + mRvPosition.setAdapter(mPositionAdapter); + LogUtils.d(TAG, "PositionAdapter初始化完成(已绑定MainService)"); + } + + /** + * 显示Toast(Java 7 显式Toast.makeText) + */ + private void showToast(String content) { + Toast.makeText(this, content, Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); - // 1. 最终反注册GPS监听(双重保险,避免遗漏) - unregisterGpsListener(); - // 2. 清理Adapter资源 - if (mAdapter != null) { - mAdapter.release(); - mAdapter = null; + + // 1. 释放Adapter资源(反注册服务监听,避免内存泄漏) + if (mPositionAdapter != null) { + mPositionAdapter.release(); + } + + // 2. 解绑MainService(避免Activity销毁后服务仍被持有) + if (isServiceBound) { + unbindService(mServiceConnection); + LogUtils.d(TAG, "MainService解绑完成"); } - // 3. 置空服务实例+缓存数据+GPS数据(帮助GC回收) - mMainService = null; - mCachedPositionList = null; - mCachedTaskList = null; - mCurrentGpsPos = null; - mGpsUpdateListener = null; - mRecyclerView = null; - LogUtils.d(TAG, "页面销毁:已清理所有资源(服务/缓存/GPS监听)"); } + + public static class LocalBinder extends android.os.Binder { + // 持有 MainService 实例引用 + private MainService mService; - // ---------------------- 新增:GPS监听初始化+注册/反注册(核心适配逻辑) ---------------------- - /** - * 初始化GPS监听:实现MainService.GpsUpdateListener,接收实时GPS数据 - */ - 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())); + // 构造时传入服务实例 + public LocalBinder(MainService service) { + this.mService = service; + } - // 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); - } - } - }; + // 对外提供获取服务实例的方法(供Activity调用) + public MainService getService() { + return mService; + } } - - /** - * 注册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实例获取成功"); - refreshCachedDataAndAdapter(); - } - } - }, 200); - LogUtils.d(TAG, "startAndInitMainService end."); - } - - /** - * 检查服务状态(通过服务PUBLIC方法,不访问私有字段) - */ -// private void checkServiceStatus() { -// LogUtils.d(TAG, "checkServiceStatus"); -// // 1. 服务实例未初始化(等待延迟获取,不重复处理) -// if (mMainService == null) { -// LogUtils.d(TAG, "服务实例未就绪,等待初始化..."); -// return; -// } -// -// // 2. 检查服务运行状态(调用isServiceRunning()) -// if (!mMainService.isServiceRunning()) { -// // 服务未运行:调用run()启动 -// mMainService.run(); -// LogUtils.d(TAG, "服务未运行,已通过run()触发启动"); -// } -// -// // 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数据,同步服务交互) ---------------------- - /** - * 缓存服务数据(新增GPS位置缓存,供Adapter使用) - */ - private void cacheServiceData() { - LogUtils.d(TAG, "cacheServiceData()"); - if (mMainService == null) { - ToastUtils.show("缓存数据失败:服务实例为空"); - LogUtils.e(TAG, "缓存数据失败:服务实例为空"); - mCachedPositionList = new ArrayList(); - mCachedTaskList = new ArrayList(); - return; - } - - // 从服务PUBLIC方法获取核心数据 - mCachedPositionList = mMainService.getPositionList(); - mCachedTaskList = mMainService.getPositionTasksList(); - // (新增)从服务同步最新GPS位置(避免页面缓存与服务不一致) - if (mCurrentGpsPos == null) { - // 若页面未收到GPS回调,从服务获取最近位置(需MainService新增getCurrentGpsPosition()方法) - // 【注意】需在MainService中添加PUBLIC方法:返回mCurrentGpsPosition - // mCurrentGpsPos = mMainService.getCurrentGpsPosition(); - } - - // 容错:初始化空列表避免空指针 - if (mCachedPositionList == null) mCachedPositionList = new ArrayList(); - if (mCachedTaskList == null) mCachedTaskList = new ArrayList(); - - ToastUtils.show("缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); - LogUtils.d(TAG, "缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); - } - - /** - * 初始化RecyclerView(保持原有逻辑,无修改) - */ - private void initRecyclerViewConfig() { - mRecyclerView = (RecyclerView) findViewById(R.id.rv_position_list); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - mRecyclerView.setLayoutManager(layoutManager); - mRecyclerView.setHasFixedSize(true); // 固定大小,优化绘制 - } - - /** - * 初始化Adapter(新增传入GPS位置,支持Adapter显示距离信息) - */ - private void initAdapter() { - LogUtils.d(TAG, "initAdapter"); - if (mCachedPositionList == null || mCachedTaskList == null) { - LogUtils.e(TAG, "初始化Adapter失败:缓存数据为空"); - return; - } - - // 1. 初始化Adapter(传入缓存数据+当前GPS位置,供Adapter计算/显示距离) - mAdapter = new PositionAdapter(this, mCachedPositionList, mCachedTaskList); - mRecyclerView.setAdapter(mAdapter); - - // 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; - } - - PositionModel targetPos = mCachedPositionList.get(position); - String targetPosId = targetPos.getPositionId(); - // 调用服务PUBLIC方法删除 - mMainService.removePosition(targetPosId); - LogUtils.d(TAG, "通过服务删除位置:ID=" + targetPosId); - - // 刷新缓存+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; - } - - // 调用服务PUBLIC方法更新 - mMainService.updatePosition(updatedPos); - LogUtils.d(TAG, "通过服务保存位置:ID=" + updatedPos.getPositionId()); - - // 刷新缓存+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; - } - - // 构建新任务列表(缓存任务+新任务,去重) - ArrayList newTaskList = new ArrayList(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); - - // 调用服务PUBLIC方法同步任务 - mMainService.syncAllPositionTasks(newTaskList); - LogUtils.d(TAG, "通过服务保存任务:ID=" + newTask.getTaskId()); - - // 刷新缓存+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数据) ---------------------- - /** - * 新增位置(调用服务addPosition(),可选:用当前GPS位置初始化新位置) - */ - public void addNewPosition(View view) { - // 1. 隐藏软键盘 - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null && getCurrentFocus() != null) { - imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); - } - - // 2. 校验服务状态 - if (mMainService == null) { - Toast.makeText(this, "新增失败:服务未初始化", Toast.LENGTH_SHORT).show(); - return; - } - - // 3. 创建新位置模型(优化:优先用当前GPS位置初始化,无则用默认值) - PositionModel newPos = new PositionModel(); - 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); // 启用距离计算(依赖GPS) - - // 4. 调用服务PUBLIC方法新增 - mMainService.addPosition(newPos); - LogUtils.d(TAG, "通过服务新增位置:ID=" + newPos.getPositionId() + ",纬度=" + newPos.getLatitude()); - - // 5. 刷新缓存+Adapter(显示新增结果+距离) - refreshCachedDataAndAdapter(); - Toast.makeText(this, "新增位置成功(已启用GPS距离计算)", Toast.LENGTH_SHORT).show(); - } - - // ---------------------- 辅助方法(完善数据刷新逻辑,包含GPS距离) ---------------------- - /** - * 刷新缓存数据+Adapter(从服务重新获取数据,确保GPS距离同步) - */ - private void refreshCachedDataAndAdapter() { - if (mMainService == null) { - LogUtils.w(TAG, "刷新失败:服务实例为空"); - return; - } - - // 1. 从服务重新获取所有数据(包括更新后的位置距离) - mCachedPositionList = mMainService.getPositionList(); - mCachedTaskList = mMainService.getPositionTasksList(); - // (新增)同步服务最新GPS位置(避免页面缓存滞后) - // 【需在MainService中添加以下PUBLIC方法】 - // if (mMainService.getCurrentGpsPosition() != null) { - // mCurrentGpsPos = mMainService.getCurrentGpsPosition(); - // } - - // 2. 容错处理(避免空指针) - if (mCachedPositionList == null) mCachedPositionList = new ArrayList(); - if (mCachedTaskList == null) mCachedTaskList = new ArrayList(); - - // 3. 刷新Adapter(传递最新数据+GPS位置,更新距离显示) - if (mAdapter != null) { - mAdapter.updateAllData(mCachedPositionList, mCachedTaskList); - } - LogUtils.d(TAG, "刷新完成:位置数=" + mCachedPositionList.size() + ",GPS位置=" + (mCurrentGpsPos != null ? "已获取" : "未获取")); - } - - // ---------------------- 补充:MainService 需新增的 PUBLIC 方法(确保交互完整) ---------------------- - /* - * 注:以下方法需手动添加到 MainService 类中,否则 LocationActivity 会报“方法未定义”错误 - * 核心作用:暴露当前GPS位置给外部(如LocationActivity),确保数据一致性 - */ - } - diff --git a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java index d4f13e0..ab60875 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java +++ b/positions/src/main/java/cc/winboll/studio/positions/adapters/PositionAdapter.java @@ -5,7 +5,6 @@ package cc.winboll.studio.positions.adapters; * @Date 2025/09/29 20:25 * @Describe 位置数据适配器(完全独立,无未知接口依赖,仅用LocationActivity缓存数据) */ - import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -15,30 +14,36 @@ import android.widget.Button; import android.widget.EditText; import android.widget.RadioGroup; import android.widget.TextView; +import android.text.TextUtils; import androidx.recyclerview.widget.RecyclerView; + import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.positions.R; import cc.winboll.studio.positions.models.PositionModel; import cc.winboll.studio.positions.models.PositionTaskModel; +import cc.winboll.studio.positions.services.MainService; + +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; /** - * 核心调整: - * 1. 彻底删除所有与 DistanceServiceInterface 相关的代码(解决未知类型问题) - * 2. 完全基于 LocationActivity 传入的缓存数据(mCachedPositionList/mCachedTaskList)实现所有功能 - * 3. 移除服务回调、可见位置通知等冗余逻辑,仅保留“数据显示+用户交互→通知Activity处理”的核心流程 - * 4. 确保无未定义接口、无外部服务依赖,代码可直接编译运行 + * Java 7 语法适配: + * 1. 移除 Lambda/方法引用,用匿名内部类替代 + * 2. 集合操作使用迭代器(避免 ConcurrentModificationException) + * 3. 弱引用管理 MainService,避免内存泄漏 + * 4. 所有任务数据从 MainService 获取,更新通过 MainService 接口 */ -public class PositionAdapter extends RecyclerView.Adapter { +public class PositionAdapter extends RecyclerView.Adapter implements MainService.TaskUpdateListener { public static final String TAG = "PositionAdapter"; - // 1. 视图类型常量(简单视图/编辑视图,无魔法值) + // 视图类型常量(Java 7 静态常量定义) private static final int VIEW_TYPE_SIMPLE = 0; private static final int VIEW_TYPE_EDIT = 1; - // 2. 默认配置(文本显示/任务默认值,统一管理) + + // 默认配置常量(统一管理,避免魔法值) private static final String DEFAULT_MEMO = "无备注"; private static final String DEFAULT_TASK_DESC = "新任务"; private static final int DEFAULT_TASK_DISTANCE = 50; // 单位:米 @@ -46,81 +51,61 @@ public class PositionAdapter extends RecyclerView.Adapter mCachedPositionList; // 位置缓存(来自Activity) - private ArrayList mCachedTaskList; // 任务缓存(来自Activity) + private final ArrayList mCachedPositionList; // 位置缓存(从Activity传入,最终需与MainService同步) + private final WeakReference mMainServiceRef; // 弱引用MainService,避免内存泄漏 + private final ConcurrentHashMap mPosDistanceViewMap; // 距离控件缓存(优化UI更新) - // 4. 本地辅助缓存(优化性能:避免重复遍历,不依赖外部服务) - private final ConcurrentHashMap> mPosToTasksMap; // 位置ID→关联任务(本地分组) - private final ConcurrentHashMap mPosDistanceViewMap; // 位置ID→距离控件(更新UI用) - - // ========================================================================= - // 与 LocationActivity 交互的回调接口(仅定义必要功能,无冗余) - // ========================================================================= - // 删除位置:通知Activity删除指定索引的位置数据 + // 回调接口(与Activity交互,仅处理位置逻辑,任务逻辑直接调用MainService) public interface OnDeleteClickListener { void onDeleteClick(int position); } - // 保存位置:通知Activity保存更新后的位置模型(带索引,方便Activity定位修改) public interface OnSavePositionClickListener { void onSavePositionClick(int position, PositionModel updatedPos); } - // 保存任务:通知Activity保存新增/修改的任务(带任务模型,Activity处理数据同步) - public interface OnSavePositionTaskClickListener { - void onSavePositionTaskClick(PositionTaskModel newTask); - } - - // 回调实例(由LocationActivity初始化时设置,解耦Activity与Adapter) private OnDeleteClickListener mOnDeleteListener; private OnSavePositionClickListener mOnSavePosListener; - private OnSavePositionTaskClickListener mOnSaveTaskListener; // ========================================================================= - // 构造函数(仅接收上下文+Activity缓存数据,无其他依赖,解决接口未定义问题) + // 构造函数(Java 7 风格:初始化依赖+注册任务监听) // ========================================================================= - /** - * 唯一构造函数(确保无未知接口依赖,直接使用Activity提供的数据) - * @param context 上下文(加载布局、操作软键盘、获取资源) - * @param cachedPositionList LocationActivity中的位置缓存(唯一位置数据源) - * @param cachedTaskList LocationActivity中的任务缓存(唯一任务数据源) - */ - public PositionAdapter(Context context, ArrayList cachedPositionList, ArrayList cachedTaskList) { + public PositionAdapter(Context context, ArrayList cachedPositionList, MainService mainService) { this.mContext = context; - // 初始化数据源(容错处理:若Activity传入null,初始化空列表避免空指针) + // 容错处理:避免传入null导致空指针 this.mCachedPositionList = (cachedPositionList != null) ? cachedPositionList : new ArrayList(); - this.mCachedTaskList = (cachedTaskList != null) ? cachedTaskList : new ArrayList(); - - // 初始化本地辅助缓存(基于Activity传入的数据构建,提升后续操作性能) - this.mPosToTasksMap = new ConcurrentHashMap>(); + // 弱引用MainService:防止Adapter持有Service导致内存泄漏(Java 7 弱引用语法) + this.mMainServiceRef = new WeakReference(mainService); + // 初始化距离控件缓存(线程安全集合,适配多线程更新场景) this.mPosDistanceViewMap = new ConcurrentHashMap(); - // 初始化“位置→任务”映射(从Activity任务缓存中分组,避免每次显示都遍历全量任务) - refreshPositionTaskMap(); - LogUtils.d(TAG, "Adapter初始化完成:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); + // 注册MainService任务监听:服务任务变化时自动刷新Adapter(Java 7 接口实现) + if (mainService != null) { + mainService.registerTaskUpdateListener(this); + LogUtils.d(TAG, "已注册MainService任务监听,确保任务数据与服务同步"); + } else { + LogUtils.w(TAG, "构造函数:MainService为空,任务数据无法同步"); + } + + LogUtils.d(TAG, "Adapter初始化完成:位置数量=" + mCachedPositionList.size()); } // ========================================================================= - // RecyclerView 核心方法(完全基于Activity缓存数据实现,无接口调用) + // RecyclerView 核心方法(Java 7 语法适配) // ========================================================================= - /** - * 判断当前项的视图类型(简单/编辑):从Activity缓存数据中读取状态 - */ @Override public int getItemViewType(int position) { + // 从位置缓存获取状态,判断视图类型(简单/编辑) PositionModel posModel = getPositionByIndex(position); - // 若位置模型为空或标记为简单视图,返回简单视图类型 return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; } - /** - * 创建视图Holder(根据类型加载布局,无外部依赖) - */ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(mContext); + // 根据视图类型加载对应布局(Java 7 条件判断) if (viewType == VIEW_TYPE_SIMPLE) { View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false); return new SimpleViewHolder(simpleView); @@ -130,111 +115,117 @@ public class PositionAdapter extends RecyclerView.Adapter updatedTasks = mPosToTasksMap.get(posId); - if (updatedTasks == null) { - updatedTasks = new ArrayList(); - } - updatedTasks.add(newTask); - mPosToTasksMap.put(posId, updatedTasks); - holder.tvTaskCount.setText("任务数量:" + updatedTasks.size()); // 实时更新显示 - LogUtils.d(TAG, "触发任务新增:位置ID=" + posId + ",任务ID=" + newTask.getTaskId()); + // 调用MainService接口新增任务(数据写入服务,由服务处理持久化+通知刷新) + mainService.addPositionTask(newTask); + hideSoftKeyboard(v); + LogUtils.d(TAG, "触发新增任务:调用MainService接口,位置ID=" + posId + ",任务ID=" + newTask.getTaskId()); } }); } // ========================================================================= - // 核心工具方法(无接口依赖,基于缓存数据实现,确保功能完整) + // 工具方法(Java 7 风格:无Lambda,纯匿名内部类+迭代器) // ========================================================================= /** - * 更新距离显示(根据位置模型状态,显示不同文本和颜色,无外部依赖) + * 更新距离显示(根据位置模型状态,显示不同文本+颜色) */ private void updateDistanceDisplay(TextView distanceView, PositionModel posModel) { - if (distanceView == null || posModel == null) return; + if (distanceView == null || posModel == null) { + LogUtils.w(TAG, "updateDistanceDisplay:参数为空(控件/位置模型)"); + return; + } - // 场景1:距离计算未启用(从缓存数据状态判断) + // 场景1:距离未启用 if (!posModel.isEnableRealPositionDistance()) { distanceView.setText(DISTANCE_DISABLED); distanceView.setTextColor(mContext.getResources().getColor(R.color.gray)); return; } - // 场景2:距离计算失败(用-1标记失败,从缓存数据取distance字段) + // 场景2:距离计算失败(用-1标记失败状态) double distance = posModel.getRealPositionDistance(); if (distance < 0) { - distanceView.setText(DISTANCE_ERROR); - distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); - return; - } + distanceView.setText(DISTANCE_ERROR); + distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); + return; + } -// 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度) - distanceView.setText(String.format(DISTANCE_FORMAT, distance)); - if (distance <= 100) { // 近距离(≤100米):绿色 - distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); - } else if (distance <= 500) { // 中距离(≤500米):黄色 - distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow)); - } else { // 远距离(>500米):红色 - distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); - } - } + // 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度) + distanceView.setText(String.format(DISTANCE_FORMAT, distance)); + if (distance <= 100) { + distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); // 近距离(≤100米) + } else if (distance <= 500) { + distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));// 中距离(≤500米) + } else { + distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); // 远距离(>500米) + } + } - /** + /** + * 根据索引获取位置模型(从位置缓存取数,容错处理) + */ + private PositionModel getPositionByIndex(int index) { + if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) { + LogUtils.w(TAG, "getPositionByIndex:无效索引(" + index + ")或位置缓存为空"); + return null; + } + return mCachedPositionList.get(index); + } - - 根据索引获取位置模型(从Activity缓存数据直接读取,无中间层,避免数据不一致) - */ - private PositionModel getPositionByIndex(int index) { - if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) { - LogUtils.w(TAG, "获取位置失败:索引无效(" + index + ")或缓存数据为空"); - return null; - } - return mCachedPositionList.get(index); - } + /** + * 根据位置ID获取列表索引(用于精准刷新视图) + */ + private int getPositionIndexById(String positionId) { + if (TextUtils.isEmpty(positionId) || mCachedPositionList == null || mCachedPositionList.isEmpty()) { + LogUtils.w(TAG, "getPositionIndexById:参数无效(位置ID/缓存为空)"); + return -1; + } - /** + // Java 7 增强for循环遍历(替代Lambda,适配Java 7语法) + for (int i = 0; i < mCachedPositionList.size(); i++) { + PositionModel pos = mCachedPositionList.get(i); + if (positionId.equals(pos.getPositionId())) { + return i; // 找到匹配ID,返回索引 + } + } + LogUtils.w(TAG, "getPositionIndexById:未找到位置ID=" + positionId); + return -1; + } - - 根据位置ID获取列表索引(从Activity缓存数据遍历,用于视图切换后精准刷新) - */ - private int getPositionIndexById(String positionId) { - if (mCachedPositionList == null || positionId == null || positionId.isEmpty()) { - LogUtils.w(TAG, "获取位置索引失败:缓存数据为空或位置ID无效"); - return -1; - } - for (int i = 0; i < mCachedPositionList.size(); i++) { - PositionModel pos = mCachedPositionList.get(i); - if (positionId.equals(pos.getPositionId())) { - return i; // 找到匹配ID,返回对应索引 - } - } - LogUtils.w(TAG, "未找到位置ID:" + positionId + ",返回无效索引-1"); - return -1; - } + /** + * 局部更新距离UI(仅更新指定位置的距离,避免全量刷新卡顿) + */ + public void updateSinglePositionDistance(String positionId) { + // 校验参数:位置ID无效或控件未缓存,直接返回 + if (TextUtils.isEmpty(positionId) || !mPosDistanceViewMap.containsKey(positionId)) { + LogUtils.w(TAG, "updateSinglePositionDistance:位置ID无效或控件未缓存(ID=" + positionId + ")"); + return; + } - /** + // 从MainService获取最新位置模型(确保距离值是服务端最新) + PositionModel latestPos = null; + MainService mainService = mMainServiceRef.get(); + if (mainService != null) { + ArrayList servicePosList = mainService.getPositionList(); + if (servicePosList != null && !servicePosList.isEmpty()) { + // Java 7 迭代器遍历服务端位置列表,找到目标位置 + Iterator posIter = servicePosList.iterator(); + while (posIter.hasNext()) { + PositionModel pos = posIter.next(); + if (positionId.equals(pos.getPositionId())) { + latestPos = pos; + break; + } + } + } + } - - 刷新“位置→任务”映射(基于Activity最新任务缓存重建,确保本地映射与Activity数据一致) - - (Activity任务数据更新后调用,避免本地映射滞后) - */ - public void refreshPositionTaskMap() { - if (mCachedTaskList == null) { - mPosToTasksMap.clear(); // 任务缓存为空时,清空本地映射 - LogUtils.d(TAG, "刷新任务映射:Activity任务缓存为空,已清空本地映射"); - return; - }try { - mPosToTasksMap.clear(); // 先清空旧映射,避免数据残留 - Iterator taskIter = mCachedTaskList.iterator(); - while (taskIter.hasNext()) { - PositionTaskModel task = (PositionTaskModel)taskIter.next(); - String posId = task.getPositionId(); -// 按位置ID分组:同一位置的任务放入同一个列表 - if (!mPosToTasksMap.containsKey(posId)) { - mPosToTasksMap.put(posId, new ArrayList()); - } - mPosToTasksMap.get(posId).add(task); - } - LogUtils.d(TAG, "刷新任务映射完成:已分组" + mPosToTasksMap.size() + "个位置的任务"); - } catch (ConcurrentModificationException e) { - LogUtils.d(TAG, "刷新任务映射失败:并发修改Activity任务缓存。" + e); - } - } + // 用服务端最新距离更新UI(直接操作缓存的距离控件,无需刷新整个项) + if (latestPos != null) { + TextView distanceView = mPosDistanceViewMap.get(positionId); + updateDistanceDisplay(distanceView, latestPos); + LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米"); + } else { + LogUtils.w(TAG, "局部更新距离失败:未在MainService找到位置ID=" + positionId); + } + } - /** + /** + * 全量更新位置数据(从MainService同步最新位置列表,刷新UI) + */ + public void updateAllPositionData(ArrayList newPosList) { + if (newPosList == null) { + LogUtils.w(TAG, "updateAllPositionData:新位置列表为空,跳过更新"); + return; + } - - 全量更新数据(接收Activity最新缓存数据,同步本地数据源+辅助缓存,刷新UI) - - (Activity新增/删除位置/任务后调用,确保Adapter与Activity数据100%一致) - */ - public void updateAllData(ArrayList newPosList, ArrayList newTaskList) { -// 同步Activity最新缓存数据(容错处理:避免接收null导致空指针) - this.mCachedPositionList = (newPosList != null) ? newPosList : new ArrayList(); - this.mCachedTaskList = (newTaskList != null) ? newTaskList : new ArrayList();// 刷新本地辅助缓存(确保映射与最新数据同步) - refreshPositionTaskMap(); - mPosDistanceViewMap.clear(); // 清空旧距离控件缓存,避免引用失效控件// 通知RecyclerView全量刷新(数据已同步,更新UI显示) - notifyDataSetChanged(); - LogUtils.d(TAG, "全量更新数据完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size()); - } + // 同步服务端最新位置数据到本地缓存 + this.mCachedPositionList.clear(); + this.mCachedPositionList.addAll(newPosList); + // 清空旧距离控件缓存(避免引用失效控件) + mPosDistanceViewMap.clear(); + // 通知RecyclerView全量刷新UI + notifyDataSetChanged(); + LogUtils.d(TAG, "全量更新位置数据完成:当前位置数量=" + mCachedPositionList.size() + "(数据来源:MainService)"); + } - /** + /** + * 隐藏软键盘(编辑完成后调用,提升用户体验) + */ + private void hideSoftKeyboard(View view) { + if (mContext == null || view == null) { + LogUtils.w(TAG, "hideSoftKeyboard:参数为空(上下文/视图),无法隐藏键盘"); + return; + } - - 局部更新距离UI(仅更新指定位置的距离显示,避免全量刷新卡顿) - - (Activity同步服务计算的最新距离后调用,精准更新受影响的位置) - */ - public void updateSinglePositionDistance(String positionId) { -// 校验参数:位置ID无效或控件未缓存时,直接返回 - if (positionId == null || positionId.isEmpty() || !mPosDistanceViewMap.containsKey(positionId)) { - LogUtils.w(TAG, "局部更新距离失败:位置ID无效或距离控件未缓存(ID=" + positionId + ")"); - return; - }// 从Activity缓存数据中获取最新位置模型(确保距离值是最新的) - PositionModel latestPos = null; - for (PositionModel pos : mCachedPositionList) { - if (positionId.equals(pos.getPositionId())) { - latestPos = pos; - break; - } - }// 用最新距离值更新UI(直接操作缓存的距离控件,无需刷新整个项) - if (latestPos != null) { - updateDistanceDisplay(mPosDistanceViewMap.get(positionId), latestPos); - LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米"); - } else { - LogUtils.w(TAG, "局部更新距离失败:未在Activity缓存中找到位置ID=" + positionId); - } - } + // Java 7 显式获取输入法服务,避免Lambda + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘 + } + } - /** + // ========================================================================= + // 实现 MainService.TaskUpdateListener 接口(服务任务变化时回调) + // ========================================================================= + @Override + public void onTaskUpdated() { + LogUtils.d(TAG, "收到MainService任务更新通知(任务新增/删除/状态变化),刷新UI"); + // 任务数据变化时,全量刷新Adapter(确保任务数量等显示同步) + notifyDataSetChanged(); + } - - 隐藏软键盘(编辑完成后调用,提升用户体验,避免键盘遮挡) - */ - private void hideSoftKeyboard(View view) { - if (mContext == null || view == null) return; - InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘 - } - } + // ========================================================================= + // 回调设置方法(供LocationActivity调用,绑定交互逻辑) + // ========================================================================= + public void setOnDeleteClickListener(OnDeleteClickListener listener) { + this.mOnDeleteListener = listener; + } - /** + public void setOnSavePositionClickListener(OnSavePositionClickListener listener) { + this.mOnSavePosListener = listener; + } - - 释放资源(Activity销毁时调用,彻底避免内存泄漏) - */ - public void release() { -// 清空本地辅助缓存(解除控件、数据映射引用) - mPosToTasksMap.clear(); - mPosDistanceViewMap.clear(); -// 置空回调实例(避免持有Activity引用导致内存泄漏) - mOnDeleteListener = null; - mOnSavePosListener = null; - mOnSaveTaskListener = null; -// 置空数据源引用(帮助GC回收,避免残留数据占用内存) - mCachedPositionList = null; - mCachedTaskList = null; - LogUtils.d(TAG, "Adapter资源已完全释放"); - } + // ========================================================================= + // 资源释放(Activity销毁时调用,避免内存泄漏) + // ========================================================================= + public void release() { + // 1. 反注册MainService任务监听(解除与服务的绑定,避免内存泄漏) + MainService mainService = mMainServiceRef.get(); + if (mainService != null) { + mainService.unregisterTaskUpdateListener(this); + LogUtils.d(TAG, "已反注册MainService任务监听,避免内存泄漏"); + } -// ========================================================================= -// 回调设置方法(LocationActivity调用,绑定交互逻辑,无冗余参数) -// ========================================================================= - public void setOnDeleteClickListener(OnDeleteClickListener listener) { - this.mOnDeleteListener = listener; - } + // 2. 清空本地缓存(解除控件/数据引用,帮助GC回收) + mPosDistanceViewMap.clear(); + if (mCachedPositionList != null) { + mCachedPositionList.clear(); + } - public void setOnSavePositionClickListener(OnSavePositionClickListener listener) { - this.mOnSavePosListener = listener; - } + // 3. 置空回调实例(避免持有Activity引用导致内存泄漏) + mOnDeleteListener = null; + mOnSavePosListener = null; - public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) { - this.mOnSaveTaskListener = listener; - } + LogUtils.d(TAG, "Adapter资源已完全释放(缓存清空+监听反注册)"); + } -// ========================================================================= -// 视图Holder类(静态内部类,不持有外部引用,彻底避免内存泄漏) -// ========================================================================= - /** + // ========================================================================= + // 静态内部类:视图Holder(Java 7 静态内部类,不持有外部引用,避免内存泄漏) + // ========================================================================= + /** + * 简单视图Holder(仅显示数据,对应布局:item_position_simple.xml) + */ + public static class SimpleViewHolder extends RecyclerView.ViewHolder { + TextView tvSimpleLon; // 经度显示控件 + TextView tvSimpleLat; // 纬度显示控件 + TextView tvSimpleMemo; // 备注显示控件 + TextView tvSimpleDistance;// 实时距离显示控件 - - 简单视图Holder(仅显示数据,对应布局:item_position_simple.xml) - */ - public static class SimpleViewHolder extends RecyclerView.ViewHolder { - TextView tvSimpleLon; // 经度显示控件 - TextView tvSimpleLat; // 纬度显示控件 - TextView tvSimpleMemo; // 备注显示控件 - TextView tvSimpleDistance;// 实时距离显示控件 - public SimpleViewHolder(View itemView) { - super(itemView); -// 绑定布局控件(与XML中ID严格对应,避免运行时空指针) - tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude); - tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude); - tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); - tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance); - } - } + public SimpleViewHolder(View itemView) { + super(itemView); + // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) + tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude); + tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude); + tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); + tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance); + } + } - /** + /** + * 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml) + */ + public static class EditViewHolder extends RecyclerView.ViewHolder { + TextView tvEditLon; // 经度显示控件(不可编辑) + TextView tvEditLat; // 纬度显示控件(不可编辑) + EditText etEditMemo; // 备注编辑控件 + TextView tvEditDistance; // 实时距离显示控件 + RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组 + Button btnCancel; // 取消编辑按钮 + Button btnDelete; // 删除位置按钮 + Button btnSave; // 保存位置按钮 + Button btnAddTask; // 新增任务按钮 + TextView tvTaskCount; // 任务数量显示控件 - - 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml) - */ - public static class EditViewHolder extends RecyclerView.ViewHolder { - TextView tvEditLon; // 经度显示控件(不可编辑) - TextView tvEditLat; // 纬度显示控件(不可编辑) - EditText etEditMemo; // 备注编辑控件 - TextView tvEditDistance; // 实时距离显示控件 - RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组 - Button btnCancel; // 取消编辑按钮 - Button btnDelete; // 删除位置按钮 - Button btnSave; // 保存位置按钮 - Button btnAddTask; // 新增任务按钮 - TextView tvTaskCount; // 任务数量显示控件(简化设计) - public EditViewHolder(View itemView) { - super(itemView); -// 绑定布局控件(与XML中ID严格对应,避免运行时空指针) - tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude); - tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude); - etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); - tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance); - rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch); - btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); - btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); - btnSave = (Button) itemView.findViewById(R.id.btn_edit_save); - btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); - tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count); - } - } + public EditViewHolder(View itemView) { + super(itemView); + // 绑定布局控件(与XML中ID严格对应,避免运行时空指针) + tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude); + tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude); + etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); + tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance); + rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch); + btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); + btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); + btnSave = (Button) itemView.findViewById(R.id.btn_edit_save); + btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); + tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count); + } + } } + diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java index 3aa6227..59b9af0 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java +++ b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java @@ -16,9 +16,12 @@ import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.text.TextUtils; +import android.util.Log; + import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.positions.models.PositionModel; import cc.winboll.studio.positions.models.PositionTaskModel; @@ -26,6 +29,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; @@ -38,175 +42,209 @@ public class MainService extends Service { public static final String TAG = "MainService"; - // ---------------------- 1. 新增:GPS实时更新监听接口(供外部类实现) ---------------------- - /** - * GPS位置更新监听器接口 - * 外部类实现此接口,即可接收MainService的实时GPS位置数据 - */ + // GPS监听接口(Java 7 标准接口定义,无Lambda依赖) 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> mGpsListeners = new HashSet<>(); - // 监听集合的锁对象:确保注册/反注册/通知监听时的线程安全 - private final Object mListenerLock = new Object(); + // 任务更新监听接口(Java 7 风格,供Adapter监听任务变化) + public interface TaskUpdateListener { + void onTaskUpdated(); + } + // 监听管理(弱引用+线程安全集合,适配Java 7,避免内存泄漏+并发异常) + private final Set> mGpsListeners = new HashSet>(); + private final Set> mTaskListeners = new HashSet>(); + private final Object mListenerLock = new Object(); // 监听操作锁,保证线程安全 - // ---------------------- 原有变量(保持不变) ---------------------- + // 原有核心变量(Java 7 显式初始化,无Java 8+语法) + private LocalBinder mLocalBinder; //持有 LocalBinder 实例(用于暴露服务) 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 static final long GPS_UPDATE_INTERVAL = 2000; // GPS更新间隔:2秒 + private static final float GPS_UPDATE_DISTANCE = 1; // GPS更新距离阈值:1米 + private boolean isGpsEnabled = false; // GPS是否启用标记 + private boolean isGpsPermissionGranted = false; // 定位权限是否授予标记 - private final ArrayList mPositionList = new ArrayList(); - private final ArrayList mTaskList = new ArrayList(); - private PositionModel mCurrentGpsPosition; + // 数据存储集合(Java 7 基础集合,避免Stream/forEach等Java 8+特性) + private final ArrayList mPositionList = new ArrayList(); // 位置数据列表 + private final ArrayList mTaskList = new ArrayList();// 任务数据列表 + private PositionModel mCurrentGpsPosition; // 当前GPS定位数据 + // 服务相关变量(Java 7 显式声明,保持原逻辑) MyServiceConnection mMyServiceConnection; - volatile static boolean _mIsServiceRunning; + volatile static boolean _mIsServiceRunning; // 服务运行状态(volatile保证可见性) AppConfigsUtil mAppConfigsUtil; - private final ScheduledExecutorService distanceExecutor; - private final Set mVisiblePositionIds = new HashSet(); + private ScheduledExecutorService distanceExecutor = Executors.newSingleThreadScheduledExecutor(); // 单线程池处理距离计算 + private final Set mVisiblePositionIds = new HashSet(); // 可见位置ID集合 + // 单例+应用上下文(Java 7 静态变量,保证服务实例唯一+上下文安全) private static volatile MainService sInstance; private static Context sAppContext; - // ---------------------- 3. 新增:外部类调用的「注册/反注册监听」方法 ---------------------- + + // ========================================================================= + // 任务操作核心接口(Java 7 实现,全迭代器遍历,无ConcurrentModificationException) + // ========================================================================= /** - * 注册GPS更新监听(外部类调用此方法,开始接收实时GPS数据) - * @param listener 实现了GpsUpdateListener的外部类实例 + * 新增任务(Adapter调用,通过MainService统一管理任务,保证数据一致性) + * @param newTask 待新增的任务模型 */ - public void registerGpsUpdateListener(GpsUpdateListener listener) { - if (listener == null) { - LogUtils.w(TAG, "registerGpsUpdateListener:监听者为空,跳过注册"); + public void addPositionTask(PositionTaskModel newTask) { + // 参数校验(Java 7 基础判断,无Optional等Java 8+特性) + if (newTask == null || TextUtils.isEmpty(newTask.getPositionId())) { + LogUtils.w(TAG, "addPositionTask:任务为空或未绑定位置ID,新增失败"); return; } - synchronized (mListenerLock) { - // 用弱引用包装监听者,避免内存泄漏 - mGpsListeners.add(new WeakReference<>(listener)); - LogUtils.d(TAG, "GpsUpdateListener注册成功,当前监听者数量:" + mGpsListeners.size()); - // 注册后立即返回当前已有的GPS位置(避免外部类等待首次定位) - if (mCurrentGpsPosition != null) { - notifySingleListener(listener, mCurrentGpsPosition); + // 任务去重(Java 7 迭代器遍历,避免增强for循环删除/新增导致的并发异常) + boolean isDuplicate = false; + Iterator taskIter = mTaskList.iterator(); + while (taskIter.hasNext()) { + PositionTaskModel task = taskIter.next(); + if (newTask.getTaskId().equals(task.getTaskId())) { + isDuplicate = true; + break; + } + } + if (isDuplicate) { + LogUtils.w(TAG, "addPositionTask:任务ID已存在(" + newTask.getTaskId() + "),新增失败"); + return; + } + + // 新增任务+持久化+通知刷新(全Java 7 语法) + mTaskList.add(newTask); + saveTaskList(); + notifyTaskUpdated(); // 通知所有监听者(如Adapter)任务已更新 + LogUtils.d(TAG, "addPositionTask:成功(位置ID=" + newTask.getPositionId() + ",任务ID=" + newTask.getTaskId() + ")"); + } + + /** + * 获取指定位置的所有任务(Adapter显示任务数量用,数据来源唯一) + * @param positionId 位置ID + * @return 该位置绑定的所有任务(返回新列表,避免外部修改原数据) + */ + public ArrayList getTasksByPositionId(String positionId) { + ArrayList posTasks = new ArrayList(); + if (TextUtils.isEmpty(positionId) || mTaskList.isEmpty()) { + return posTasks; + } + + // 筛选任务(Java 7 迭代器遍历,安全筛选) + Iterator taskIter = mTaskList.iterator(); + while (taskIter.hasNext()) { + PositionTaskModel task = taskIter.next(); + if (positionId.equals(task.getPositionId())) { + posTasks.add(task); + } + } + return posTasks; + } + + /** + * 获取所有任务(Adapter全量刷新用,返回拷贝避免原数据被外部修改) + * @return 所有任务的拷贝列表 + */ + public ArrayList getAllTasks() { + return new ArrayList(mTaskList); // Java 7 集合拷贝方式 + } + + /** + * 删除任务(Adapter调用,通过迭代器安全删除,避免并发异常) + * @param taskId 待删除任务的ID + */ + public void deletePositionTask(String taskId) { + if (TextUtils.isEmpty(taskId) || mTaskList.isEmpty()) { + LogUtils.w(TAG, "deletePositionTask:任务ID为空或列表为空,删除失败"); + return; + } + + // 迭代器删除(Java 7 唯一安全删除集合元素的方式) + Iterator taskIter = mTaskList.iterator(); + while (taskIter.hasNext()) { + PositionTaskModel task = taskIter.next(); + if (taskId.equals(task.getTaskId())) { + taskIter.remove(); // 迭代器安全删除,无ConcurrentModificationException + saveTaskList(); + notifyTaskUpdated(); + LogUtils.d(TAG, "deletePositionTask:成功(任务ID=" + taskId + ")"); + break; } } } /** - * 反注册GPS更新监听(外部类销毁前必须调用,防止内存泄漏) - * @param listener 已注册的GpsUpdateListener实例 + * 注册任务更新监听(Java 7 弱引用管理,避免内存泄漏) + * @param listener 任务更新监听者(如Adapter) */ - public void unregisterGpsUpdateListener(GpsUpdateListener listener) { + public void registerTaskUpdateListener(TaskUpdateListener listener) { if (listener == null) { - LogUtils.w(TAG, "unregisterGpsUpdateListener:监听者为空,跳过反注册"); + LogUtils.w(TAG, "registerTaskUpdateListener:监听者为空,跳过"); + return; + } + synchronized (mListenerLock) { // 加锁保证多线程注册安全 + mTaskListeners.add(new WeakReference(listener)); + } + } + + /** + * 反注册任务更新监听(Java 7 迭代器清理,避免内存泄漏) + * @param listener 待反注册的监听者 + */ + public void unregisterTaskUpdateListener(TaskUpdateListener listener) { + if (listener == null) { + LogUtils.w(TAG, "unregisterTaskUpdateListener:监听者为空,跳过"); return; } synchronized (mListenerLock) { - Iterator> iterator = mGpsListeners.iterator(); - while (iterator.hasNext()) { - WeakReference ref = iterator.next(); - // 匹配到目标监听者,或监听者已被GC回收(弱引用为空),则移除 + Iterator> iter = mTaskListeners.iterator(); + while (iter.hasNext()) { + WeakReference ref = iter.next(); + // 清理目标监听者或已被回收的弱引用 if (ref.get() == listener || ref.get() == null) { - iterator.remove(); - LogUtils.d(TAG, "GpsUpdateListener反注册成功,当前监听者数量:" + mGpsListeners.size()); - break; + iter.remove(); } } } } /** - * 通知所有已注册的监听者(GPS位置更新)- 内部调用 - * @param currentGpsPos 最新的GPS位置 + * 通知所有任务监听者更新(Java 7 匿名内部类实现主线程回调,无Lambda) */ - private void notifyAllGpsListeners(PositionModel currentGpsPos) { - if (currentGpsPos == null || mGpsListeners.isEmpty()) { - return; - } + private void notifyTaskUpdated() { synchronized (mListenerLock) { - Iterator> iterator = mGpsListeners.iterator(); - while (iterator.hasNext()) { - WeakReference 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> iterator = mGpsListeners.iterator(); - while (iterator.hasNext()) { - WeakReference ref = iterator.next(); - final GpsUpdateListener listener = ref.get(); - if (listener != null) { - // 主线程回调状态变化 + Iterator> iter = mTaskListeners.iterator(); + while (iter.hasNext()) { + final WeakReference ref = iter.next(); + if (ref.get() != null) { + // 判断是否在主线程,不在则切换(Java 7 匿名Runnable,无Lambda) if (Looper.myLooper() == Looper.getMainLooper()) { - listener.onGpsStatusChanged(status); + ref.get().onTaskUpdated(); } else { - new android.os.Handler(Looper.getMainLooper()).post(new Runnable() { + new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { - listener.onGpsStatusChanged(status); + ref.get().onTaskUpdated(); } }); } } else { - iterator.remove(); + iter.remove(); // 清理已回收的弱引用,避免内存泄漏 } } } } - // ---------------------- 原有方法(仅修改GPS相关回调,添加监听通知逻辑) ---------------------- + // ========================================================================= + // 原有基础方法(Java 7 语法调整:移除所有Lambda/方法引用,用匿名内部类替代) + // ========================================================================= + /** + * 获取服务单例(Java 7 静态同步方法,保证线程安全) + * @param context 上下文 + * @return MainService实例(未绑定成功时返回null) + */ public static synchronized MainService getInstance(Context context) { if (sInstance == null) { Intent intent = new Intent(context.getApplicationContext(), MainService.class); @@ -219,275 +257,539 @@ public class MainService extends Service { return sInstance; } + /** + * 服务绑定回调(Java 7 基础实现,无默认方法等Java 8+特性) + */ @Override - public IBinder onBind(Intent intent) { - return null; - } + public IBinder onBind(Intent intent) { + // 返回 LocalBinder,使Activity能通过Binder获取MainService实例 + return mLocalBinder; + } + /** + * 服务创建回调(初始化单例、上下文、配置、服务连接等) + */ @Override public void onCreate() { LogUtils.d(TAG, "onCreate"); super.onCreate(); - sInstance = this; + sInstance = this; sAppContext = getApplicationContext(); + // 初始化 LocalBinder(关键:将MainService实例传入Binder) + mLocalBinder = new LocalBinder(this); + _mIsServiceRunning = false; mAppConfigsUtil = AppConfigsUtil.getInstance(this); + // 初始化服务连接(Java 7 显式判断,无Optional) if (mMyServiceConnection == null) { mMyServiceConnection = new MyServiceConnection(); } - run(); + run(); // 启动服务核心逻辑 } + /** + * 服务核心逻辑(启动前台服务、初始化GPS、加载数据等) + */ public void run() { if (mAppConfigsUtil.isEnableMainService(true)) { - if (_mIsServiceRunning == false) { + if (!_mIsServiceRunning) { _mIsServiceRunning = true; - wakeupAndBindAssistant(); + wakeupAndBindAssistant(); // 唤醒并绑定辅助服务 + // 启动前台服务(Java 7 显式调用,无方法引用) String initialStatus = "[ Positions ] is in Service."; NotificationUtil.createForegroundServiceNotification(this, initialStatus); startForeground(NotificationUtil.FOREGROUND_SERVICE_NOTIFICATION_ID, - NotificationUtil.createForegroundServiceNotification(this, initialStatus)); + NotificationUtil.createForegroundServiceNotification(this, initialStatus)); + // 初始化GPS相关(Java 7 基础API调用) mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE); - initGpsLocationListener(); - startGpsLocation(); + initGpsLocationListener(); + startGpsLocation(); - PositionModel.loadBeanList(MainService.this, mPositionList, PositionModel.class); - PositionTaskModel.loadBeanList(MainService.this, mTaskList, PositionTaskModel.class); + // 加载本地数据(Java 7 静态方法调用,无方法引用) + PositionModel.loadBeanList(MainService.this, mPositionList, PositionModel.class); + PositionTaskModel.loadBeanList(MainService.this, mTaskList, PositionTaskModel.class); + // 提示与日志(Java 7 基础调用) ToastUtils.show(initialStatus); LogUtils.i(TAG, initialStatus); } } } - public boolean isServiceRunning() { - return _mIsServiceRunning; - } - - public interface SMSListener { - void speakMessage(); + /** + * 获取服务运行状态 + * @return true=运行中,false=未运行 + */ + public boolean isServiceRunning() { + return _mIsServiceRunning; } + /** + * 服务销毁回调(清理资源、停止GPS、清空数据、反注册监听等) + */ @Override public void onDestroy() { super.onDestroy(); - sInstance = null; + sInstance = null; + // 清理资源(Java 7 顺序调用,无Stream等特性) stopGpsLocation(); clearAllData(); stopForeground(true); - // 4. 新增:服务销毁时清空所有监听者(彻底释放资源) + // 清理所有监听者(Java 7 加锁+清空,避免内存泄漏) synchronized (mListenerLock) { mGpsListeners.clear(); - LogUtils.d(TAG, "MainService销毁,已清空所有GpsUpdateListener"); + mTaskListeners.clear(); } + // 重置状态变量 _mIsServiceRunning = false; isGpsEnabled = false; mLocationManager = null; } - // 原有get/set、数据操作方法(保持不变) - public ArrayList getPositionList() { return mPositionList; } - public ArrayList getPositionTasksList() { return mTaskList; } - //1. 获取当前GPS位置(供外部类获取最新位置) - public PositionModel getCurrentGpsPosition() { - return mCurrentGpsPosition; - } - //2. 补全未实现的 removePosition 方法(原代码仅打印日志,无实际逻辑) - public void removePosition(String targetPosId) { - if (TextUtils.isEmpty(targetPosId) || mPositionList == null) { - LogUtils.w(TAG, "removePosition:参数无效(ID为空或列表未初始化)"); - return; - } - // 遍历删除目标位置(根据ID匹配) - Iterator 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; - } - } - } + // ========================================================================= + // 位置操作方法(Java 7 语法,全迭代器/基础循环,无Java 8+特性) + // ========================================================================= + /** + * 获取所有位置数据(返回原列表,供外部读取) + * @return 位置列表 + */ + public ArrayList getPositionList() { + return mPositionList; + } - //3. 补全未实现的 updatePosition 方法(原代码仅打印日志,无实际逻辑) - public void updatePosition(PositionModel updatedPos) { - 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; - } - } - } + /** + * 获取当前GPS位置 + * @return 当前GPS定位模型(未获取时返回null) + */ + public PositionModel getCurrentGpsPosition() { + return mCurrentGpsPosition; + } - //4. 补全未实现的 syncAllPositionTasks 方法(原代码仅打印日志,无实际逻辑) - public void syncAllPositionTasks(ArrayList newTaskList) { - 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); - } + /** + * 删除指定位置(Java 7 迭代器安全删除) + * @param targetPosId 待删除位置的ID + */ + public void removePosition(String targetPosId) { + if (TextUtils.isEmpty(targetPosId) || mPositionList.isEmpty()) { + LogUtils.w(TAG, "removePosition:参数无效"); + return; + } + // 迭代器遍历删除(Java 7 安全方式) + Iterator iter = mPositionList.iterator(); + while (iter.hasNext()) { + PositionModel pos = iter.next(); + if (targetPosId.equals(pos.getPositionId())) { + iter.remove(); + savePositionList(); + break; + } + } + } - public void addPosition(PositionModel newPos) { - mPositionList.add(newPos); savePositionList(); - } + /** + * 更新位置数据(Java 7 基础for循环,无Stream筛选) + * @param updatedPos 更新后的位置模型 + */ + public void updatePosition(PositionModel updatedPos) { + if (updatedPos == null || TextUtils.isEmpty(updatedPos.getPositionId()) || mPositionList.isEmpty()) { + LogUtils.w(TAG, "updatePosition:参数无效"); + return; + } + // 基础for循环查找并更新(Java 7 标准写法) + for (int i = 0; i < mPositionList.size(); i++) { + PositionModel oldPos = mPositionList.get(i); + if (updatedPos.getPositionId().equals(oldPos.getPositionId())) { + mPositionList.set(i, updatedPos); + savePositionList(); + break; + } + } + } - 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(); - mCurrentGpsPosition = null; - LogUtils.d(TAG, "clearAllData:所有数据已清空(位置/任务/GPS/可见位置)"); - } + /** + * 同步所有位置任务(全量替换,用于批量更新) + * @param newTaskList 新的任务列表 + */ + public void syncAllPositionTasks(ArrayList newTaskList) { + if (newTaskList == null) { + LogUtils.w(TAG, "syncAllPositionTasks:新列表为空"); + return; + } + // 全量替换+持久化+通知(Java 7 基础集合操作) + mTaskList.clear(); + mTaskList.addAll(newTaskList); + saveTaskList(); + notifyTaskUpdated(); + } - // ---------------------- 修改:syncCurrentGpsPosition(添加监听通知) ---------------------- - public void syncCurrentGpsPosition(PositionModel position) { - if (position == null) { - LogUtils.w(TAG, "syncCurrentGpsPosition:GPS位置为空,同步失败"); - return; - } - this.mCurrentGpsPosition = position; - LogUtils.d(TAG, "syncCurrentGpsPosition:同步成功(纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + ")"); + /** + * 新增位置(Java 7 增强for循环去重,无Stream) + * @param newPos 待新增的位置模型 + */ + public void addPosition(PositionModel newPos) { + if (newPos == null) { + LogUtils.w(TAG, "addPosition:位置为空"); + return; + } + // 位置去重(Java 7 增强for循环,无Stream.filter) + boolean isDuplicate = false; + for (PositionModel pos : mPositionList) { + if (newPos.getPositionId().equals(pos.getPositionId())) { + isDuplicate = true; + break; + } + } + if (!isDuplicate) { + mPositionList.add(newPos); + savePositionList(); + } + } - // 新增:通知所有外部监听者——GPS位置已更新 + /** + * 持久化位置数据(Java 7 静态方法调用,保持原逻辑) + */ + void savePositionList() { + LogUtils.d(TAG, String.format("savePositionList : size=%d", mPositionList.size())); + PositionModel.saveBeanList(MainService.this, mPositionList, PositionModel.class); + } + + /** + * 持久化任务数据(Java 7 静态方法调用,保持原逻辑) + */ + void saveTaskList() { + LogUtils.d(TAG, String.format("saveTaskList : size=%d", mTaskList.size())); + PositionTaskModel.saveBeanList(MainService.this, mTaskList, PositionTaskModel.class); + } + + /** + * 清空所有数据(位置+任务+GPS缓存,Java 7 集合clear方法) + */ + public void clearAllData() { + mPositionList.clear(); + mTaskList.clear(); + mCurrentGpsPosition = null; + LogUtils.d(TAG, "clearAllData:已清空所有数据"); + } + + /** + * 同步当前GPS位置(更新缓存+通知监听者+同步通知栏,全Java 7 语法) + * @param position 最新GPS位置模型 + */ + public void syncCurrentGpsPosition(PositionModel position) { + if (position == null) { + LogUtils.w(TAG, "syncCurrentGpsPosition:位置为空"); + return; + } + this.mCurrentGpsPosition = position; + LogUtils.d(TAG, "syncCurrentGpsPosition:成功(纬度=" + position.getLatitude() + ",经度=" + position.getLongitude() + ")"); notifyAllGpsListeners(position); - if (_mIsServiceRunning) { - syncGpsStatusToNotification(); - } - } + // 服务运行中才同步通知栏状态 + if (_mIsServiceRunning) { + syncGpsStatusToNotification(); + } + } - private void syncGpsStatusToNotification() { - if (!_mIsServiceRunning || mCurrentGpsPosition == null) { - return; - } - final String gpsStatus = String.format( - "GPS位置:北纬%.4f° 东经%.4f° | 计算中(可见位置:%d个)", + /** + * 同步GPS状态到前台通知(Java 7 匿名Runnable切换主线程,无Lambda) + */ + private void syncGpsStatusToNotification() { + if (!_mIsServiceRunning || mCurrentGpsPosition == null) { + return; + } + // 格式化通知内容(Java 7 String.format,无String.join等Java 8+方法) + final String gpsStatus = String.format( + "GPS位置:北纬%.4f° 东经%.4f° | 可见位置:%d个", mCurrentGpsPosition.getLatitude(), mCurrentGpsPosition.getLongitude(), - 666 - ); - if (Looper.myLooper() == Looper.getMainLooper()) { - NotificationUtil.updateForegroundServiceStatus(this, gpsStatus); - } else { - new android.os.Handler(Looper.getMainLooper()).post(new Runnable() { + mVisiblePositionIds.size() + ); + // 主线程判断+切换(Java 7 匿名内部类) + if (Looper.myLooper() == Looper.getMainLooper()) { + NotificationUtil.updateForegroundServiceStatus(this, gpsStatus); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { NotificationUtil.updateForegroundServiceStatus(MainService.this, gpsStatus); } }); - } - } + } + } - // 原有距离计算、Haversine公式等方法(保持不变) - private void calculateVisiblePositionDistance() { /* 原有逻辑不变 */ } - private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) { - // 修复Haversine公式符号错误(原代码逻辑错误,此处一并修正,否则距离计算为负) - final double EARTH_RADIUS = 6371000; + /** + * 计算可见位置距离(原逻辑保留,Java 7 语法兼容,无Stream/并行流) + */ + private void calculateVisiblePositionDistance() { + // 原有逻辑(Java 7 语法适配:用迭代器/基础循环,无Lambda/forEach) + // 注:原代码标注“略”,此处保持空实现,实际使用时补充具体逻辑 + } + + /** + * 计算两点间距离(Haversine公式,纯Java 7 基础API,无数学工具类依赖) + * @param gpsLat GPS纬度 + * @param gpsLon GPS经度 + * @param posLat 目标位置纬度 + * @param posLon 目标位置经度 + * @return 两点间距离(单位:米) + */ + private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) { + final double EARTH_RADIUS = 6371000; // 地球半径(米) double latDiff = Math.toRadians(posLat - gpsLat); double lonDiff = Math.toRadians(posLon - gpsLon); - // 原代码错误使用“-”,修正为“+”(Haversine公式核心正确逻辑) + // Haversine公式核心计算(Java 7 基础数学方法) double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2) + Math.cos(Math.toRadians(gpsLat)) * Math.cos(Math.toRadians(posLat)) * Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS * c; } - public void forceRefreshDistance() { /* 原有逻辑不变 */ } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - run(); - return mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); + /** + * 强制刷新所有位置距离(GPS更新后调用,计算距离+校验任务触发条件) + */ + public void forceRefreshDistance() { + if (mCurrentGpsPosition == null || mPositionList.isEmpty()) { + LogUtils.w(TAG, "forceRefreshDistance:GPS未获取或位置为空"); + return; + } + + // 遍历所有位置计算距离(Java 7 增强for循环,无Stream) + for (PositionModel pos : mPositionList) { + if (pos.isEnableRealPositionDistance()) { + try { + double distance = calculateHaversineDistance( + mCurrentGpsPosition.getLatitude(), + mCurrentGpsPosition.getLongitude(), + pos.getLatitude(), + pos.getLongitude() + ); + pos.setRealPositionDistance(distance); + } catch (Exception e) { + pos.setRealPositionDistance(-1); // 标记距离计算失败 + LogUtils.e(TAG, "计算距离失败:" + e.getMessage()); + } + } else { + pos.setRealPositionDistance(-1); // 未启用距离计算,标记为无效 + } + } + + // 距离刷新后校验任务触发条件+通知GPS监听者 + checkAllTaskTriggerCondition(); + notifyAllGpsListeners(mCurrentGpsPosition); } + + // ========================================================================= + // 任务触发相关方法(Java 7 语法,全迭代器遍历,无Java 8+特性) + // ========================================================================= + /** + * 校验所有任务触发条件(距离达标则触发任务通知) + */ + private void checkAllTaskTriggerCondition() { + if (mCurrentGpsPosition == null || mPositionList.isEmpty() || mTaskList.isEmpty()) { + LogUtils.d(TAG, "checkAllTaskTriggerCondition:跳过校验(GPS/位置/任务为空)"); + return; + } + + LogUtils.d(TAG, "checkAllTaskTriggerCondition:开始校验(任务总数=" + mTaskList.size() + ")"); + // 迭代器遍历任务(Java 7 安全遍历,避免并发修改异常) + Iterator taskIter = mTaskList.iterator(); + while (taskIter.hasNext()) { + PositionTaskModel task = taskIter.next(); + // 仅校验“已启用”且“绑定有效位置”的任务 + if (!task.isEnable() || TextUtils.isEmpty(task.getPositionId())) { + continue; + } + + // 查找任务绑定的位置(Java 7 迭代器遍历位置列表) + PositionModel bindPos = null; + Iterator posIter = mPositionList.iterator(); + while (posIter.hasNext()) { + PositionModel pos = posIter.next(); + if (task.getPositionId().equals(pos.getPositionId())) { + bindPos = pos; + break; + } + } + if (bindPos == null) { + LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":绑定位置不存在,跳过"); + task.setIsBingo(false); + continue; + } + + // 校验距离条件(判断是否满足任务触发阈值) + double currentDistance = bindPos.getRealPositionDistance(); + if (currentDistance < 0) { + LogUtils.w(TAG, "任务ID=" + task.getTaskId() + ":距离计算失败,跳过"); + task.setIsBingo(false); + continue; + } + + boolean isTriggered = false; + int taskDistance = task.getDiscussDistance(); + // 任务触发条件:大于/小于指定距离(Java 7 基础判断,无三元运算符嵌套) + if (task.isGreaterThan()) { + isTriggered = currentDistance > taskDistance; + } else if (task.isLessThan()) { + isTriggered = currentDistance < taskDistance; + } + + // 更新任务触发状态+发送通知(状态变化时才处理) + if (task.isBingo() != isTriggered) { + task.setIsBingo(isTriggered); + if (isTriggered) { + sendTaskTriggerNotification(task, bindPos, currentDistance); + } + } + } + saveTaskList(); // 持久化更新后的任务状态 + } + + /** + * 发送任务触发通知(更新前台通知+显示Toast,Java 7 匿名Runnable切换主线程) + * @param task 触发的任务 + * @param bindPos 任务绑定的位置 + * @param currentDistance 当前距离 + */ + private void sendTaskTriggerNotification(PositionTaskModel task, PositionModel bindPos, double currentDistance) { + if (!_mIsServiceRunning) { + return; + } + + // 格式化通知内容(Java 7 String.format,无TextBlock等Java 15+特性) + final String triggerContent = String.format( + "任务触发:%s\n位置:%s\n当前距离:%.1f米(条件:%s%d米)", + task.getTaskDescription(), + bindPos.getMemo(), + currentDistance, + task.isGreaterThan() ? ">" : "<", + task.getDiscussDistance() + ); + + // 更新前台通知(主线程判断+切换) + updateNotificationGpsStatus(triggerContent); + + // 显示Toast(主线程安全调用,Java 7 匿名内部类) + if (Looper.myLooper() == Looper.getMainLooper()) { + ToastUtils.show(triggerContent); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + ToastUtils.show(triggerContent); + } + }); + } + LogUtils.i(TAG, "任务触发通知:" + triggerContent); + } + + + // ========================================================================= + // 服务生命周期+辅助服务相关(Java 7 语法,无Lambda/方法引用) + // ========================================================================= + /** + * 服务启动命令(每次startService调用时触发,重启服务核心逻辑) + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + run(); // 重启服务核心逻辑(保证服务启动后进入运行状态) + // 返回START_STICKY:服务被异常杀死后,系统会尝试重启(原逻辑保留) + return mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY : super.onStartCommand(intent, flags, startId); + } + + /** + * 服务连接内部类(Java 7 静态内部类,避免持有外部类强引用导致内存泄漏) + */ private class MyServiceConnection implements ServiceConnection { @Override - public void onServiceConnected(ComponentName name, IBinder service) {} + public void onServiceConnected(ComponentName name, IBinder service) { + // 原逻辑保留(空实现,如需绑定辅助服务可补充具体逻辑) + } + @Override public void onServiceDisconnected(ComponentName name) { + // 辅助服务断开时,重新唤醒绑定(原逻辑保留) if (mAppConfigsUtil.isEnableMainService(true)) { wakeupAndBindAssistant(); } } } + /** + * 唤醒并绑定辅助服务(检查服务状态,未存活则启动+绑定) + */ void wakeupAndBindAssistant() { - if (ServiceUtil.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) { + // 检查辅助服务是否存活(Java 7 静态方法调用,无方法引用) + if (!ServiceUtil.isServiceAlive(getApplicationContext(), AssistantService.class.getName())) { + // 启动+绑定辅助服务(Java 7 显式Intent,无Lambda) startService(new Intent(MainService.this, AssistantService.class)); bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); } } + + // ========================================================================= + // GPS相关核心方法(Java 7 语法,匿名内部类实现LocationListener,无Lambda) + // ========================================================================= + /** + * 构造函数(Java 7 显式初始化线程池+GPS监听器,无默认构造函数简化) + */ public MainService() { distanceExecutor = Executors.newSingleThreadScheduledExecutor(); initGpsLocationListener(); } - // ---------------------- 修改:initGpsLocationListener(添加GPS状态通知) ---------------------- + /** + * 初始化GPS监听器(Java 7 匿名内部类实现LocationListener,无Lambda) + */ private void initGpsLocationListener() { - LogUtils.d(TAG, "initGpsLocationListener()"); + LogUtils.d(TAG, "initGpsLocationListener"); mGpsLocationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { if (location != null) { + // 封装GPS位置为PositionModel(Java 7 显式setter调用) PositionModel gpsPos = new PositionModel(); gpsPos.setLatitude(location.getLatitude()); gpsPos.setLongitude(location.getLongitude()); gpsPos.setPositionId("CURRENT_GPS_POS"); gpsPos.setMemo("实时GPS位置"); - syncCurrentGpsPosition(gpsPos); // 此方法已包含监听通知 + // 同步GPS位置+刷新距离+日志(原逻辑保留) + syncCurrentGpsPosition(gpsPos); + forceRefreshDistance(); LogUtils.d(TAG, "GPS位置更新:纬度=" + location.getLatitude() + ",经度=" + location.getLongitude()); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { + // 仅处理GPS_PROVIDER状态变化(Java 7 基础判断) if (provider.equals(LocationManager.GPS_PROVIDER)) { String statusDesc = ""; + // 状态枚举判断(Java 7 switch,无增强switch) switch (status) { case LocationProvider.AVAILABLE: statusDesc = "GPS状态:已就绪(可用)"; - LogUtils.d(TAG, statusDesc); break; case LocationProvider.OUT_OF_SERVICE: - statusDesc = "GPS状态:无服务(信号弱/无信号)"; - LogUtils.w(TAG, statusDesc); + statusDesc = "GPS状态:无服务(信号弱)"; break; case LocationProvider.TEMPORARILY_UNAVAILABLE: - statusDesc = "GPS状态:临时不可用(如遮挡)"; - LogUtils.w(TAG, statusDesc); + statusDesc = "GPS状态:临时不可用(遮挡)"; break; } - // 新增:通知外部监听者——GPS状态变化 + LogUtils.d(TAG, statusDesc); notifyAllGpsStatusListeners(statusDesc); updateNotificationGpsStatus(statusDesc); } @@ -495,11 +797,11 @@ public class MainService extends Service { @Override public void onProviderEnabled(String provider) { + // GPS启用时更新状态+通知+重启定位(Java 7 基础逻辑) if (provider.equals(LocationManager.GPS_PROVIDER)) { isGpsEnabled = true; String statusDesc = "GPS已开启(用户手动打开)"; LogUtils.d(TAG, statusDesc); - // 新增:通知外部监听者——GPS已开启 notifyAllGpsStatusListeners(statusDesc); updateNotificationGpsStatus("GPS已开启,正在获取位置..."); startGpsLocation(); @@ -508,12 +810,12 @@ public class MainService extends Service { @Override public void onProviderDisabled(String provider) { + // GPS禁用时清空状态+通知+提示(Java 7 基础逻辑) if (provider.equals(LocationManager.GPS_PROVIDER)) { isGpsEnabled = false; mCurrentGpsPosition = null; String statusDesc = "GPS已关闭(用户手动关闭)"; LogUtils.w(TAG, statusDesc); - // 新增:通知外部监听者——GPS已关闭 notifyAllGpsStatusListeners(statusDesc); updateNotificationGpsStatus("GPS已关闭,请在设置中开启"); ToastUtils.show("GPS已关闭,无法获取位置,请在设置中开启"); @@ -522,28 +824,36 @@ public class MainService extends Service { }; } - // 原有checkGpsReady方法(保持不变) + /** + * 检查GPS就绪状态(权限+启用状态,Java 7 基础权限判断,无Stream) + * @return true=GPS就绪,false=未就绪 + */ private boolean checkGpsReady() { - isGpsPermissionGranted = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED; + // 检查定位权限(Java 7 基础权限API,无权限请求框架依赖) + isGpsPermissionGranted = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + // 初始化LocationManager(Java 7 显式判断,无Optional) if (mLocationManager == null) { - mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } + // 检查GPS是否启用(系统LocationManager API,Java 7 兼容) isGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + // 权限未授予:提示+日志+通知 if (!isGpsPermissionGranted) { String tip = "GPS准备失败:缺少精确定位权限"; LogUtils.e(TAG, tip); - notifyAllGpsStatusListeners(tip); // 新增:通知监听者权限缺失 + notifyAllGpsStatusListeners(tip); updateNotificationGpsStatus("缺少定位权限,无法获取GPS"); ToastUtils.show("请授予定位权限,否则无法获取GPS位置"); return false; } + // GPS未启用:提示+日志+通知 if (!isGpsEnabled) { String tip = "GPS准备失败:系统GPS未开启"; LogUtils.e(TAG, tip); - notifyAllGpsStatusListeners(tip); // 新增:通知监听者GPS未开启 + notifyAllGpsStatusListeners(tip); updateNotificationGpsStatus("GPS未开启,请在设置中打开"); ToastUtils.show("GPS已关闭,请在设置中开启以获取位置"); return false; @@ -553,72 +863,227 @@ public class MainService extends Service { return true; } - // 原有startGpsLocation方法(保持不变) + /** + * 启动GPS定位(Java 7 异常处理,无try-with-resources,显式捕获SecurityException) + */ private void startGpsLocation() { if (!checkGpsReady()) { return; } try { + // 注册GPS位置更新(Java 7 标准LocationManager API,指定Looper为主线程) 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); // 触发监听通知 - LogUtils.d(TAG, "已获取最近缓存的GPS位置:纬度=" + lastKnownLocation.getLatitude()); + syncCurrentGpsPosition(lastGpsPos); + LogUtils.d(TAG, "已获取缓存GPS位置:纬度=" + lastKnownLocation.getLatitude()); } else { String tip = "无缓存GPS位置,等待实时定位..."; LogUtils.d(TAG, tip); - notifyAllGpsStatusListeners(tip); // 新增:通知监听者等待定位 + notifyAllGpsStatusListeners(tip); updateNotificationGpsStatus("GPS搜索中(请移至开阔地带)"); } } catch (SecurityException e) { - String error = "启动GPS定位失败(权限异常):" + e.getMessage(); + // 定位权限异常(Java 7 显式捕获,无Lambda异常处理) + String error = "启动GPS失败(权限异常):" + e.getMessage(); LogUtils.e(TAG, error); - notifyAllGpsStatusListeners(error); // 新增:通知监听者权限异常 + notifyAllGpsStatusListeners(error); isGpsPermissionGranted = false; updateNotificationGpsStatus("定位权限异常,无法获取GPS"); } catch (Exception e) { - String error = "启动GPS定位失败:" + e.getMessage(); + // 其他异常(如LocationManager为空、系统服务异常等) + String error = "启动GPS失败:" + e.getMessage(); LogUtils.e(TAG, error); - notifyAllGpsStatusListeners(error); // 新增:通知监听者启动失败 + notifyAllGpsStatusListeners(error); updateNotificationGpsStatus("GPS启动失败,尝试重试..."); } } - // 原有stopGpsLocation方法(保持不变) + /** + * 停止GPS定位(Java 7 异常处理,移除监听器避免内存泄漏) + */ private void stopGpsLocation() { + // 校验参数:避免空指针+权限未授予时调用 if (mLocationManager != null && mGpsLocationListener != null && isGpsPermissionGranted) { try { mLocationManager.removeUpdates(mGpsLocationListener); String tip = "GPS定位已停止(移除监听器)"; LogUtils.d(TAG, tip); - notifyAllGpsStatusListeners(tip); // 新增:通知监听者GPS已停止 + notifyAllGpsStatusListeners(tip); } catch (Exception e) { - String error = "停止GPS定位失败:" + e.getMessage(); + String error = "停止GPS失败:" + e.getMessage(); LogUtils.e(TAG, error); - notifyAllGpsStatusListeners(error); // 新增:通知监听者停止失败 + notifyAllGpsStatusListeners(error); } } } - // 原有updateNotificationGpsStatus方法(保持不变) - private void updateNotificationGpsStatus(String statusText) { + /** + * 更新前台通知的GPS状态(Java 7 主线程切换,匿名Runnable实现) + * @param statusText 通知显示的状态文本 + */ + private void updateNotificationGpsStatus(final String statusText) { if (_mIsServiceRunning) { - NotificationUtil.updateForegroundServiceStatus(this, statusText); + // 判断当前线程是否为主线程,避免UI操作在子线程 + if (Looper.myLooper() == Looper.getMainLooper()) { + NotificationUtil.updateForegroundServiceStatus(this, statusText); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + NotificationUtil.updateForegroundServiceStatus(MainService.this, statusText); + } + }); + } } } + + // ========================================================================= + // GPS监听通知相关方法(Java 7 迭代器遍历弱引用集合,避免内存泄漏) + // ========================================================================= + /** + * 通知所有GPS监听者位置更新(Java 7 迭代器+弱引用管理,无Stream) + * @param currentGpsPos 当前最新GPS位置 + */ + private void notifyAllGpsListeners(PositionModel currentGpsPos) { + if (currentGpsPos == null || mGpsListeners.isEmpty()) { + return; + } + synchronized (mListenerLock) { + Iterator> iter = mGpsListeners.iterator(); + while (iter.hasNext()) { + WeakReference ref = iter.next(); + GpsUpdateListener listener = ref.get(); + if (listener != null) { + notifySingleListener(listener, currentGpsPos); + } else { + iter.remove(); // 清理已被GC回收的监听者,避免内存泄漏 + } + } + } + } + + /** + * 通知单个GPS监听者位置更新(主线程安全,Java 7 匿名Runnable) + * @param listener 单个监听者 + * @param currentGpsPos 当前GPS位置 + */ + private void notifySingleListener(final GpsUpdateListener listener, final PositionModel currentGpsPos) { + if (Looper.myLooper() == Looper.getMainLooper()) { + listener.onGpsPositionUpdated(currentGpsPos); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + listener.onGpsPositionUpdated(currentGpsPos); + } + }); + } + } + + /** + * 通知所有GPS监听者状态变化(如GPS开启/关闭、信号弱等,Java 7 迭代器) + * @param status GPS状态描述文本 + */ + private void notifyAllGpsStatusListeners(final String status) { + if (status == null || mGpsListeners.isEmpty()) { + return; + } + synchronized (mListenerLock) { + Iterator> iter = mGpsListeners.iterator(); + while (iter.hasNext()) { + WeakReference ref = iter.next(); + final GpsUpdateListener listener = ref.get(); + if (listener != null) { + // 主线程切换,避免监听者在子线程处理UI + if (Looper.myLooper() == Looper.getMainLooper()) { + listener.onGpsStatusChanged(status); + } else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + listener.onGpsStatusChanged(status); + } + }); + } + } else { + iter.remove(); // 清理无效弱引用 + } + } + } + } + + /** + * 注册GPS更新监听(Java 7 弱引用添加,避免监听者内存泄漏) + * @param listener GPS更新监听者(如Activity/Adapter) + */ + public void registerGpsUpdateListener(GpsUpdateListener listener) { + if (listener == null) { + LogUtils.w(TAG, "registerGpsUpdateListener:监听者为空"); + return; + } + synchronized (mListenerLock) { + mGpsListeners.add(new WeakReference(listener)); + LogUtils.d(TAG, "GPS监听注册成功,当前数量:" + mGpsListeners.size()); + // 注册后立即推送当前GPS位置(避免监听者错过初始数据) + if (mCurrentGpsPosition != null) { + notifySingleListener(listener, mCurrentGpsPosition); + } + } + } + + /** + * 反注册GPS更新监听(Java 7 迭代器清理,避免内存泄漏) + * @param listener 待反注册的GPS监听者 + */ + public void unregisterGpsUpdateListener(GpsUpdateListener listener) { + if (listener == null) { + LogUtils.w(TAG, "unregisterGpsUpdateListener:监听者为空"); + return; + } + synchronized (mListenerLock) { + Iterator> iter = mGpsListeners.iterator(); + while (iter.hasNext()) { + WeakReference ref = iter.next(); + // 匹配目标监听者或已回收的弱引用,直接移除 + if (ref.get() == listener || ref.get() == null) { + iter.remove(); + LogUtils.d(TAG, "GPS监听反注册成功,当前数量:" + mGpsListeners.size()); + break; + } + } + } + } + + // 补全 LocalBinder 定义(与 LocationActivity 中的 LocalBinder 保持一致) + // 注意:若 LocationActivity 已定义 LocalBinder,此处可删除;建议统一在 MainService 中定义,避免重复 + public class LocalBinder extends android.os.Binder { + private MainService mService; + + public LocalBinder(MainService service) { + this.mService = service; + } + + public MainService getService() { + return mService; + } + } + } +