From f5b2500f06b5d5d8a437110248ed0b0f01d65dec Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Tue, 30 Sep 2025 21:18:29 +0800 Subject: [PATCH] 20250930_211825_255 --- positions/build.gradle | 6 +- positions/build.properties | 4 +- positions/src/main/AndroidManifest.xml | 32 +- .../studio/positions/MainActivity.java | 22 +- .../activities/LocationActivity.java | 39 +- .../positions/adapters/PositionAdapter.java | 955 +++++++++++------- .../positions/models/PositionModel.java | 10 + .../services/DistanceRefreshService.java | 340 +++++++ .../src/main/res/layout/activity_main.xml | 17 +- 9 files changed, 996 insertions(+), 429 deletions(-) create mode 100644 positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java diff --git a/positions/build.gradle b/positions/build.gradle index b7aaa28..356a27a 100644 --- a/positions/build.gradle +++ b/positions/build.gradle @@ -70,7 +70,7 @@ dependencies { //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' //api 'androidx.fragment:fragment:1.1.0' - api 'cc.winboll.studio:libaes:15.9.3' - api 'cc.winboll.studio:libapputils:15.8.5' - api 'cc.winboll.studio:libappbase:15.9.5' + api 'cc.winboll.studio:libaes:15.10.2' + api 'cc.winboll.studio:libapputils:15.10.2' + api 'cc.winboll.studio:libappbase:15.10.9' } diff --git a/positions/build.properties b/positions/build.properties index 45635f0..b620072 100644 --- a/positions/build.properties +++ b/positions/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Sep 30 17:50:26 HKT 2025 +#Tue Sep 30 13:02:23 GMT 2025 stageCount=4 libraryProject= baseVersion=15.0 publishVersion=15.0.3 -buildCount=0 +buildCount=9 baseBetaVersion=15.0.4 diff --git a/positions/src/main/AndroidManifest.xml b/positions/src/main/AndroidManifest.xml index 65f5e10..1b9f180 100644 --- a/positions/src/main/AndroidManifest.xml +++ b/positions/src/main/AndroidManifest.xml @@ -2,20 +2,20 @@ - - - - - - - - - + + + + + + + + + - + android:required="false"/> + - - + + android:value="@integer/google_play_services_version"/> + + + - + \ No newline at end of file diff --git a/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java b/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java index 635b835..8d489c2 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java +++ b/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java @@ -1,17 +1,17 @@ package cc.winboll.studio.positions; +import android.content.Intent; import android.os.Bundle; +import android.view.View; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import cc.winboll.studio.libappbase.LogActivity; import cc.winboll.studio.libappbase.LogView; -import com.hjq.toast.ToastUtils; -import android.view.View; import cc.winboll.studio.positions.activities.LocationActivity; -import android.content.Intent; +import com.hjq.toast.ToastUtils; public class MainActivity extends AppCompatActivity { - LogView mLogView; @Override protected void onCreate(Bundle savedInstanceState) { @@ -20,19 +20,13 @@ public class MainActivity extends AppCompatActivity { Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar); setSupportActionBar(toolbar); - - mLogView = findViewById(R.id.logview); - - ToastUtils.show("onCreate"); } - @Override - protected void onResume() { - super.onResume(); - mLogView.start(); - } - public void onPositions(View view) { startActivity(new Intent(this, LocationActivity.class)); } + + public void onLog(View view) { + LogActivity.startLogActivity(this); + } } 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 ee63438..74cb561 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 @@ -411,29 +411,28 @@ public class LocationActivity extends AppCompatActivity { * 活动销毁:停止定位 + 停止距离刷新定时器(避免内存泄漏) */ @Override - protected void onDestroy() { - super.onDestroy(); + protected void onDestroy() { + super.onDestroy(); - // 1. 停止定位监听(释放定位资源) - if (fusedLocationClient != null && locationCallback != null) { - fusedLocationClient.removeLocationUpdates(locationCallback); - } + // 1. 停止定位监听(原逻辑不变) + if (fusedLocationClient != null && locationCallback != null) { + fusedLocationClient.removeLocationUpdates(locationCallback); + } - // 2. 停止Adapter的距离刷新定时器(关键:避免Timer持有Context导致内存泄漏) - if (positionAdapter != null) { - positionAdapter.stopTimer(); - } - - // 3. 最后同步一次数据(确保所有修改保存) - try { - if (mPositionList != null && !mPositionList.isEmpty()) { - PositionModel.saveBeanList(this, mPositionList, PositionModel.class); - } - } catch (Exception e) { - e.printStackTrace(); - } - } + // 2. 关键:调用Adapter的stopTimer(内部已实现服务解绑,避免内存泄漏) + if (positionAdapter != null) { + positionAdapter.stopTimer(); // 此方法已重构为:解绑服务+清理资源 + } + // 3. 最后同步一次数据(原逻辑不变) + try { + if (mPositionList != null && !mPositionList.isEmpty()) { + PositionModel.saveBeanList(this, mPositionList, PositionModel.class); + } + } catch (Exception e) { + e.printStackTrace(); + } + } /** * 辅助工具:dp转px(适配不同屏幕分辨率) 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 d0110fa..986564a 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 @@ -3,10 +3,13 @@ package cc.winboll.studio.positions.adapters; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/09/29 20:25 - * @Describe 位置数据适配器(修复定时器:仅刷新实时距离,不干扰编辑中的任务;优化任务触发通知) + * @Describe 位置数据适配器(移除定时器,通过绑定服务获取距离更新) */ +import android.content.ComponentName; import android.content.Context; -import android.os.Handler; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; import android.os.Looper; import android.view.LayoutInflater; import android.view.MenuInflater; @@ -20,44 +23,82 @@ import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; + import androidx.annotation.NonNull; 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.DistanceRefreshService; import cc.winboll.studio.positions.views.PositionTaskListView; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import cc.winboll.studio.positions.utils.NotificationUtils; -public class PositionAdapter extends RecyclerView.Adapter { - // 常量定义(不变) +/** + * 位置列表适配器:管理位置视图渲染、服务绑定、距离UI更新 + * Java 7 适配:移除Lambda、Stream,使用匿名内部类+迭代器,明确泛型,删除Java8+API(如forEach) + */ +public class PositionAdapter extends RecyclerView.Adapter implements DistanceRefreshService.OnDistanceUpdateReceiver { public static final String TAG = "PositionAdapter"; + // 视图类型常量 private static final int VIEW_TYPE_SIMPLE = 1; private static final int VIEW_TYPE_EDIT = 2; - private static final long REFRESH_INTERVAL = 5000; + // 默认任务参数 private static final String DEFAULT_TASK_DESC = "新位置任务"; private static final int DEFAULT_TASK_DISTANCE = 100; - // 核心成员变量(新增:1.位置ID→索引映射 2.主线程Handler 3.任务触发标记) + // 核心成员变量(Java7:明确泛型声明与初始化) private final ArrayList mPositionList; private final ArrayList mAllPositionTasks; private final Context mContext; private PositionModel mCurrentGpsPosition; - private final Timer mDistanceTimer; - private final Handler mMainHandler; private final Map> mPositionTaskMap; - private final Map mVisibleDistanceViews = new HashMap<>(); - // 新增1:存储“位置ID→列表索引”映射(快速定位位置项,避免遍历) - private final Map mPositionIdToIndexMap = new HashMap<>(); - // 新增2:主线程Handler(确保UI更新在主线程,避免异常) - private final Handler mUiHandler = new Handler(Looper.getMainLooper()); + private final Map mVisibleDistanceViews = new HashMap(); + private final Map mPositionIdToIndexMap = new HashMap(); + private final android.os.Handler mUiHandler = new android.os.Handler(Looper.getMainLooper()); - // 接口回调(不变) + // 服务相关变量 + private DistanceRefreshService mDistanceService; + private boolean isServiceBound = false; + // Java7:匿名内部类实现ServiceConnection(替代Lambda) + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + DistanceRefreshService.DistanceBinder binder = (DistanceRefreshService.DistanceBinder) service; + mDistanceService = binder.getService(); + isServiceBound = true; + + // 绑定服务后初始化:设置接收器+同步数据 + mDistanceService.setOnDistanceUpdateReceiver(PositionAdapter.this); + mDistanceService.syncPositionList(mPositionList); + mDistanceService.syncAllPositionTasks(mAllPositionTasks); + mDistanceService.syncCurrentGpsPosition(mCurrentGpsPosition); + + // 同步已有的可见控件(解决“控件已添加但服务未收到”问题) + Iterator posIdIterator = mVisibleDistanceViews.keySet().iterator(); + while (posIdIterator.hasNext()) { + String positionId = posIdIterator.next(); + mDistanceService.addVisibleDistanceView(positionId); + LogUtils.d(TAG, "服务绑定后同步可见控件:位置ID=" + positionId); + } + + LogUtils.d(TAG, "DistanceRefreshService 绑定成功"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mDistanceService = null; + isServiceBound = false; + LogUtils.d(TAG, "DistanceRefreshService 意外断开"); + } + }; + + // 接口回调(外部监听) public interface OnDeleteClickListener { void onDeleteClick(int position); } @@ -74,39 +115,67 @@ public class PositionAdapter extends RecyclerView.Adapter positionList, ArrayList allPositionTasks) { this.mContext = context; + // Java7:显式非空判断(避免空指针) this.mPositionList = (positionList != null) ? positionList : new ArrayList(); this.mAllPositionTasks = (allPositionTasks != null) ? allPositionTasks : new ArrayList(); - this.mMainHandler = new Handler(Looper.getMainLooper()); - this.mDistanceTimer = new Timer(); this.mPositionTaskMap = new HashMap>(); - // 初始化任务映射表(不变) - for (PositionModel model : mPositionList) { + // 初始化任务映射表(Java7:双迭代器遍历) + Iterator modelIterator = mPositionList.iterator(); + while (modelIterator.hasNext()) { + PositionModel model = modelIterator.next(); String validPosId = model.getPositionId(); ArrayList matchedTasks = new ArrayList(); - for (PositionTaskModel task : this.mAllPositionTasks) { + + Iterator taskIterator = this.mAllPositionTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); if (task != null && validPosId.equals(task.getPositionId())) { matchedTasks.add(task); } } + mPositionTaskMap.put(validPosId, matchedTasks); - // 新增:初始化“位置ID→索引”映射(索引即列表中的位置) mPositionIdToIndexMap.put(validPosId, mPositionList.indexOf(model)); } - // 启动距离刷新定时器(不变) - startDistanceRefreshTimer(); + // 绑定服务 + bindDistanceRefreshService(); } - // 对外API:设置当前GPS位置(不变) + // ---------------------- 服务绑定/解绑 ---------------------- + private void bindDistanceRefreshService() { + if (!isServiceBound && mContext != null) { + Intent serviceIntent = new Intent(mContext, DistanceRefreshService.class); + mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + public void unbindDistanceRefreshService() { + if (isServiceBound && mContext != null) { + if (mDistanceService != null) { + mDistanceService.clearVisibleDistanceViews(); + mDistanceService.setOnDistanceUpdateReceiver(null); + } + mContext.unbindService(mServiceConnection); + isServiceBound = false; + mDistanceService = null; + LogUtils.d(TAG, "DistanceRefreshService 解绑成功"); + } + } + + // ---------------------- 对外API(数据更新) ---------------------- public void setCurrentGpsPosition(PositionModel currentGpsPosition) { this.mCurrentGpsPosition = currentGpsPosition; + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncCurrentGpsPosition(currentGpsPosition); + LogUtils.d(TAG, "Adapter同步GPS位置到服务:纬度=" + (currentGpsPosition != null ? currentGpsPosition.getLatitude() : 0.0f)); + } } - // 对外API:设置监听器(不变) public void setOnDeleteClickListener(OnDeleteClickListener listener) { this.mOnDeleteClickListener = listener; } @@ -119,94 +188,88 @@ public class PositionAdapter extends RecyclerView.Adapter entry : mVisibleDistanceViews.entrySet()) { - String positionId = entry.getKey(); - TextView distanceView = entry.getValue(); - - PositionModel targetModel = null; - for (PositionModel model : mPositionList) { - if (positionId.equals(model.getPositionId())) { - targetModel = model; - break; - } - } - - if (targetModel != null) { - bindRealDistance(distanceView, targetModel); - } - } - for (PositionTaskModel task : mAllPositionTasks) { - if (task.isBingo() && task.isEnable()) { - NotificationUtils.show(mContext, task.getTaskId(), task.getPositionId(), task.getTaskDescription()); - } - } - } - }); - } - }, 0, REFRESH_INTERVAL); + // ---------------------- 服务消息接收(距离更新) ---------------------- + @Override + public void onDistanceUpdate(final String positionId, final String distanceText, final int distanceColor) { + // 保障主线程更新(Java7:匿名内部类实现Runnable,替代Lambda) + if (Looper.myLooper() != Looper.getMainLooper()) { + mUiHandler.post(new Runnable() { + @Override + public void run() { + updateDistanceView(positionId, distanceText, distanceColor); + } + }); + } else { + updateDistanceView(positionId, distanceText, distanceColor); + } } - // ---------------------- 核心修复2:管理可见/不可见的距离TextView(不变) ---------------------- - @Override - public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) { - super.onViewAttachedToWindow(holder); - int position = holder.getAdapterPosition(); - if (position < 0 || position >= mPositionList.size()) { + /** + * 精准更新距离TextView(Java7:显式非空判断,避免空指针) + */ + private void updateDistanceView(String positionId, String distanceText, int distanceColor) { + // 1. 检查核心参数(避免无效更新) + if (positionId == null || distanceText == null || mVisibleDistanceViews.isEmpty()) { + LogUtils.w(TAG, "更新距离失败:参数无效(位置ID=" + positionId + ",距离文本=" + distanceText + ")"); return; } - PositionModel model = mPositionList.get(position); - String positionId = model.getPositionId(); - // 同步更新位置ID→索引映射(防止列表项位置变化导致索引失效) - mPositionIdToIndexMap.put(positionId, position); - - TextView distanceView = null; - if (holder instanceof SimpleViewHolder) { - distanceView = ((SimpleViewHolder) holder).tvSimpleRealDistance; - } else if (holder instanceof EditViewHolder) { - distanceView = ((EditViewHolder) holder).tvEditRealDistance; - } - - if (distanceView != null) { - mVisibleDistanceViews.put(positionId, distanceView); + // 2. 找到目标TextView并更新 + TextView distanceView = mVisibleDistanceViews.get(positionId); + if (distanceView != null && distanceView.isShown()) { // 仅更新“显示中”的控件 + LogUtils.d(TAG, "更新距离UI:位置ID=" + positionId + ",文本=" + distanceText); + distanceView.setText(distanceText); + distanceView.setTextColor(distanceColor); + // 同步更新任务状态 + refreshTaskBingoStatus(positionId); + } else { + // 控件不存在/不可见时,移除映射+同步服务 + if (mVisibleDistanceViews.containsKey(positionId)) { + mVisibleDistanceViews.remove(positionId); + LogUtils.w(TAG, "更新距离失败:位置ID=" + positionId + " 对应的TextView不存在/已移除"); + } + if (isServiceBound && mDistanceService != null) { + mDistanceService.removeVisibleDistanceView(positionId); + } } } @Override - public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { - super.onViewDetachedFromWindow(holder); - int position = holder.getAdapterPosition(); - if (position < 0 || position >= mPositionList.size()) { - return; + public int getColorRes(int resId) { + if (mContext != null) { + try { + // Java7:显式调用getResources(),避免空指针 + return mContext.getResources().getColor(resId); + } catch (Exception e) { + LogUtils.d(TAG, "获取颜色资源失败(resId=" + resId + ")" + e.getMessage()); + return mContext.getResources().getColor(R.color.colorGrayText); // 异常时返回默认灰色 + } } + return mContext.getResources().getColor(R.color.colorGrayText); + } - PositionModel model = mPositionList.get(position); - String positionId = model.getPositionId(); - if (mVisibleDistanceViews.containsKey(positionId)) { - mVisibleDistanceViews.remove(positionId); + // ---------------------- 任务状态刷新 ---------------------- + private void refreshTaskBingoStatus(String positionId) { + Integer targetPosIndex = mPositionIdToIndexMap.get(positionId); + if (targetPosIndex != null && targetPosIndex >= 0 && targetPosIndex < mPositionList.size()) { + notifyItemChanged(targetPosIndex); + LogUtils.d(TAG, "刷新任务状态:位置ID=" + positionId + ",列表索引=" + targetPosIndex); } } - // ---------------------- RecyclerView 核心方法(不变) ---------------------- + // ---------------------- RecyclerView 核心方法 ---------------------- @Override public int getItemViewType(int position) { + // Java7:显式非空+范围判断(避免越界) + if (position < 0 || position >= mPositionList.size()) { + LogUtils.w(TAG, "getItemViewType:无效列表位置(position=" + position + ")"); + return VIEW_TYPE_SIMPLE; // 异常时返回默认类型 + } PositionModel model = mPositionList.get(position); return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; } @@ -226,10 +289,15 @@ public class PositionAdapter extends RecyclerView.Adapter= mPositionList.size()) { + LogUtils.w(TAG, "onBindViewHolder:无效列表位置(position=" + position + ")"); + return; + } PositionModel model = mPositionList.get(position); - // 同步更新位置ID→索引映射(确保索引与当前列表项位置一致) mPositionIdToIndexMap.put(model.getPositionId(), position); + // 绑定对应视图 if (holder instanceof SimpleViewHolder) { bindSimpleView((SimpleViewHolder) holder, model, position); } else if (holder instanceof EditViewHolder) { @@ -242,34 +310,109 @@ public class PositionAdapter extends RecyclerView.Adapter= mPositionList.size()) { + LogUtils.w(TAG, "onViewAttached:无效列表位置(position=" + position + ")"); + return; + } + + PositionModel model = mPositionList.get(position); + String positionId = model.getPositionId(); + mPositionIdToIndexMap.put(positionId, position); // 同步索引 + + // 找到距离TextView(Java7:显式类型转换+非空判断) + TextView distanceView = null; + if (holder instanceof SimpleViewHolder) { + distanceView = ((SimpleViewHolder) holder).tvSimpleRealDistance; + } else if (holder instanceof EditViewHolder) { + distanceView = ((EditViewHolder) holder).tvEditRealDistance; + } + + // 仅添加有效控件,避免重复映射 + if (distanceView != null && !mVisibleDistanceViews.containsKey(positionId)) { + mVisibleDistanceViews.put(positionId, distanceView); + LogUtils.d(TAG, "视图附加:添加可见控件(位置ID=" + positionId + "),当前可见数量=" + mVisibleDistanceViews.size()); + // 同步到服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.addVisibleDistanceView(positionId); + } else { + LogUtils.w(TAG, "视图附加:服务未绑定,暂未同步控件(位置ID=" + positionId + ")"); + } + } + } + + @Override + public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { + super.onViewDetachedFromWindow(holder); + int position = holder.getAdapterPosition(); + if (position < 0 || position >= mPositionList.size()) { + LogUtils.w(TAG, "onViewDetached:无效列表位置(position=" + position + ")"); + return; + } + + PositionModel model = mPositionList.get(position); + String positionId = model.getPositionId(); + // 移除本地映射+同步服务 + if (mVisibleDistanceViews.containsKey(positionId)) { + mVisibleDistanceViews.remove(positionId); + LogUtils.d(TAG, "视图分离:移除可见控件(位置ID=" + positionId + "),当前可见数量=" + mVisibleDistanceViews.size()); + if (isServiceBound && mDistanceService != null) { + mDistanceService.removeVisibleDistanceView(positionId); + } + } + } + @Override public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { super.onViewRecycled(holder); int position = holder.getAdapterPosition(); if (position >= 0 && position < mPositionList.size()) { String positionId = mPositionList.get(position).getPositionId(); + // 清理本地映射 mVisibleDistanceViews.remove(positionId); - // 移除已回收位置的索引映射(避免内存泄漏) mPositionIdToIndexMap.remove(positionId); + LogUtils.d(TAG, "视图回收:清理控件映射(位置ID=" + positionId + ")"); + // 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.removeVisibleDistanceView(positionId); + } } + // 清理任务列表资源(Java7:显式非空判断) if (holder instanceof SimpleViewHolder) { - ((SimpleViewHolder) holder).ptlvSimpleTasks.clearData(); - ((SimpleViewHolder) holder).ptlvSimpleTasks.setOnTaskUpdatedListener(null); + SimpleViewHolder simpleHolder = (SimpleViewHolder) holder; + if (simpleHolder.ptlvSimpleTasks != null) { + simpleHolder.ptlvSimpleTasks.clearData(); + simpleHolder.ptlvSimpleTasks.setOnTaskUpdatedListener(null); + } } else if (holder instanceof EditViewHolder) { - ((EditViewHolder) holder).ptlvEditTasks.clearData(); - ((EditViewHolder) holder).ptlvEditTasks.setOnTaskUpdatedListener(null); + EditViewHolder editHolder = (EditViewHolder) holder; + if (editHolder.ptlvEditTasks != null) { + editHolder.ptlvEditTasks.clearData(); + editHolder.ptlvEditTasks.setOnTaskUpdatedListener(null); + } } } - // 绑定简单视图(不变) + // ---------------------- 视图绑定(简单视图/编辑视图) ---------------------- + /** + * 绑定简单视图(仅展示,无编辑功能) + */ private void bindSimpleView(final SimpleViewHolder holder, final PositionModel model, final int position) { + // 绑定基础数据(Java7:显式String.format,避免自动类型转换) holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude())); holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); holder.tvSimpleMemo.setText(String.format("备注:%s", model.getMemo())); - bindRealDistance(holder.tvSimpleRealDistance, model); + // 初始默认文本(记录日志,便于排查未覆盖问题) + holder.tvSimpleRealDistance.setText("实时距离:加载中..."); + holder.tvSimpleRealDistance.setTextColor(mContext.getResources().getColor(R.color.colorGrayText)); + LogUtils.d(TAG, "绑定简单视图:初始化距离文本(位置ID=" + model.getPositionId() + ")"); + // 长按编辑逻辑(Java7:匿名内部类实现OnLongClickListener+PopupMenu监听) holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { @@ -277,6 +420,7 @@ public class PositionAdapter extends RecyclerView.Adapter matchedTasks = getSafeTasks(currentPosId); - holder.ptlvSimpleTasks.clearData(); - holder.ptlvSimpleTasks.setViewStatus(PositionTaskListView.VIEW_MODE_SIMPLE); - if (holder.ptlvSimpleTasks.getAllTasks().isEmpty()) { - holder.ptlvSimpleTasks.init(matchedTasks, currentPosId); + if (holder.ptlvSimpleTasks != null) { + holder.ptlvSimpleTasks.clearData(); + holder.ptlvSimpleTasks.setViewStatus(PositionTaskListView.VIEW_MODE_SIMPLE); + if (holder.ptlvSimpleTasks.getAllTasks().isEmpty()) { + holder.ptlvSimpleTasks.init(matchedTasks, currentPosId); + } } } - // 绑定编辑视图(不变) + /** + * 绑定编辑视图(支持修改备注、开关距离、增删任务) + */ private void bindEditView(final EditViewHolder holder, final PositionModel model, final int position) { + // 绑定基础数据 holder.tvEditLongitude.setText(String.format("经度:%.6f", model.getLongitude())); holder.tvEditLatitude.setText(String.format("纬度:%.6f", model.getLatitude())); holder.etEditMemo.setText(model.getMemo()); holder.etEditMemo.setSelection(holder.etEditMemo.getText().length()); - bindRealDistance(holder.tvEditRealDistance, model); + // 初始默认文本 + holder.tvEditRealDistance.setText("实时距离:加载中..."); + holder.tvEditRealDistance.setTextColor(mContext.getResources().getColor(R.color.colorGrayText)); + LogUtils.d(TAG, "绑定编辑视图:初始化距离文本(位置ID=" + model.getPositionId() + ")"); + // 实时距离开关状态(Java7:显式判断设置选中) if (model.isEnableRealPositionDistance()) { holder.rgRealDistanceSwitch.check(R.id.rb_enable); } else { holder.rgRealDistanceSwitch.check(R.id.rb_disable); } + // 删除按钮逻辑(Java7:匿名内部类实现OnClickListener) holder.btnEditDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mOnDeleteClickListener != null) { mOnDeleteClickListener.onDeleteClick(position); + // 清理本地映射 mPositionTaskMap.remove(model.getPositionId()); - // 移除删除位置的索引映射 mPositionIdToIndexMap.remove(model.getPositionId()); + // 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.removeVisibleDistanceView(model.getPositionId()); + } } } }); + // 取消编辑逻辑 holder.btnEditCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -338,25 +497,51 @@ public class PositionAdapter extends RecyclerView.Adapter allTasks = holder.ptlvEditTasks.getAllTasks(); + ArrayList allTasks = (holder.ptlvEditTasks != null) ? holder.ptlvEditTasks.getAllTasks() : new ArrayList(); ArrayList boundTasks = new ArrayList(); - for (PositionTaskModel task : allTasks) { + + // Java7:迭代器遍历任务列表,绑定位置ID + Iterator taskIterator = allTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); if (task != null) { task.setPositionId(currentPosId); boundTasks.add(task); } } - mPositionTaskMap.put(currentPosId, boundTasks); + // 更新本地任务映射 + mPositionTaskMap.put(currentPosId, boundTasks); + // 清理旧任务+添加新任务(Java7:迭代器删除,避免ConcurrentModificationException) + Iterator globalTaskIter = mAllPositionTasks.iterator(); + while (globalTaskIter.hasNext()) { + PositionTaskModel task = globalTaskIter.next(); + if (task != null && currentPosId.equals(task.getPositionId())) { + globalTaskIter.remove(); // Java7:迭代器安全删除 + } + } + mAllPositionTasks.addAll(boundTasks); + + // 同步服务数据 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncPositionList(mPositionList); + mDistanceService.syncAllPositionTasks(mAllPositionTasks); + LogUtils.d(TAG, "编辑视图保存:同步位置+任务数据到服务(位置ID=" + currentPosId + ")"); + } + + // 切换视图+回调通知 model.setIsSimpleView(true); notifyItemChanged(position); if (mOnSavePositionClickListener != null) { @@ -366,46 +551,71 @@ public class PositionAdapter extends RecyclerView.Adapter matchedTasks = getSafeTasks(currentPosId); - holder.ptlvEditTasks.clearData(); + if (holder.ptlvEditTasks != null) { + holder.ptlvEditTasks.clearData(); - holder.ptlvEditTasks.setOnTaskUpdatedListener(new PositionTaskListView.OnTaskUpdatedListener() { - @Override - public void onTaskUpdated(String posId, ArrayList updatedTasks) { - ArrayList boundTasks = new ArrayList(); - for (PositionTaskModel task : updatedTasks) { - if (task != null) { - task.setPositionId(currentPosId); - boundTasks.add(task); + // 任务更新监听(Java7:匿名内部类实现) + holder.ptlvEditTasks.setOnTaskUpdatedListener(new PositionTaskListView.OnTaskUpdatedListener() { + @Override + public void onTaskUpdated(String posId, ArrayList updatedTasks) { + ArrayList boundTasks = new ArrayList(); + // 绑定任务到当前位置 + Iterator taskIterator = updatedTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); + if (task != null) { + task.setPositionId(currentPosId); + boundTasks.add(task); + } } - } - mPositionTaskMap.put(currentPosId, boundTasks); - Iterator taskIterator = mAllPositionTasks.iterator(); - while (taskIterator.hasNext()) { - PositionTaskModel task = taskIterator.next(); - if (task != null && currentPosId.equals(task.getPositionId())) { - taskIterator.remove(); + // 更新本地映射 + mPositionTaskMap.put(currentPosId, boundTasks); + // 清理旧任务+添加新任务 + Iterator globalTaskIter = mAllPositionTasks.iterator(); + while (globalTaskIter.hasNext()) { + PositionTaskModel task = globalTaskIter.next(); + if (task != null && currentPosId.equals(task.getPositionId())) { + globalTaskIter.remove(); + } } - } - mAllPositionTasks.addAll(boundTasks); + mAllPositionTasks.addAll(boundTasks); - if (mOnSavePositionTaskClickListener != null) { - mOnSavePositionTaskClickListener.onSavePositionTaskClick(); - } - Toast.makeText(mContext, "任务信息已保存", Toast.LENGTH_SHORT).show(); - } - }); + // 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncAllPositionTasks(mAllPositionTasks); + LogUtils.d(TAG, "任务更新:同步任务数据到服务(位置ID=" + currentPosId + ",任务数量=" + boundTasks.size() + ")"); + } - if (holder.ptlvEditTasks.getAllTasks().isEmpty()) { - holder.ptlvEditTasks.init(matchedTasks, currentPosId); + // 回调通知 + if (mOnSavePositionTaskClickListener != null) { + mOnSavePositionTaskClickListener.onSavePositionTaskClick(); + } + Toast.makeText(mContext, "任务信息已保存", Toast.LENGTH_SHORT).show(); + } + }); + + // 初始化任务列表 + if (holder.ptlvEditTasks.getAllTasks().isEmpty()) { + holder.ptlvEditTasks.init(matchedTasks, currentPosId); + } + holder.ptlvEditTasks.setViewStatus(PositionTaskListView.VIEW_MODE_EDIT); } - holder.ptlvEditTasks.setViewStatus(PositionTaskListView.VIEW_MODE_EDIT); + // 添加任务逻辑(Java7:匿名内部类实现OnClickListener) holder.btnAddTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if (holder.ptlvEditTasks == null) { + LogUtils.e(TAG, "添加任务失败:任务列表视图为空(位置ID=" + currentPosId + ")"); + Toast.makeText(mContext, "添加任务失败,请重试", Toast.LENGTH_SHORT).show(); + return; + } + + // 创建新任务(Java7:显式调用构造函数,避免Builder模式) PositionTaskModel newTask = new PositionTaskModel( PositionTaskModel.genTaskId(), currentPosId, @@ -415,11 +625,19 @@ public class PositionAdapter extends RecyclerView.Adapter currentTasks = getSafeTasks(currentPosId); currentTasks.add(newTask); mPositionTaskMap.put(currentPosId, new ArrayList(currentTasks)); mAllPositionTasks.add(newTask); + // 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncAllPositionTasks(mAllPositionTasks); + LogUtils.d(TAG, "新增任务:同步任务数据到服务(位置ID=" + currentPosId + ",新任务ID=" + newTask.getTaskId() + ")"); + } + + // 刷新任务列表视图 holder.ptlvEditTasks.clearData(); holder.ptlvEditTasks.init(currentTasks, currentPosId); holder.ptlvEditTasks.setViewStatus(PositionTaskListView.VIEW_MODE_EDIT); @@ -428,248 +646,259 @@ public class PositionAdapter extends RecyclerView.Adapter getSafeTasks(String positionId) { + if (positionId == null) { + LogUtils.w(TAG, "getSafeTasks:位置ID为空,返回空任务列表"); + return new ArrayList(); + } + if (!mPositionTaskMap.containsKey(positionId)) { + mPositionTaskMap.put(positionId, new ArrayList()); + LogUtils.d(TAG, "getSafeTasks:位置ID=" + positionId + " 无任务列表,初始化空列表"); + } + return mPositionTaskMap.get(positionId); + } + + /** + * 新增位置(Java7:显式非空判断,迭代器同步索引) + */ + public void addPosition(PositionModel model) { + if (model == null || model.getPositionId() == null) { + LogUtils.e(TAG, "新增位置失败:位置模型/位置ID为空"); + return; + } + String validPosId = model.getPositionId(); + mPositionList.add(model); + mPositionIdToIndexMap.put(validPosId, mPositionList.size() - 1); + + // 初始化任务列表(若不存在) + if (!mPositionTaskMap.containsKey(validPosId)) { + mPositionTaskMap.put(validPosId, new ArrayList()); + } + + // 同步服务+刷新视图 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncPositionList(mPositionList); + // 若控件已添加,立即同步服务 + if (mVisibleDistanceViews.containsKey(validPosId)) { + mDistanceService.addVisibleDistanceView(validPosId); + LogUtils.d(TAG, "新增位置:同步服务并添加控件(位置ID=" + validPosId + ")"); + } + } + + notifyItemInserted(mPositionList.size() - 1); + LogUtils.d(TAG, "新增位置成功:位置ID=" + validPosId + ",当前列表总数=" + mPositionList.size()); + } + + /** + * 删除位置(Java7:迭代器安全删除,避免并发异常) + */ + public void removePosition(int position) { + if (position < 0 || position >= mPositionList.size()) { + LogUtils.w(TAG, "删除位置失败:无效列表位置(position=" + position + ")"); + return; + } + PositionModel removedModel = mPositionList.get(position); + String removedPosId = removedModel.getPositionId(); + if (removedPosId == null) { + LogUtils.w(TAG, "删除位置失败:待删除位置ID为空(列表位置=" + position + ")"); return; } - if (mCurrentGpsPosition == null) { - distanceView.setText("实时距离:等待GPS定位"); - distanceView.setTextColor(mContext.getResources().getColor(R.color.colorGrayText)); - return; - } + // 1. 清理本地缓存和映射 + mVisibleDistanceViews.remove(removedPosId); + mPositionIdToIndexMap.remove(removedPosId); + LogUtils.d(TAG, "删除位置:清理本地映射(位置ID=" + removedPosId + ")"); - try { - double distanceM = PositionModel.calculatePositionDistance(mCurrentGpsPosition, model, false); -// 设置任务列表触发状态(调用优化后的通知逻辑) - bingoTask(model, (int) distanceM); - String distanceText = (distanceM < 1000) ? - String.format("实时距离:%.1f 米", distanceM) : - String.format("实时距离:%.1f 千米", distanceM / 1000); - distanceView.setText(distanceText); - distanceView.setTextColor(mContext.getResources().getColor(R.color.colorEnableGreen)); - } catch (IllegalArgumentException e) { - distanceView.setText("实时距离:数据无效"); - distanceView.setTextColor(mContext.getResources().getColor(R.color.colorRed)); - } - } + // 2. 重新同步剩余位置的索引映射(Java7:迭代器遍历) + Iterator modelIterator = mPositionList.iterator(); + int index = 0; + while (modelIterator.hasNext()) { + PositionModel remainingModel = modelIterator.next(); + mPositionIdToIndexMap.put(remainingModel.getPositionId(), index); + index++; + } -// ---------------------- 核心优化:bingoTask 任务触发通知逻辑(替换 notifyDataSetChanged()) ---------------------- - private void bingoTask(PositionModel model, int distanceM) { -// 1. 标记是否有任务触发(避免无变化时无效更新) - boolean hasTaskTriggered = false; -// 2. 记录当前位置ID(后续精准定位列表项) - final String targetPosId = model.getPositionId(); + // 3. 清理全局任务列表和映射表(Java7:迭代器安全删除) + Iterator taskIterator = mAllPositionTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); + if (task != null && removedPosId.equals(task.getPositionId())) { + taskIterator.remove(); + } + } + mPositionTaskMap.remove(removedPosId); + mPositionList.remove(position); + LogUtils.d(TAG, "删除位置:清理任务数据(位置ID=" + removedPosId + "),剩余列表总数=" + mPositionList.size()); -// 遍历当前位置的任务,判断是否触发 - for (PositionTaskModel task : mAllPositionTasks) { - if (targetPosId.equals(task.getPositionId())) { -// 暂存旧状态(避免重复触发更新) - boolean oldBingoState = task.isBingo(); - boolean newBingoState = false; + // 4. 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncPositionList(mPositionList); + mDistanceService.syncAllPositionTasks(mAllPositionTasks); + mDistanceService.removeVisibleDistanceView(removedPosId); + LogUtils.d(TAG, "删除位置:同步服务数据(位置ID=" + removedPosId + ")"); + } -// 根据任务条件判断新触发状态 - if (task.isGreaterThan()) { - newBingoState = task.isEnable() && distanceM > task.getDiscussDistance(); - } else if (task.isLessThan()) { - newBingoState = task.isEnable() && distanceM < task.getDiscussDistance(); - } else { - newBingoState = task.isEnable() && true; - } + // 刷新视图 + notifyItemRemoved(position); + notifyItemRangeChanged(position, mPositionList.size()); + } -// 仅当状态变化时,才更新任务状态并标记需要通知 - if (newBingoState != oldBingoState) { - task.setIsBingo(newBingoState); - hasTaskTriggered = true; - } - } - } + /** + * 更新所有位置(Java7:双迭代器清理无效数据) + */ + public void updateAllPositions(ArrayList newPositionList) { + if (newPositionList == null) { + LogUtils.e(TAG, "更新所有位置失败:新位置列表为空"); + return; + } + mPositionList.clear(); + mPositionList.addAll(newPositionList); + LogUtils.d(TAG, "更新所有位置:接收新列表(数量=" + newPositionList.size() + ")"); -// 3. 仅当有任务触发且状态变化时,才执行精准更新(避免全局刷新) - if (hasTaskTriggered) { -// 切换到主线程更新UI(解决子线程更新异常) - mUiHandler.post(new Runnable() { - @Override - public void run() { -// 从映射表快速获取位置索引(避免遍历列表,提升效率) - Integer targetPosIndex = mPositionIdToIndexMap.get(targetPosId); - if (targetPosIndex != null && targetPosIndex >= 0 && targetPosIndex < mPositionList.size()) { -// 精准更新单个位置项(仅刷新该位置的任务列表,不干扰其他项) - notifyItemChanged(targetPosIndex); - } - } - }); - } - } + // 1. 清理本地无效可见控件(Java7:迭代器遍历+判断存在性) + Iterator distanceViewIter = mVisibleDistanceViews.keySet().iterator(); + while (distanceViewIter.hasNext()) { + String posId = distanceViewIter.next(); + boolean isPosExist = false; -// 工具方法:安全获取任务列表(不变) - private ArrayList getSafeTasks(String positionId) { - if (!mPositionTaskMap.containsKey(positionId)) { - mPositionTaskMap.put(positionId, new ArrayList()); - } - return mPositionTaskMap.get(positionId); - } + Iterator modelIter = newPositionList.iterator(); + while (modelIter.hasNext()) { + PositionModel model = modelIter.next(); + if (posId.equals(model.getPositionId())) { + isPosExist = true; + break; + } + } -// 对外API:新增位置(补充索引映射更新) - public void addPosition(PositionModel model) { - if (model == null) return; - String validPosId = model.getPositionId(); - mPositionList.add(model); -// 新增:添加新位置的索引映射(索引为列表最后一位) - mPositionIdToIndexMap.put(validPosId, mPositionList.size() - 1); + if (!isPosExist) { + distanceViewIter.remove(); + LogUtils.d(TAG, "更新所有位置:清理无效可见控件(位置ID=" + posId + ")"); + } + } - if (!mPositionTaskMap.containsKey(validPosId)) { - mPositionTaskMap.put(validPosId, new ArrayList()); - } - notifyItemInserted(mPositionList.size() - 1); - } + // 2. 清理任务映射表无效项(Java7:迭代器遍历) + Iterator taskMapKeyIter = mPositionTaskMap.keySet().iterator(); + while (taskMapKeyIter.hasNext()) { + String posId = taskMapKeyIter.next(); + boolean isPosExist = false; -// 对外API:删除位置(补充索引映射清理) - public void removePosition(int position) { - if (position < 0 || position >= mPositionList.size()) return; - PositionModel removedModel = mPositionList.get(position); - String removedPosId = removedModel.getPositionId(); + Iterator modelIter = newPositionList.iterator(); + while (modelIter.hasNext()) { + PositionModel model = modelIter.next(); + if (posId.equals(model.getPositionId())) { + isPosExist = true; + break; + } + } -// 1. 清理距离缓存和索引映射(新增) - mVisibleDistanceViews.remove(removedPosId); - mPositionIdToIndexMap.remove(removedPosId); + if (!isPosExist) { + taskMapKeyIter.remove(); + LogUtils.d(TAG, "更新所有位置:清理无效任务映射(位置ID=" + posId + ")"); + } + } -// 2. 重新同步剩余位置的索引映射(避免删除后索引错位) - for (int i = position; i < mPositionList.size(); i++) { - PositionModel remainingModel = mPositionList.get(i); - mPositionIdToIndexMap.put(remainingModel.getPositionId(), i); - } + // 3. 重新初始化索引映射(Java7:普通for循环,避免迭代器索引问题) + mPositionIdToIndexMap.clear(); + for (int i = 0; i < newPositionList.size(); i++) { + PositionModel model = newPositionList.get(i); + String posId = model.getPositionId(); + mPositionIdToIndexMap.put(posId, i); + // 初始化新位置的任务列表(若不存在) + if (!mPositionTaskMap.containsKey(posId)) { + mPositionTaskMap.put(posId, new ArrayList()); + LogUtils.d(TAG, "更新所有位置:初始化新位置任务列表(位置ID=" + posId + ")"); + } + } -// 3. 清理全局任务列表和映射表(不变) - Iterator taskIterator = mAllPositionTasks.iterator(); - while (taskIterator.hasNext()) { - PositionTaskModel task = (PositionTaskModel)taskIterator.next(); - if (task != null && removedPosId.equals(task.getPositionId())) { - taskIterator.remove(); - } - } - mPositionTaskMap.remove(removedPosId); - mPositionList.remove(position); + // 4. 同步服务 + if (isServiceBound && mDistanceService != null) { + mDistanceService.syncPositionList(mPositionList); + mDistanceService.clearVisibleDistanceViews(); + // 同步有效可见控件(Java7:迭代器遍历) + Iterator validPosIter = mVisibleDistanceViews.keySet().iterator(); + while (validPosIter.hasNext()) { + mDistanceService.addVisibleDistanceView(validPosIter.next()); + } + LogUtils.d(TAG, "更新所有位置:同步服务数据(可见控件数量=" + mVisibleDistanceViews.size() + ")"); + } - notifyItemRemoved(position); - notifyItemRangeChanged(position, mPositionList.size()); - } + // 刷新视图 + notifyDataSetChanged(); + } -// 对外API:更新所有位置(补充索引映射同步) - public void updateAllPositions(ArrayList newPositionList) { - if (newPositionList == null) return; - mPositionList.clear(); - mPositionList.addAll(newPositionList); + /** + * 批量切换简单视图(Java7:迭代器遍历) + */ + public void switchAllToSimpleView() { + Iterator modelIterator = mPositionList.iterator(); + while (modelIterator.hasNext()) { + PositionModel model = modelIterator.next(); + model.setIsSimpleView(true); + } + notifyDataSetChanged(); + LogUtils.d(TAG, "批量切换简单视图:已切换所有位置(总数=" + mPositionList.size() + ")"); + } -// 1. 清理距离缓存中无效的位置ID(不变) - Iterator distanceViewIter = mVisibleDistanceViews.keySet().iterator(); - while (distanceViewIter.hasNext()) { - String posId = (String)distanceViewIter.next(); - boolean isPosExist = false; - for (PositionModel model : newPositionList) { - if (posId.equals(model.getPositionId())) { - isPosExist = true; - break; - } - } - if (!isPosExist) { - distanceViewIter.remove(); - } - } + /** + * 获取位置列表(Java7:返回新列表,避免外部修改) + */ + public ArrayList getPositionList() { + return new ArrayList(mPositionList); + } -// 2. 清理任务映射表(不变) - Iterator taskMapKeyIter = mPositionTaskMap.keySet().iterator(); - while (taskMapKeyIter.hasNext()) { - String posId = (String)taskMapKeyIter.next(); - boolean isPosExist = false; - for (PositionModel model : newPositionList) { - if (posId.equals(model.getPositionId())) { - isPosExist = true; - break; - } - } - if (!isPosExist) { - taskMapKeyIter.remove(); - } - } + // ---------------------- ViewHolder 定义(完全兼容Java7) ---------------------- + public static class SimpleViewHolder extends RecyclerView.ViewHolder { + TextView tvSimpleLongitude; + TextView tvSimpleLatitude; + TextView tvSimpleMemo; + TextView tvSimpleRealDistance; + PositionTaskListView ptlvSimpleTasks; -// 3. 重新初始化索引映射和任务列表(新增索引同步) - mPositionIdToIndexMap.clear(); - for (int i = 0; i < newPositionList.size(); i++) { - PositionModel model = (PositionModel)newPositionList.get(i); - String posId = model.getPositionId(); - mPositionIdToIndexMap.put(posId, i); // 同步新列表的索引 - if (!mPositionTaskMap.containsKey(posId)) { - mPositionTaskMap.put(posId, new ArrayList()); - } - } + public SimpleViewHolder(@NonNull View itemView) { + super(itemView); + // Java7:显式 findViewById + 类型转换 + tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); + tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); + tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); + tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance); + ptlvSimpleTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_simple_tasks); + } + } - notifyDataSetChanged(); - } + public static class EditViewHolder extends RecyclerView.ViewHolder { + TextView tvEditLongitude; + TextView tvEditLatitude; + EditText etEditMemo; + RadioGroup rgRealDistanceSwitch; + RadioButton rbDisable; + RadioButton rbEnable; + Button btnEditDelete; + Button btnEditCancel; + Button btnEditConfirm; + PositionTaskListView ptlvEditTasks; + Button btnAddTask; + TextView tvEditRealDistance; -// 对外API:批量切换简单视图(不变) - public void switchAllToSimpleView() { - for (PositionModel model : mPositionList) { - model.setIsSimpleView(true); - } - notifyDataSetChanged(); - } - -// 对外API:获取位置列表(不变) - public ArrayList getPositionList() { - return new ArrayList(mPositionList); - } - -// ---------------------- ViewHolder 定义(不变) ---------------------- - public static class SimpleViewHolder extends RecyclerView.ViewHolder { - TextView tvSimpleLongitude; - TextView tvSimpleLatitude; - TextView tvSimpleMemo; - TextView tvSimpleRealDistance; - PositionTaskListView ptlvSimpleTasks; - - public SimpleViewHolder(@NonNull View itemView) { - super(itemView); - tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude); - tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude); - tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo); - tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance); - ptlvSimpleTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_simple_tasks); - } - } - - public static class EditViewHolder extends RecyclerView.ViewHolder { - TextView tvEditLongitude; - TextView tvEditLatitude; - EditText etEditMemo; - RadioGroup rgRealDistanceSwitch; - RadioButton rbDisable; - RadioButton rbEnable; - Button btnEditDelete; - Button btnEditCancel; - Button btnEditConfirm; - PositionTaskListView ptlvEditTasks; - Button btnAddTask; - TextView tvEditRealDistance; - - public EditViewHolder(@NonNull View itemView) { - super(itemView); - tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); - tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); - etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); - rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch); - rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable); - rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable); - btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); - btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); - btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); - ptlvEditTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_edit_tasks); - btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); - tvEditRealDistance = (TextView) itemView.findViewById(R.id.tv_edit_real_distance); - } - } + public EditViewHolder(@NonNull View itemView) { + super(itemView); + // Java7:显式 findViewById + 类型转换 + tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude); + tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude); + etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo); + rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch); + rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable); + rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable); + btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete); + btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel); + btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm); + ptlvEditTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_edit_tasks); + btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task); + tvEditRealDistance = (TextView) itemView.findViewById(R.id.tv_edit_real_distance); + } + } } diff --git a/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java b/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java index ea05bef..c5a0154 100644 --- a/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java +++ b/positions/src/main/java/cc/winboll/studio/positions/models/PositionModel.java @@ -22,6 +22,8 @@ public class PositionModel extends BaseBean { double latitude; // 位置备注(空值时显示“无备注”) String memo; + // 定位点与指定点实时距离大小 + double realPositionDistance; // 是否启用实时距离计算 boolean isEnableRealPositionDistance; // 是否显示简单视图(true=简单视图,false=编辑视图) @@ -45,6 +47,14 @@ public class PositionModel extends BaseBean { this.isEnableRealPositionDistance = false; } + public void setRealPositionDistance(double realPositionDistance) { + this.realPositionDistance = realPositionDistance; + } + + public double getRealPositionDistance() { + return realPositionDistance; + } + // ---------------------- Getter/Setter(确保字段有效性) ---------------------- public void setPositionId(String positionId) { this.positionId = (positionId == null || positionId.trim().isEmpty()) ? genPositionId() : positionId; diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java b/positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java new file mode 100644 index 0000000..3a73039 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java @@ -0,0 +1,340 @@ +package cc.winboll.studio.positions.services; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/30 19:53 + * @Describe DistanceRefreshService + */ +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +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.utils.NotificationUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +/** + * 距离刷新服务:独立管理定时器,负责实时距离计算、任务触发判断、发送UI更新消息 + * 特性:1. 自启动(绑定后自动启动定时器) 2. 数据与Activity/Adapter同步 3. 本地消息通知UI更新 + * Java 7 适配:移除Lambda、Stream,使用匿名内部类+迭代器,明确泛型声明 + */ +public class DistanceRefreshService extends Service { + // 常量定义 + public static final String TAG = "DistanceRefreshService"; + public static final long REFRESH_INTERVAL = 5000; // 5秒刷新一次 + public static final int MSG_UPDATE_DISTANCE = 1001; + public static final String KEY_POSITION_ID = "key_position_id"; + public static final String KEY_DISTANCE_TEXT = "key_distance_text"; + public static final String KEY_DISTANCE_COLOR = "key_distance_color"; + + // 核心成员变量(Java7:明确泛型初始化) + private Timer mDistanceTimer; + private Handler mMainHandler; + private PositionModel mCurrentGpsPosition; + private ArrayList mPositionList; + private ArrayList mAllPositionTasks; + private Map mVisibleDistanceViewTags = new HashMap(); + private OnDistanceUpdateReceiver mUpdateReceiver; + + // 数据同步与消息接收接口(Activity/Adapter实现) + public interface OnDistanceUpdateReceiver { + void onDistanceUpdate(String positionId, String distanceText, int distanceColor); + int getColorRes(int resId); + } + + // 服务绑定器(用于外部获取服务实例) + public class DistanceBinder extends Binder { + public DistanceRefreshService getService() { + return DistanceRefreshService.this; + } + } + + private final IBinder mBinder = new DistanceBinder(); + + @Override + public void onCreate() { + super.onCreate(); + Log.d(TAG, "DistanceRefreshService onCreate"); + // 初始化主线程Handler(Java7:匿名内部类实现handleMessage) + mMainHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (msg.what == MSG_UPDATE_DISTANCE && mUpdateReceiver != null) { + // 解析消息(Java7:显式调用getData(),避免链式调用) + String positionId = msg.getData().getString(KEY_POSITION_ID); + String distanceText = msg.getData().getString(KEY_DISTANCE_TEXT); + int distanceColor = msg.getData().getInt(KEY_DISTANCE_COLOR); + LogUtils.d(TAG, "接收消息→转发更新:位置ID=" + positionId + ",距离文本=" + distanceText); + + mUpdateReceiver.onDistanceUpdate(positionId, distanceText, distanceColor); + } + } + }; + // 初始化数据集(Java7:明确泛型类型) + mPositionList = new ArrayList(); + mAllPositionTasks = new ArrayList(); + } + + @Override + public IBinder onBind(Intent intent) { + // 绑定服务时启动定时器(确保仅启动一次) + if (mDistanceTimer == null) { + startDistanceTimer(); + Log.d(TAG, "DistanceRefreshService onBind - 定时器首次启动"); + } else { + Log.d(TAG, "DistanceRefreshService onBind - 定时器已在运行,无需重复启动"); + } + return mBinder; + } + + /** + * 启动定时器:核心逻辑(距离计算、任务触发、发送UI更新消息) + * Java7:使用匿名内部类实现TimerTask,显式调用cancel+purge + */ + private void startDistanceTimer() { + // 先停止旧定时器(避免残留任务) + if (mDistanceTimer != null) { + mDistanceTimer.cancel(); + mDistanceTimer.purge(); // 清空已取消的任务,释放资源 + } + mDistanceTimer = new Timer(); + // Java7:匿名内部类实现TimerTask的run方法(替代Lambda) + mDistanceTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + LogUtils.d(TAG, "定时器触发→开始计算距离,可见位置数量=" + mVisibleDistanceViewTags.size()); + calculateAndSendDistanceUpdates(); + checkAndTriggerTasks(); + } + }, 0, REFRESH_INTERVAL); // 立即执行,之后每5秒执行一次 + } + + /** + * 计算距离并发送UI更新消息 + * Java7:使用迭代器遍历Map(替代forEach Lambda),显式空判断 + */ + private void calculateAndSendDistanceUpdates() { + // 无可见控件/数据/接收器时,直接返回 + if (mVisibleDistanceViewTags.isEmpty()) { + LogUtils.d(TAG, "无需要更新的可见控件,跳过计算"); + return; + } + if (mPositionList.isEmpty()) { + LogUtils.d(TAG, "位置列表为空,无法计算距离"); + return; + } + if (mUpdateReceiver == null) { + LogUtils.d(TAG, "未设置消息接收器(Adapter未绑定),无法发送更新"); + return; + } + + // Java7:使用Iterator遍历Map.Entry(替代forEach Lambda) + Iterator> entryIterator = mVisibleDistanceViewTags.entrySet().iterator(); + while (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + String positionId = entry.getKey(); + PositionModel targetModel = findPositionModelById(positionId); + + if (targetModel == null) { + // 位置模型不存在时,发送“无效位置”消息 + sendDistanceUpdateMessage( + positionId, + "实时距离:位置无效", + mUpdateReceiver.getColorRes(R.color.colorRed) + ); + LogUtils.d(TAG, "位置ID=" + positionId + " 未找到对应模型,发送无效提示"); + continue; + } + + // 距离计算与文本生成 + String distanceText; + int distanceColor; + if (!targetModel.isEnableRealPositionDistance()) { + distanceText = "实时距离:未启用"; + distanceColor = mUpdateReceiver.getColorRes(R.color.colorGrayText); + } else if (mCurrentGpsPosition == null) { + // 无GPS时持续发送“等待定位”(避免默认文本回落) + distanceText = "实时距离:等待GPS定位"; + distanceColor = mUpdateReceiver.getColorRes(R.color.colorGrayText); + LogUtils.d(TAG, "位置ID=" + positionId + " 无GPS数据,发送等待提示"); + } else { + try { + // 计算距离(复用PositionModel静态方法) + double distanceM = PositionModel.calculatePositionDistance(mCurrentGpsPosition, targetModel, false); + judgeTaskBingoStatus(targetModel, (int) distanceM); + // 格式化距离文本(Java7:显式类型转换,避免自动拆箱问题) + if (distanceM < 1000) { + distanceText = String.format("实时距离:%.1f 米", distanceM); + } else { + distanceText = String.format("实时距离:%.1f 千米", distanceM / 1000); + } + distanceColor = mUpdateReceiver.getColorRes(R.color.colorEnableGreen); + LogUtils.d(TAG, "位置ID=" + positionId + " 计算完成:" + distanceText); + } catch (IllegalArgumentException e) { + distanceText = "实时距离:数据无效"; + distanceColor = mUpdateReceiver.getColorRes(R.color.colorRed); + LogUtils.e(TAG, "位置ID=" + positionId + " 计算异常:" + e.getMessage()); + } + } + + // 发送消息到UI线程 + sendDistanceUpdateMessage(positionId, distanceText, distanceColor); + } + } + + /** + * 检查任务触发状态并发送通知 + * Java7:使用迭代器遍历任务列表(替代forEach Lambda) + */ + private void checkAndTriggerTasks() { + if (mAllPositionTasks.isEmpty()) { + return; + } + // Java7:Iterator遍历ArrayList(替代forEach Lambda) + Iterator taskIterator = mAllPositionTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); + if (task.isBingo() && task.isEnable()) { + NotificationUtils.show(getApplicationContext(), task.getTaskId(), task.getPositionId(), task.getTaskDescription()); + } + } + } + + /** + * 判断任务触发状态(仅修改数据,UI更新由Adapter处理) + * Java7:迭代器遍历任务列表,显式状态判断 + */ + private void judgeTaskBingoStatus(PositionModel model, int distanceM) { + String targetPosId = model.getPositionId(); + // Java7:Iterator遍历任务列表 + Iterator taskIterator = mAllPositionTasks.iterator(); + while (taskIterator.hasNext()) { + PositionTaskModel task = taskIterator.next(); + if (targetPosId.equals(task.getPositionId())) { + boolean oldBingoState = task.isBingo(); + boolean newBingoState = false; + + // 根据任务条件判断新状态(Java7:if-else替代三元表达式,增强可读性) + if (task.isGreaterThan()) { + newBingoState = task.isEnable() && distanceM > task.getDiscussDistance(); + } else if (task.isLessThan()) { + newBingoState = task.isEnable() && distanceM < task.getDiscussDistance(); + } else { + newBingoState = task.isEnable(); + } + + // 更新任务状态(仅状态变化时更新) + if (newBingoState != oldBingoState) { + task.setIsBingo(newBingoState); + } + } + } + } + + /** + * 发送距离更新消息(通过Handler发送到UI线程) + * Java7:显式创建Message,避免obtainMessage链式调用 + */ + private void sendDistanceUpdateMessage(String positionId, String distanceText, int distanceColor) { + Message msg = mMainHandler.obtainMessage(MSG_UPDATE_DISTANCE); + // Java7:显式调用put方法(替代链式put) + msg.getData().putString(KEY_POSITION_ID, positionId); + msg.getData().putString(KEY_DISTANCE_TEXT, distanceText); + msg.getData().putInt(KEY_DISTANCE_COLOR, distanceColor); + mMainHandler.sendMessage(msg); + LogUtils.d(TAG, "发送消息→位置ID=" + positionId + ",距离文本=" + distanceText); + } + + /** + * 根据位置ID查找位置模型(工具方法) + * Java7:迭代器遍历位置列表(替代stream().filter()) + */ + private PositionModel findPositionModelById(String positionId) { + // Java7:Iterator遍历ArrayList + Iterator modelIterator = mPositionList.iterator(); + while (modelIterator.hasNext()) { + PositionModel model = modelIterator.next(); + if (positionId.equals(model.getPositionId())) { + return model; + } + } + return null; + } + + // ---------------------- 对外API(供Activity/Adapter调用) ---------------------- + public void setOnDistanceUpdateReceiver(OnDistanceUpdateReceiver receiver) { + this.mUpdateReceiver = receiver; + } + + public void syncCurrentGpsPosition(PositionModel currentGpsPosition) { + this.mCurrentGpsPosition = currentGpsPosition; + LogUtils.d(TAG, "同步GPS位置:纬度=" + (currentGpsPosition != null ? currentGpsPosition.getLatitude() : 0.0f)); + } + + public void syncPositionList(ArrayList positionList) { + if (positionList != null) { + this.mPositionList.clear(); + this.mPositionList.addAll(positionList); + LogUtils.d(TAG, "同步位置列表:数量=" + positionList.size()); + } + } + + public void syncAllPositionTasks(ArrayList allPositionTasks) { + if (allPositionTasks != null) { + this.mAllPositionTasks.clear(); + this.mAllPositionTasks.addAll(allPositionTasks); + } + } + + public void addVisibleDistanceView(String positionId) { + if (positionId != null && !mVisibleDistanceViewTags.containsKey(positionId)) { + mVisibleDistanceViewTags.put(positionId, 0); // 用0占位,仅标记存在 + LogUtils.d(TAG, "添加可见位置ID:" + positionId + ",当前可见数量=" + mVisibleDistanceViewTags.size()); + } + } + + public void removeVisibleDistanceView(String positionId) { + if (positionId != null) { + mVisibleDistanceViewTags.remove(positionId); + LogUtils.d(TAG, "移除可见位置ID:" + positionId + ",当前可见数量=" + mVisibleDistanceViewTags.size()); + } + } + + public void clearVisibleDistanceViews() { + mVisibleDistanceViewTags.clear(); + LogUtils.d(TAG, "清空所有可见位置ID"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, "DistanceRefreshService onDestroy - 定时器销毁"); + // 销毁定时器,避免内存泄漏(Java7:显式判断非空) + if (mDistanceTimer != null) { + mDistanceTimer.cancel(); + mDistanceTimer.purge(); + } + // 清空数据,解除引用(避免内存泄漏) + mCurrentGpsPosition = null; + mPositionList.clear(); + mAllPositionTasks.clear(); + mVisibleDistanceViewTags.clear(); + mUpdateReceiver = null; + } +} + diff --git a/positions/src/main/res/layout/activity_main.xml b/positions/src/main/res/layout/activity_main.xml index 97e0df2..09f11dc 100644 --- a/positions/src/main/res/layout/activity_main.xml +++ b/positions/src/main/res/layout/activity_main.xml @@ -32,18 +32,11 @@ android:text="Positions" android:onClick="onPositions"/> - - - - - +