diff --git a/positions/build.properties b/positions/build.properties
index 4d4420c9..8e7279fc 100644
--- a/positions/build.properties
+++ b/positions/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Tue Sep 30 18:06:22 GMT 2025
+#Tue Sep 30 21:17:41 GMT 2025
stageCount=4
libraryProject=
baseVersion=15.0
publishVersion=15.0.3
-buildCount=10
+buildCount=19
baseBetaVersion=15.0.4
diff --git a/positions/src/main/AndroidManifest.xml b/positions/src/main/AndroidManifest.xml
index 1b9f1800..f3054543 100644
--- a/positions/src/main/AndroidManifest.xml
+++ b/positions/src/main/AndroidManifest.xml
@@ -12,6 +12,9 @@
+
+
+
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 8d489c26..58694ed1 100644
--- a/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java
+++ b/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java
@@ -1,32 +1,186 @@
package cc.winboll.studio.positions;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.View;
+import android.widget.CompoundButton;
+
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
+
import cc.winboll.studio.libappbase.LogActivity;
-import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.positions.activities.LocationActivity;
+import cc.winboll.studio.positions.services.DistanceRefreshService;
import com.hjq.toast.ToastUtils;
+import cc.winboll.studio.positions.models.AppConfigsModel;
+/**
+ * 主页面:仅负责
+ * 1. 位置服务启动/停止(通过 Switch 开关控制)
+ * 2. 跳转至“位置管理页(LocationActivity)”和“日志页(LogActivity)”
+ * 3. Java 7 语法适配:无 Lambda、显式接口实现、兼容低版本
+ */
public class MainActivity extends AppCompatActivity {
+ public static final String TAG = "MainActivity";
+ // UI 控件:服务控制开关、顶部工具栏
+ private SwitchCompat mServiceSwitch;
+ private Toolbar mToolbar;
+ // 服务相关:服务实例、绑定状态标记
+ private DistanceRefreshService mDistanceService;
+ private boolean isServiceBound = false;
+ // ---------------------- 服务连接回调(仅用于获取服务状态,不依赖服务执行核心逻辑) ----------------------
+ private final ServiceConnection mServiceConn = new ServiceConnection() {
+ /**
+ * 服务绑定成功:获取服务实例,同步开关状态(以服务实际状态为准)
+ */
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // Java 7 显式强转 Binder 实例(确保类型匹配,避免ClassCastException)
+ DistanceRefreshService.DistanceBinder binder = (DistanceRefreshService.DistanceBinder) service;
+ mDistanceService = binder.getService();
+ isServiceBound = true;
+ // 绑定后立即同步开关状态,避免UI与服务实际状态不一致
+ syncSwitchState();
+ }
+
+ /**
+ * 服务意外断开(如服务崩溃):重置服务实例和绑定状态
+ */
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mDistanceService = null;
+ isServiceBound = false;
+ // 断开后同步开关状态(从SP读取上次保存的状态)
+ syncSwitchState();
+ }
+ };
+
+ // ---------------------- Activity 生命周期(核心:初始化UI、绑定服务、释放资源) ----------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+ setContentView(R.layout.activity_main); // 关联主页面布局
- Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ // 1. 初始化顶部 Toolbar(保留原逻辑,设置页面标题)
+ initToolbar();
+ // 2. 初始化服务控制开关(核心功能:绑定开关点击事件、读取SP状态)
+ initServiceSwitch();
+ // 3. 绑定服务(仅用于获取服务实时状态,不影响服务独立运行)
+ bindDistanceService();
}
- public void onPositions(View view) {
- startActivity(new Intent(this, LocationActivity.class));
- }
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // 页面销毁时解绑服务,避免Activity与服务相互引用导致内存泄漏
+ if (isServiceBound) {
+ unbindService(mServiceConn);
+ isServiceBound = false;
+ mDistanceService = null;
+ }
+ }
- public void onLog(View view) {
- LogActivity.startLogActivity(this);
- }
+ // ---------------------- 核心功能1:初始化UI组件(Toolbar + 服务开关) ----------------------
+ /**
+ * 初始化顶部 Toolbar,设置页面标题
+ */
+ private void initToolbar() {
+ mToolbar = (Toolbar) findViewById(R.id.toolbar); // Java 7 显式 findViewById + 强转
+ setSupportActionBar(mToolbar);
+ // 给ActionBar设置标题(先判断非空,避免空指针异常)
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setTitle("位置管理");
+ }
+ }
+
+ /**
+ * 初始化服务控制开关:读取SP状态、绑定点击事件
+ */
+ private void initServiceSwitch() {
+ mServiceSwitch = (SwitchCompat) findViewById(R.id.switch_service_control); // 显式强转
+
+ // 2. 绑定开关状态变化监听(Java 7 用匿名内部类实现 CompoundButton.OnCheckedChangeListener)
+ mServiceSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ AppConfigsModel.saveBean(MainActivity.this, new AppConfigsModel(true));
+ // 开关打开:启动服务(通过startService确保服务独立运行,不受Activity绑定影响)
+ startService(new Intent(MainActivity.this, DistanceRefreshService.class));
+
+ } else {
+ AppConfigsModel.saveBean(MainActivity.this, new AppConfigsModel(false));
+ // 开关关闭:先解绑服务(避免服务被Activity持有),再停止服务
+ if (isServiceBound) {
+ unbindService(mServiceConn);
+ isServiceBound = false;
+ }
+ stopService(new Intent(MainActivity.this, DistanceRefreshService.class));
+ }
+ // 状态变化后同步开关UI(确保UI与服务实际状态一致)
+ syncSwitchState();
+ }
+ });
+ }
+
+ // ---------------------- 核心功能2:服务状态同步与绑定 ----------------------
+ /**
+ * 同步服务开关状态:优先以服务实时状态为准,无服务则读SP
+ */
+ private void syncSwitchState() {
+ if (mServiceSwitch == null) {
+ return; // 开关未初始化,直接返回
+ }
+
+ if (isServiceBound && mDistanceService != null) {
+ ToastUtils.show("位置服务已启动");
+ } else {
+ ToastUtils.show("位置服务已关闭");
+ }
+ }
+
+ /**
+ * 绑定服务(仅用于获取服务状态,不启动服务)
+ */
+ private void bindDistanceService() {
+ Intent serviceIntent = new Intent(this, DistanceRefreshService.class);
+ // 绑定服务:BIND_AUTO_CREATE 表示若服务未启动则创建(仅为获取状态,后续由开关控制启停)
+ bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
+ }
+
+ // ---------------------- 核心功能3:页面跳转(位置管理页+日志页) ----------------------
+ /**
+ * 跳转至“位置管理页(LocationActivity)”(按钮点击触发,需在布局中设置 android:onClick="onPositions")
+ * 服务未启动时提示,不允许跳转(避免LocationActivity无数据)
+ */
+ public void onPositions(View view) {
+ // 从配置文件读取服务状态(避免依赖服务绑定,提升稳定性)
+ AppConfigsModel bean = AppConfigsModel.loadBean(MainActivity.this, AppConfigsModel.class);
+ boolean isServiceRunning = (bean == null) ? false : bean.isEnableDistanceRefreshService();
+
+ if (!isServiceRunning) {
+ ToastUtils.show("请先启动位置服务,否则无法加载数据");
+ return; // 服务未启动,不跳转
+ }
+
+ // 服务已启动:跳转到位置管理页
+ startActivity(new Intent(MainActivity.this, LocationActivity.class));
+ }
+
+ /**
+ * 跳转至“日志页(LogActivity)”(按钮点击触发,需在布局中设置 android:onClick="onLog")
+ * 无服务状态限制,直接跳转
+ */
+ public void onLog(View view) {
+ LogActivity.startLogActivity(this); // 调用LogActivity静态方法跳转(保留原逻辑)
+ }
}
+
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 74cb5619..54d0154d 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
@@ -3,443 +3,251 @@ package cc.winboll.studio.positions.activities;
/**
* @Author ZhanGSKen&豆包大模型
* @Date 2025/09/29 18:22
- * @Describe 当前位置实时显示 + 位置列表实时距离计算
+ * @Describe 位置列表页面(Java 7 兼容,完全依赖DistanceRefreshService数据)
*/
-import android.Manifest;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.pm.PackageManager;
-import android.location.Location;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.os.Bundle;
-import android.text.InputType;
+import android.os.IBinder;
import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
-import androidx.annotation.NonNull;
+
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+
+import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.adapters.PositionAdapter;
import cc.winboll.studio.positions.models.PositionModel;
-import com.google.android.gms.location.FusedLocationProviderClient;
-import com.google.android.gms.location.LocationCallback;
-import com.google.android.gms.location.LocationRequest;
-import com.google.android.gms.location.LocationResult;
-import com.google.android.gms.location.LocationServices;
-import com.google.android.gms.tasks.OnSuccessListener;
+import cc.winboll.studio.positions.services.DistanceRefreshService;
+
import java.util.ArrayList;
-import cc.winboll.studio.positions.models.PositionTaskModel;
/**
- * 实时定位活动窗口:
- * 1. 申请定位必需权限(精确定位)
- * 2. 初始化FusedLocationProviderClient(谷歌官方定位服务,兼容所有安卓版本)
- * 3. 实时监听位置变化,更新显示经度、纬度 + 同步给Adapter计算实时距离
- * 4. 右下角圆形悬浮按钮(含大写P字母,支持添加位置)
- * 5. 位置列表支持实时距离显示(按isEnableRealPositionDistance控制)
+ * 核心逻辑:
+ * 1. 启动前检查服务状态,未运行则拦截并提示
+ * 2. 绑定服务后通过接口获取数据,不本地存储数据
+ * 3. Adapter初始化仅传上下文+服务实例,数据从服务实时获取
+ * 4. 严格Java 7语法:显式类型转换、匿名内部类、无Lambda
*/
public class LocationActivity extends AppCompatActivity {
-
public static final String TAG = "LocationActivity";
+
+ // SP配置常量(与服务保持一致,用于判断服务状态)
+ private static final String SP_SERVICE_CONFIG = "service_config";
+ private static final String KEY_SERVICE_RUNNING = "is_service_running";
+ // 页面核心控件与变量
+ private RecyclerView mRecyclerView;
+ private PositionAdapter mAdapter;
+ private DistanceRefreshService mDistanceService; // 服务实例(已实现DistanceServiceInterface)
+ private boolean isServiceBound = false; // 服务绑定状态标记
- // 1. 核心组件与常量定义(兼容Java 7,移除不必要final)
- private static final int REQUEST_LOCATION_PERMISSIONS = 1004; // 定位权限请求码
- private FusedLocationProviderClient fusedLocationClient; // 定位核心客户端
- private LocationCallback locationCallback; // 位置变化监听器
- private LocationRequest locationRequest; // 定位请求配置(频率、精度等)
- private Location currentLocation; // 存储当前最新位置(用于同步给Adapter)
+ // ---------------------- 服务连接(Java 7 匿名内部类实现) ----------------------
+ private final ServiceConnection mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // 显式类型转换(Java 7 不支持自动推断,必须强转)
+ DistanceRefreshService.DistanceBinder binder = (DistanceRefreshService.DistanceBinder) service;
+ mDistanceService = binder.getService();
+ isServiceBound = true;
+ LogUtils.d("LocationActivity", "服务绑定成功,开始初始化Adapter");
+ // 绑定成功后初始化Adapter(仅传上下文+服务实例)
+ initAdapter();
+ }
- // UI控件(Java 7显式声明+强制转换)
- private TextView tvLongitude; // 经度显示
- private TextView tvLatitude; // 纬度显示
- private Button fabPButton; // 右下角圆形悬浮按钮(P字母)
- private RecyclerView rvPositionList; // 位置列表(RecyclerView)
- private PositionAdapter positionAdapter; // 列表Adapter(含实时距离逻辑)
- ArrayList mPositionList = new ArrayList(); // 位置数据集合
- ArrayList mPositionTasksList = new ArrayList(); // 位置数据集合
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // 服务意外断开:置空引用+更新状态,避免空指针
+ mDistanceService = null;
+ isServiceBound = false;
+ LogUtils.w("LocationActivity", "服务意外断开连接(可能被系统回收)");
+ Toast.makeText(LocationActivity.this, "服务已断开,请重新进入页面", Toast.LENGTH_SHORT).show();
+ }
+ };
+ // ---------------------- 页面生命周期(严格管理服务绑定/资源) ----------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);
- // 绑定UI控件(Java 7显式强制转换)
- tvLongitude = (TextView) findViewById(R.id.tv_longitude);
- tvLatitude = (TextView) findViewById(R.id.tv_latitude);
- fabPButton = (Button) findViewById(R.id.fab_p_button);
- rvPositionList = (RecyclerView) findViewById(R.id.rv_position_list);
+ // 1. 优先检查服务状态:未运行则提示并关闭页面
+ checkServiceRunningStatus();
+ // 2. 初始化RecyclerView(基础配置+性能优化)
+ initRecyclerViewConfig();
+ // 3. 绑定服务(获取数据的唯一入口,自动创建服务)
+ bindDistanceService();
+ }
- // 初始化核心逻辑:定位配置→悬浮按钮→列表Adapter→加载历史数据
- initLocationConfig();
- initFabPButton();
- initRecyclerViewAndAdapter();
- loadHistoryPositions();
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // 1. 解绑服务(仅绑定状态下执行,避免异常)
+ if (isServiceBound) {
+ unbindService(mServiceConn);
+ isServiceBound = false;
+ mDistanceService = null; // 置空引用,帮助GC回收
+ LogUtils.d("LocationActivity", "服务已解绑,避免内存泄漏");
+ }
+ // 2. 清理Adapter资源(调用Adapter内部销毁方法)
+ if (mAdapter != null) {
+ mAdapter.release();
+ mAdapter = null;
+ }
+ // 3. 清理RecyclerView引用
+ mRecyclerView = null;
+ }
- // 检查并申请定位权限(权限通过后启动实时定位)
- if (checkLocationPermissions()) {
- startRealTimeLocation();
- } else {
- requestLocationPermissions();
+ // ---------------------- 核心初始化方法(服务状态检查+RecyclerView+Adapter) ----------------------
+ /**
+ * 检查服务运行状态(从SP读取,与服务状态保持一致)
+ */
+ private void checkServiceRunningStatus() {
+ // Java 7 显式获取SP实例(不使用方法链简化)
+ SharedPreferences sp = getSharedPreferences(SP_SERVICE_CONFIG, Context.MODE_PRIVATE);
+ // 读取服务状态:默认未运行(false)
+ boolean isServiceRunning = sp.getBoolean(KEY_SERVICE_RUNNING, false);
+
+ if (!isServiceRunning) {
+ // 服务未运行:提示用户并关闭页面
+ Toast.makeText(this, "请先启动位置服务,否则无法加载数据", Toast.LENGTH_SHORT).show();
+ finish(); // 关闭当前页面,返回上一级
}
}
-
/**
- * 初始化定位配置(兼容Java 7,用LocationRequest.create()替代Builder)
+ * 初始化RecyclerView(布局管理器+性能优化)
*/
- private void initLocationConfig() {
- // 初始化定位客户端
- fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
-
- // 定位请求配置(高精度、1秒更新一次,适配旧版Google Play Services)
- locationRequest = LocationRequest.create();
- locationRequest.setInterval(1000); // 定位更新间隔(1秒)
- locationRequest.setFastestInterval(500); // 最快更新间隔(500毫秒)
- locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 优先GPS高精度定位
-
- // 位置变化监听器(实时更新UI + 同步位置给Adapter计算距离)
- locationCallback = new LocationCallback() {
- @Override
- public void onLocationResult(@NonNull LocationResult locationResult) {
- super.onLocationResult(locationResult);
- currentLocation = locationResult.getLastLocation(); // 更新当前最新位置
- if (currentLocation != null) {
- // 1. 更新页面经度、纬度显示
- double longitude = currentLocation.getLongitude();
- double latitude = currentLocation.getLatitude();
- tvLongitude.setText(String.format("当前经度:%.6f", longitude));
- tvLatitude.setText(String.format("当前纬度:%.6f", latitude));
-
- // 2. 同步当前位置给Adapter(用于计算列表项实时距离)
- if (positionAdapter != null) {
- // 创建仅含经纬度的PositionModel(memo空,isEnable无需关注)
- PositionModel currentGpsPos = new PositionModel();
- currentGpsPos.setLongitude(longitude);
- currentGpsPos.setLatitude(latitude);
- // 调用Adapter方法传入当前GPS位置
- positionAdapter.setCurrentGpsPosition(currentGpsPos);
- }
- } else {
- // 位置为空(如GPS信号弱),显示等待提示
- tvLongitude.setText("当前经度:等待更新...");
- tvLatitude.setText("当前纬度:等待更新...");
- }
- }
- };
- }
-
-
- /**
- * 初始化悬浮按钮(点击弹出备注输入框,添加当前位置到列表)
- */
- private void initFabPButton() {
- fabPButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showLocationRemarkDialog();
- }
- });
- }
-
-
- /**
- * 初始化RecyclerView和Adapter(绑定实时距离计算逻辑)
- */
- private void initRecyclerViewAndAdapter() {
- // 1. 配置RecyclerView布局管理器(垂直列表)
+ private void initRecyclerViewConfig() {
+ // 显式 findViewById + 类型转换(Java 7 必须强转)
+ mRecyclerView = (RecyclerView) findViewById(R.id.rv_position_list);
+ // 初始化线性布局管理器(垂直方向,默认)
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- rvPositionList.setLayoutManager(layoutManager);
-
- // 2. 初始化Adapter(传入上下文和数据集合,兼容Java 7)
- PositionModel.loadBeanList(this, this.mPositionList, PositionModel.class);
- PositionTaskModel.loadBeanList(this, this.mPositionTasksList, PositionTaskModel.class);
- positionAdapter = new PositionAdapter(this, mPositionList, mPositionTasksList);
- rvPositionList.setAdapter(positionAdapter);
-
- // 3. 设置Adapter删除监听(删除列表项并同步本地数据)
- positionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
- @Override
- public void onDeleteClick(int position) {
- showDeleteConfirmDialog(position);
- }
- });
-
- // 4. 设置Adapter保存监听(编辑备注后同步本地数据)
- positionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
- @Override
- public void onSavePositionClick() {
- try {
- PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class);
- Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(LocationActivity.this, "数据保存失败", Toast.LENGTH_SHORT).show();
- }
- }
- });
-
- positionAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() {
- @Override
- public void onSavePositionTaskClick() {
- try {
- PositionTaskModel.saveBeanList(LocationActivity.this, mPositionTasksList, PositionTaskModel.class);
- Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(LocationActivity.this, "数据保存失败", Toast.LENGTH_SHORT).show();
- }
- }
- });
+ mRecyclerView.setLayoutManager(layoutManager);
+ // 固定列表大小(优化性能:避免列表项变化时重复测量RecyclerView)
+ mRecyclerView.setHasFixedSize(true);
}
-
/**
- * 从本地加载历史位置数据(基于BaseBean的持久化逻辑)
+ * 初始化Adapter(核心:仅传上下文+服务实例,数据从服务获取)
*/
- private void loadHistoryPositions() {
- try {
- ArrayList historyList = new ArrayList();
- // 调用PositionModel加载方法(读取本地保存的位置数据)
- PositionModel.loadBeanList(LocationActivity.this, historyList, PositionModel.class);
- if (historyList != null && !historyList.isEmpty()) {
- mPositionList.clear();
- mPositionList.addAll(historyList);
- positionAdapter.notifyDataSetChanged(); // 通知列表刷新
- }
- } catch (Exception e) {
- e.printStackTrace(); // 首次启动无数据时忽略异常
- }
- }
-
-
- /**
- * 弹出位置备注输入对话框(添加当前位置到列表)
- */
- private void showLocationRemarkDialog() {
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(LocationActivity.this);
- dialogBuilder.setTitle("当前位置备注");
-
- // 创建输入框(配置提示文本和内边距)
- final EditText remarkInput = new EditText(LocationActivity.this);
- remarkInput.setHint("请输入备注(如:公司/家/学校)");
- remarkInput.setInputType(InputType.TYPE_CLASS_TEXT);
- remarkInput.setPadding(
- dip2px(16),
- dip2px(8),
- dip2px(16),
- dip2px(8)
- );
- dialogBuilder.setView(remarkInput);
-
- // 确定按钮:添加位置到列表 + 保存本地
- dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String inputRemark = remarkInput.getText().toString().trim();
- if (inputRemark.isEmpty()) {
- Toast.makeText(LocationActivity.this, "未输入备注", Toast.LENGTH_SHORT).show();
- return;
- }
-
- // 校验当前位置是否有效(避免无定位时添加空数据)
- if (currentLocation == null) {
- Toast.makeText(LocationActivity.this, "未获取到当前位置,请稍后再试", Toast.LENGTH_SHORT).show();
- return;
- }
-
- // 添加位置到列表(isEnableRealPositionDistance默认false,需手动开启)
- double longitude = currentLocation.getLongitude();
- double latitude = currentLocation.getLatitude();
- PositionModel newPosition = new PositionModel(
- PositionModel.genPositionId(),
- longitude,
- latitude,
- inputRemark,
- false // 默认不启用实时距离,用户可后续通过编辑开启
- );
- mPositionList.add(newPosition);
-
- // 保存到本地 + 刷新列表
- try {
- PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class);
- positionAdapter.notifyItemInserted(mPositionList.size() - 1); // 局部刷新(性能更优)
- Toast.makeText(LocationActivity.this, "位置已添加", Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(LocationActivity.this, "位置保存失败", Toast.LENGTH_SHORT).show();
- }
- dialog.dismiss();
- }
- });
-
- // 取消按钮:仅关闭对话框
- dialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
-
- // 配置对话框(禁止外部点击关闭)
- dialogBuilder.setCancelable(false);
- AlertDialog remarkDialog = dialogBuilder.create();
- remarkDialog.show();
- }
-
-
- /**
- * 显示删除确认对话框(删除列表项)
- */
- private void showDeleteConfirmDialog(final int position) {
- AlertDialog.Builder deleteDialogBuilder = new AlertDialog.Builder(this);
- deleteDialogBuilder.setTitle("删除位置记录")
- .setMessage("确定要删除这条位置吗?(删除后不可恢复)")
- .setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // 从列表移除 + 保存本地
- positionAdapter.removePosition(position);
- try {
- PositionModel.saveBeanList(LocationActivity.this, mPositionList, PositionModel.class);
- Toast.makeText(LocationActivity.this, "删除成功", Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(LocationActivity.this, "删除失败,请重试", Toast.LENGTH_SHORT).show();
- }
- }
- })
- .setNegativeButton("取消", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .setCancelable(false);
- deleteDialogBuilder.show();
- }
-
-
- /**
- * 检查定位权限(仅精确定位权限,满足实时距离计算需求)
- */
- private boolean checkLocationPermissions() {
- return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
- == PackageManager.PERMISSION_GRANTED;
- }
-
-
- /**
- * 申请定位权限
- */
- private void requestLocationPermissions() {
- String[] permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
- ActivityCompat.requestPermissions(
- this,
- permissions,
- REQUEST_LOCATION_PERMISSIONS
- );
- }
-
-
- /**
- * 启动实时定位(获取当前位置 + 监听位置变化)
- */
- private void startRealTimeLocation() {
- if (!checkLocationPermissions()) {
- Toast.makeText(this, "定位权限未授予", Toast.LENGTH_SHORT).show();
+ private void initAdapter() {
+ // 前置校验:服务未绑定/服务实例为空,直接返回
+ if (!isServiceBound || mDistanceService == null) {
+ LogUtils.e("LocationActivity", "初始化Adapter失败:服务未绑定或实例为空");
return;
}
- // 1. 先获取一次当前位置(初始化页面显示)
- fusedLocationClient.getLastLocation()
- .addOnSuccessListener(this, new OnSuccessListener() {
+ // 1. 初始化Adapter(参数匹配:Context + DistanceServiceInterface)
+ mAdapter = new PositionAdapter(this, mDistanceService);
+ mRecyclerView.setAdapter(mAdapter);
+
+ // 2. 设置删除回调(通过服务执行删除,Adapter自动同步数据)
+ mAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
@Override
- public void onSuccess(Location location) {
- if (location != null) {
- currentLocation = location;
- tvLongitude.setText(String.format("当前经度:%.6f", location.getLongitude()));
- tvLatitude.setText(String.format("当前纬度:%.6f", location.getLatitude()));
+ public void onDeleteClick(int position) {
+ // 多重校验:服务状态+索引有效性
+ if (isServiceBound && mDistanceService != null) {
+ ArrayList latestPosList = mDistanceService.getPositionList();
+ if (position >= 0 && position < latestPosList.size()) {
+ // 获取要删除的位置ID(从服务最新列表中取,避免本地数据过期)
+ PositionModel targetPos = latestPosList.get(position);
+ String posId = targetPos.getPositionId();
+ // 调用服务删除方法(服务内部处理“删除位置+关联任务+清理可见位置”)
+ mDistanceService.removePosition(posId);
+ // 刷新Adapter(从服务获取最新列表,确保数据一致)
+ mAdapter.updateAllPositions(mDistanceService.getPositionList());
+ Toast.makeText(LocationActivity.this, "位置已删除(含关联任务)", Toast.LENGTH_SHORT).show();
+ } else {
+ LogUtils.w("LocationActivity", "删除失败:位置索引无效(" + position + ")");
+ }
} else {
- tvLongitude.setText("当前经度:等待更新...");
- tvLatitude.setText("当前纬度:等待更新...");
+ Toast.makeText(LocationActivity.this, "删除失败:服务未绑定", Toast.LENGTH_SHORT).show();
}
}
});
- // 2. 注册位置监听器(实时更新位置)
- fusedLocationClient.requestLocationUpdates(
- locationRequest,
- locationCallback,
- getMainLooper() // 主线程更新UI,避免线程异常
- );
+ // 3. 设置位置保存回调(保存逻辑由服务处理,此处仅提示)
+ mAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
+ @Override
+ public void onSavePositionClick() {
+ Toast.makeText(LocationActivity.this, "位置信息已保存(备注/距离开关)", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // 4. 设置任务保存回调(同理,保存逻辑由服务处理)
+ mAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() {
+ @Override
+ public void onSavePositionTaskClick() {
+ Toast.makeText(LocationActivity.this, "任务信息已保存(新增/修改/删除)", Toast.LENGTH_SHORT).show();
+ }
+ });
}
+ // ---------------------- 服务绑定/数据同步方法 ----------------------
+ /**
+ * 绑定DistanceRefreshService(自动创建服务,获取数据入口)
+ */
+ private void bindDistanceService() {
+ // Java 7 显式创建Intent(不使用方法链)
+ Intent serviceIntent = new Intent(this, DistanceRefreshService.class);
+ // 绑定服务:Context.BIND_AUTO_CREATE 表示“服务未启动则自动创建”
+ bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
+ }
/**
- * 处理权限申请结果
+ * 同步GPS位置到服务(供外部定位模块调用,如GPS回调)
*/
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == REQUEST_LOCATION_PERMISSIONS) {
- boolean isGranted = false;
- for (int result : grantResults) {
- if (result == PackageManager.PERMISSION_GRANTED) {
- isGranted = true;
- break;
- }
- }
-
- if (isGranted) {
- startRealTimeLocation(); // 权限通过,启动定位
- } else {
- // 权限拒绝,提示并显示无权限状态
- Toast.makeText(this, "定位权限被拒绝,无法显示实时位置和距离", Toast.LENGTH_SHORT).show();
- tvLongitude.setText("当前经度:无权限");
- tvLatitude.setText("当前纬度:无权限");
- }
+ public void syncGpsPositionToService(PositionModel gpsModel) {
+ // 校验:服务绑定+GPS模型有效
+ if (isServiceBound && mDistanceService != null && gpsModel != null) {
+ mDistanceService.syncCurrentGpsPosition(gpsModel);
+ // 可选:GPS更新后强制刷新一次距离(避免等待定时周期)
+ mDistanceService.forceRefreshDistance();
+ LogUtils.d("LocationActivity", "GPS位置已同步到服务,并触发即时距离计算");
+ } else {
+ LogUtils.w("LocationActivity", "同步GPS失败:服务未绑定或GPS模型无效");
}
}
-
+ // ---------------------- 页面交互方法(新增位置按钮点击事件) ----------------------
/**
- * 活动销毁:停止定位 + 停止距离刷新定时器(避免内存泄漏)
+ * 新增位置(绑定到布局中“新增按钮”的 android:onClick="addNewPosition")
*/
- @Override
- protected void onDestroy() {
- super.onDestroy();
+ public void addNewPosition(View view) {
+ // 1. 隐藏软键盘(避免新增时残留输入框焦点)
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null && getCurrentFocus() != null) {
+ imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
+ }
- // 1. 停止定位监听(原逻辑不变)
- if (fusedLocationClient != null && locationCallback != null) {
- fusedLocationClient.removeLocationUpdates(locationCallback);
- }
+ // 2. 校验服务状态(未绑定则提示)
+ if (!isServiceBound || mDistanceService == null) {
+ Toast.makeText(this, "新增失败:服务未绑定", Toast.LENGTH_SHORT).show();
+ return;
+ }
- // 2. 关键:调用Adapter的stopTimer(内部已实现服务解绑,避免内存泄漏)
- if (positionAdapter != null) {
- positionAdapter.stopTimer(); // 此方法已重构为:解绑服务+清理资源
- }
+ // 3. 创建示例位置模型(实际项目需替换为“用户输入经纬度/备注”)
+ PositionModel newPos = new PositionModel();
+ newPos.setPositionId(PositionModel.genPositionId()); // 静态方法生成唯一ID(需在PositionModel中实现)
+ newPos.setLongitude(116.404267); // 示例经度(北京)
+ newPos.setLatitude(39.915119); // 示例纬度
+ newPos.setMemo("测试位置(可编辑备注)"); // 示例备注
+ newPos.setIsSimpleView(true); // 默认显示“简单视图”(非编辑模式)
+ newPos.setIsEnableRealPositionDistance(true); // 默认启用距离计算
- // 3. 最后同步一次数据(原逻辑不变)
- try {
- if (mPositionList != null && !mPositionList.isEmpty()) {
- PositionModel.saveBeanList(this, mPositionList, PositionModel.class);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 辅助工具:dp转px(适配不同屏幕分辨率)
- */
- private int dip2px(float dpValue) {
- final float scale = getResources().getDisplayMetrics().density;
- return (int) (dpValue * scale + 0.5f); // +0.5f用于四舍五入,确保精度
+ // 4. 调用服务新增位置(服务内部去重+数据管理)
+ mDistanceService.addPosition(newPos);
+ // 5. 刷新Adapter(从服务获取最新列表,显示新增位置)
+ mAdapter.updateAllPositions(mDistanceService.getPositionList());
+ Toast.makeText(this, "新增位置成功(默认启用距离计算)", Toast.LENGTH_SHORT).show();
+ LogUtils.d("LocationActivity", "新增位置:ID=" + newPos.getPositionId() + ",备注=" + newPos.getMemo());
}
}
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 ddfbf378..49fd7877 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,29 +3,20 @@ package cc.winboll.studio.positions.adapters;
/**
* @Author ZhanGSKen&豆包大模型
* @Date 2025/09/29 20:25
- * @Describe 位置数据适配器(移除定时器,通过绑定服务获取距离更新)
+ * @Describe 位置数据适配器(直连 DistanceRefreshService 数据源,Java 7 兼容+无内存泄漏+性能优化)
*/
-import android.content.ComponentName;
+
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Looper;
import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
-import android.widget.PopupMenu;
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;
@@ -33,923 +24,655 @@ 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.ConcurrentModificationException;
import java.util.Iterator;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import android.nfc.tech.TagTechnology;
/**
- * 位置列表适配器:管理位置视图渲染、服务绑定、距离UI更新
- * Java 7 适配:移除Lambda、Stream,使用匿名内部类+迭代器,明确泛型,删除Java8+API(如forEach)
+ * 位置列表Adapter(Java 7 兼容版)
+ * 核心职责:
+ * 1. 展示位置/任务数据(简单视图+编辑视图切换)
+ * 2. 接收服务的距离更新回调(OnDistanceUpdateReceiver)
+ * 3. 与Activity、DistanceRefreshService 交互(通过接口解耦)
*/
-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 String DEFAULT_TASK_DESC = "新位置任务";
- private static final int DEFAULT_TASK_DISTANCE = 100;
+public class PositionAdapter extends RecyclerView.Adapter {
+ public static final String TAG = "PositionAdapter";
+
+ // 1. 视图类型常量(简单视图/编辑视图,避免魔法值)
+ 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; // 单位:米
+ private static final String DISTANCE_FORMAT = "实时距离:%.1f 米";
+ private static final String DISTANCE_DISABLED = "实时距离:未启用";
+ private static final String DISTANCE_ERROR = "实时距离:计算失败";
- // 核心成员变量(Java7:明确泛型声明与初始化)
- private final ArrayList mPositionList; // 数据源:与Service共享同一份引用
- private final ArrayList mAllPositionTasks;
- private Context mContext;
- private PositionModel mCurrentGpsPosition;
- private final Map> mPositionTaskMap;
- private final Map mVisibleDistanceViews = new HashMap();
- private final Map mPositionIdToIndexMap = new HashMap();
- private final android.os.Handler mUiHandler = new android.os.Handler(Looper.getMainLooper());
+ // 3. 核心依赖(上下文+服务接口,数据完全从服务获取)
+ private final Context mContext;
+ private final DistanceServiceInterface mService; // 服务接口(解耦服务依赖)
+ // 4. 本地辅助缓存(避免频繁从服务拉取数据,提升性能)
+ private final ConcurrentHashMap mPosIdToIndexMap; // 位置ID→列表索引
+ private final ConcurrentHashMap> mPosToTasksMap; // 位置ID→关联任务
+ private final ConcurrentHashMap mPosDistanceViewMap; // 位置ID→距离显示控件(供更新用)
- // 服务相关变量
- 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;
+ // =========================================================================
+ // 关键修复:1. 定义 OnDistanceUpdateReceiver 接口(服务回调更新距离UI用)
+ // =========================================================================
+ /**
+ * 距离更新回调接口
+ * 作用:DistanceRefreshService 计算完距离后,通过此接口通知Adapter更新UI
+ * (服务不直接操作视图,通过接口解耦,符合单一职责原则)
+ */
+ public interface OnDistanceUpdateReceiver {
+ /**
+ * 距离更新回调(主线程调用)
+ * @param positionId 距离发生变化的位置ID(明确要更新哪个位置的UI)
+ */
+ void onDistanceUpdate(String positionId);
- // 绑定服务后初始化:设置接收器+同步数据(传递mPositionList原引用)
- mDistanceService.setOnDistanceUpdateReceiver(PositionAdapter.this);
- mDistanceService.syncPositionList(mPositionList); // 关键:传递原列表引用
- mDistanceService.syncAllPositionTasks(mAllPositionTasks);
- mDistanceService.syncCurrentGpsPosition(mCurrentGpsPosition);
+ /**
+ * 辅助方法:获取颜色资源(服务判断距离远近时需要颜色,如近=绿色、远=红色)
+ * @param resId 颜色资源ID(如 R.color.distance_near)
+ * @return 对应的颜色值(如 0xFF00FF00)
+ */
+ int getColorRes(int resId);
+ }
- // 同步已有的可见控件
- Iterator posIdIterator = mVisibleDistanceViews.keySet().iterator();
- while (posIdIterator.hasNext()) {
- String positionId = posIdIterator.next();
- mDistanceService.addVisibleDistanceView(positionId);
- LogUtils.d(TAG, "服务绑定后同步可见控件:位置ID=" + positionId);
- }
+ // =========================================================================
+ // 关键修复:2. 定义 DistanceServiceInterface 接口(Adapter 调用服务用)
+ // =========================================================================
+ /**
+ * 服务能力接口
+ * 作用:定义Adapter需要从服务获取的所有能力,避免直接依赖 DistanceRefreshService 类
+ * (即使服务类修改,只要接口不变,Adapter 无需改动)
+ */
+ public interface DistanceServiceInterface {
+ // 获取所有位置(返回拷贝,避免外部修改服务内部数据)
+ ArrayList getPositionList();
- LogUtils.d(TAG, "DistanceRefreshService 绑定成功");
+ // 获取所有任务(返回拷贝)
+ ArrayList getPositionTasksList();
+
+ // 同步当前GPS位置到服务(Adapter 无需调用,Activity 负责)
+ void syncCurrentGpsPosition(PositionModel position);
+
+ // 设置距离更新接收器(Adapter 将自己的接收器传给服务)
+ void setOnDistanceUpdateReceiver(OnDistanceUpdateReceiver receiver);
+
+ // 告诉服务:此位置进入屏幕(需要计算距离)
+ void addVisibleDistanceView(String positionId);
+
+ // 告诉服务:此位置离开屏幕(不需要计算距离,节省性能)
+ void removeVisibleDistanceView(String positionId);
+
+ // 告诉服务:清空所有可见位置(Adapter 销毁时调用)
+ void clearVisibleDistanceViews();
+ }
+
+ // =========================================================================
+ // 3. 与 Activity 交互的回调接口(删除/保存操作通知Activity)
+ // =========================================================================
+ // 删除位置回调(Activity 实现,调用服务删除数据)
+ public interface OnDeleteClickListener {
+ void onDeleteClick(int position);
+ }
+
+ // 保存位置信息回调(Activity 实现,显示保存成功提示)
+ public interface OnSavePositionClickListener {
+ void onSavePositionClick();
+ }
+
+ // 保存任务信息回调(Activity 实现,显示任务保存成功提示)
+ public interface OnSavePositionTaskClickListener {
+ void onSavePositionTaskClick();
+ }
+
+ // 回调实例(由 Activity 初始化时设置)
+ private OnDeleteClickListener mOnDeleteListener;
+ private OnSavePositionClickListener mOnSavePosListener;
+ private OnSavePositionTaskClickListener mOnSaveTaskListener;
+
+ // =========================================================================
+ // 4. 构造函数(初始化依赖+缓存+服务回调)
+ // =========================================================================
+ /**
+ * Adapter 唯一构造函数(仅接收上下文+服务接口,数据从服务获取)
+ * @param context 上下文(加载布局、操作资源、隐藏软键盘等)
+ * @param service 服务接口(提供数据和距离计算能力)
+ */
+ public PositionAdapter(Context context, DistanceServiceInterface service) {
+ this.mContext = context;
+ this.mService = service;
+
+ // 初始化并发缓存(避免多线程操作异常,Java 7 兼容)
+ this.mPosIdToIndexMap = new ConcurrentHashMap();
+ this.mPosToTasksMap = new ConcurrentHashMap>();
+ this.mPosDistanceViewMap = new ConcurrentHashMap();
+
+ // 初始化数据缓存(从服务拉取初始数据)
+ refreshPositionIndexMap();
+ refreshPositionTaskMap();
+
+ // 关键:给服务设置距离更新接收器(让服务能回调Adapter更新UI)
+ if (mService != null) {
+ mService.setOnDistanceUpdateReceiver(new OnDistanceUpdateReceiver() {
+ @Override
+ public void onDistanceUpdate(String positionId) {
+ // 服务回调:更新指定位置的距离UI(确保在主线程,RecyclerView 要求)
+ updatePositionDistanceUI(positionId);
+ }
+
+ @Override
+ public int getColorRes(int resId) {
+ // 提供颜色资源(服务无法直接访问Android资源,通过Adapter中转)
+ if (mContext != null) {
+ return mContext.getResources().getColor(resId);
+ }
+ return mContext.getResources().getColor(R.color.gray); // 默认灰色
+ }
+ });
+ } else {
+ LogUtils.e("PositionAdapter", "构造失败:服务接口为空");
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mDistanceService = null;
- isServiceBound = false;
- LogUtils.d(TAG, "DistanceRefreshService 意外断开");
- }
- };
+ }
-// 接口回调(外部监听)
- public interface OnDeleteClickListener {
- void onDeleteClick(int position);
- }
+ // =========================================================================
+ // 5. RecyclerView 核心方法(视图创建+数据绑定+生命周期)
+ // =========================================================================
+ /**
+ * 判断当前位置的视图类型(简单/编辑)
+ */
+ @Override
+ public int getItemViewType(int position) {
+ PositionModel posModel = getPositionByIndex(position);
+ return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
+ }
- public interface OnSavePositionClickListener {
- void onSavePositionClick();
- }
+ /**
+ * 创建视图Holder(根据视图类型加载不同布局)
+ */
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ if (viewType == VIEW_TYPE_SIMPLE) {
+ // 加载“简单视图”布局(仅显示数据,无编辑按钮)
+ View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false);
+ return new SimpleViewHolder(simpleView);
+ } else {
+ // 加载“编辑视图”布局(含备注输入、距离开关、删除/保存按钮)
+ View editView = inflater.inflate(R.layout.item_position_edit, parent, false);
+ return new EditViewHolder(editView);
+ }
+ }
- public interface OnSavePositionTaskClickListener {
- void onSavePositionTaskClick();
- }
+ /**
+ * 绑定视图数据(核心:给控件赋值+设置点击事件)
+ */
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ PositionModel posModel = getPositionByIndex(position);
+ if (posModel == null) {
+ LogUtils.w("PositionAdapter", "绑定数据失败:位置模型为空(索引=" + position + ")");
+ return;
+ }
+ String posId = posModel.getPositionId();
- private OnDeleteClickListener mOnDeleteClickListener;
- private OnSavePositionClickListener mOnSavePositionClickListener;
- private OnSavePositionTaskClickListener mOnSavePositionTaskClickListener;
+ // 根据视图类型绑定数据
+ if (holder instanceof SimpleViewHolder) {
+ bindSimpleView((SimpleViewHolder) holder, posModel, position);
+ } else if (holder instanceof EditViewHolder) {
+ bindEditView((EditViewHolder) holder, posModel, position);
+ }
-// 构造函数(Java7:明确泛型,非空判断初始化)
- public PositionAdapter(Context context, ArrayList positionList, ArrayList allPositionTasks) {
- this.mContext = context;
-// Java7:显式非空判断(避免空指针)
- this.mPositionList = (positionList != null) ? positionList : new ArrayList();
- this.mAllPositionTasks = (allPositionTasks != null) ? allPositionTasks : new ArrayList();
- this.mPositionTaskMap = new HashMap>();
+ // 视图进入屏幕:告诉服务“需要计算此位置距离”
+ notifyServicePositionVisible(posId, true);
+ }
-// 初始化任务映射表(Java7:双迭代器遍历)
- Iterator modelIterator = mPositionList.iterator();
- while (modelIterator.hasNext()) {
- PositionModel model = (PositionModel)modelIterator.next();
- String validPosId = model.getPositionId();
- ArrayList matchedTasks = new ArrayList();
+ /**
+ * 视图离开屏幕(RecyclerView 回收视图时调用)
+ */
+ @Override
+ public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ PositionModel posModel = getPositionByIndex(holder.getAdapterPosition());
+ if (posModel != null) {
+ // 视图离开屏幕:告诉服务“不需要计算此位置距离”(节省性能)
+ notifyServicePositionVisible(posModel.getPositionId(), false);
+ // 从缓存中移除距离控件(避免内存泄漏)
+ mPosDistanceViewMap.remove(posModel.getPositionId());
+ }
+ }
- Iterator taskIterator = this.mAllPositionTasks.iterator();
- while (taskIterator.hasNext()) {
- PositionTaskModel task = (PositionTaskModel)taskIterator.next();
- if (task != null && validPosId.equals(task.getPositionId())) {
- matchedTasks.add(task);
+ /**
+ * 获取列表项数量(从服务获取最新数据量,确保与服务同步)
+ */
+ @Override
+ public int getItemCount() {
+ return (mService != null) ? mService.getPositionList().size() : 0;
+ }
+
+ // =========================================================================
+ // 6. 视图绑定细节(简单视图+编辑视图)
+ // =========================================================================
+ /**
+ * 绑定“简单视图”(仅显示数据,点击切换到编辑视图)
+ */
+ private void bindSimpleView(SimpleViewHolder holder, final PositionModel posModel, final int position) {
+ // 6.1 显示经纬度(格式化为6位小数,避免过长)
+ holder.tvSimpleLon.setText(String.format("经度:%.6f", posModel.getLongitude()));
+ holder.tvSimpleLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
+ // 6.2 显示备注(无备注时显示默认文本)
+ String memo = posModel.getMemo();
+ holder.tvSimpleMemo.setText("备注:" + (memo != null && !memo.isEmpty() ? memo : DEFAULT_MEMO));
+ // 6.3 显示实时距离(缓存距离控件,供服务回调更新)
+ mPosDistanceViewMap.put(posModel.getPositionId(), holder.tvSimpleDistance);
+ updateDistanceDisplay(holder.tvSimpleDistance, posModel);
+ // 6.4 点击视图→切换到编辑模式
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ posModel.setIsSimpleView(false);
+ notifyItemChanged(position); // 刷新当前项为编辑视图
}
+ });
+ }
+
+ /**
+ * 绑定“编辑视图”(支持修改备注、开关距离、删除位置、管理任务)
+ */
+ private void bindEditView(final EditViewHolder holder, final PositionModel posModel, final int position) {
+ final String posId = posModel.getPositionId();
+ // 6.1 显示经纬度(不可编辑,仅展示)
+ holder.tvEditLon.setText(String.format("经度:%.6f", posModel.getLongitude()));
+ holder.tvEditLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
+ // 6.2 显示/设置备注(编辑框赋值,光标定位到末尾)
+ String memo = posModel.getMemo();
+ if (memo != null && !memo.isEmpty()) {
+ holder.etEditMemo.setText(memo);
+ holder.etEditMemo.setSelection(memo.length());
+ } else {
+ holder.etEditMemo.setText(""); // 清空默认值
+ }
+ // 6.3 显示实时距离(缓存控件)
+ mPosDistanceViewMap.put(posId, holder.tvEditDistance);
+ updateDistanceDisplay(holder.tvEditDistance, posModel);
+ // 6.4 设置距离开关状态(启用/禁用)
+ holder.rgDistanceSwitch.check(posModel.isEnableRealPositionDistance()
+ ? R.id.rb_distance_enable : R.id.rb_distance_disable);
+
+ // 6.5 取消编辑→切换回简单视图
+ holder.btnCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ posModel.setIsSimpleView(true);
+ notifyItemChanged(position);
+ hideSoftKeyboard(v); // 隐藏软键盘
+ }
+ });
+
+ // 6.6 删除位置→回调Activity(通过服务删除,Adapter 不直接操作数据)
+ holder.btnDelete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnDeleteListener != null) {
+ mOnDeleteListener.onDeleteClick(position);
+ }
+ hideSoftKeyboard(v);
+ }
+ });
+
+ // 6.7 保存位置→同步到服务(更新备注+距离开关状态)
+ holder.btnSave.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mService == null || !(mService instanceof DistanceRefreshService)) {
+ LogUtils.e("PositionAdapter", "保存失败:服务接口无效");
+ return;
+ }
+ // 获取编辑后的参数
+ String newMemo = holder.etEditMemo.getText().toString().trim();
+ boolean isDistanceEnable = (holder.rgDistanceSwitch.getCheckedRadioButtonId() == R.id.rb_distance_enable);
+
+ // 构造更新模型(仅更新可修改字段)
+ PositionModel updatedPos = new PositionModel();
+ updatedPos.setPositionId(posId);
+ updatedPos.setMemo(newMemo);
+ updatedPos.setIsEnableRealPositionDistance(isDistanceEnable);
+
+ // 调用服务更新数据(服务内部处理数据持久化)
+ ((DistanceRefreshService) mService).updatePosition(updatedPos);
+ // 本地同步(避免刷新延迟)
+ posModel.setMemo(newMemo);
+ posModel.setIsEnableRealPositionDistance(isDistanceEnable);
+ // 切换回简单视图
+ posModel.setIsSimpleView(true);
+ notifyItemChanged(position);
+ // 回调Activity显示保存成功
+ if (mOnSavePosListener != null) {
+ mOnSavePosListener.onSavePositionClick();
+ }
+ hideSoftKeyboard(v);
+ LogUtils.d("PositionAdapter", "保存位置成功:ID=" + posId + ",备注=" + newMemo);
+ }
+ });
+
+ // 6.8 绑定任务列表(编辑模式:新增/修改/删除任务)
+ bindTaskView(holder, posId);
+ }
+
+ /**
+ * 绑定任务列表(编辑视图专属:管理当前位置的任务)
+ */
+ private void bindTaskView(final EditViewHolder holder, final String posId) {
+ // 从缓存获取当前位置的任务(无则从服务拉取)
+ ArrayList tasks = mPosToTasksMap.get(posId);
+ if (tasks == null) {
+ tasks = filterTasksByPosId(mService.getPositionTasksList(), posId);
+ mPosToTasksMap.put(posId, tasks);
+ }
+
+ // 示例:显示任务数量(实际项目需替换为任务列表RecyclerView)
+ holder.tvTaskCount.setText("任务数量:" + tasks.size());
+
+ // 新增任务→同步到服务
+ holder.btnAddTask.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mService == null || !(mService instanceof DistanceRefreshService)) {
+ LogUtils.e("PositionAdapter", "新增任务失败:服务无效");
+ return;
+ }
+ // 创建默认任务(生成唯一ID)
+ PositionTaskModel newTask = new PositionTaskModel();
+ newTask.setTaskId(PositionTaskModel.genTaskId()); // 需在PositionTaskModel实现静态生成唯一ID方法
+ newTask.setPositionId(posId);
+ newTask.setTaskDescription(DEFAULT_TASK_DESC);
+ newTask.setIsEnable(true);
+ newTask.setDiscussDistance(DEFAULT_TASK_DISTANCE);
+
+// 更新本地缓存
+ ArrayList currentTasks = mPosToTasksMap.get(posId);
+ if (currentTasks == null) {
+ currentTasks = new ArrayList();
+ }
+ currentTasks.add(newTask);
+ mPosToTasksMap.put(posId, currentTasks);
+
+// 全量同步到服务(服务处理去重+存储)
+ ((DistanceRefreshService) mService).syncAllPositionTasks(currentTasks);
+// 刷新任务数量显示
+ holder.tvTaskCount.setText("任务数量:" + currentTasks.size());
+// 回调Activity提示任务新增成功
+ if (mOnSaveTaskListener != null) {
+ mOnSaveTaskListener.onSavePositionTaskClick();
+ }
+ LogUtils.d("PositionAdapter", "新增任务成功:位置ID=" + posId + ",任务ID=" + newTask.getTaskId());
+ }
+ });
+ }
+
+// =========================================================================
+// 7. 核心工具方法(距离更新、数据刷新、服务通知等)
+// =========================================================================
+ /**
+
+ - 更新指定位置的距离UI(服务回调入口)
+ */
+ private void updatePositionDistanceUI(String positionId) {
+ if (positionId == null || mPosDistanceViewMap.get(positionId) == null) {
+ LogUtils.w("PositionAdapter", "更新距离失败:位置ID无效或控件未缓存");
+ return;
+ }
+// 从服务获取最新位置模型(确保数据是最新的)
+ PositionModel latestPos = getPositionById(positionId);
+ if (latestPos == null) {
+ LogUtils.w("PositionAdapter", "更新距离失败:未找到位置ID=" + positionId);
+ return;
+ }
+// 刷新距离显示
+ updateDistanceDisplay(mPosDistanceViewMap.get(positionId), latestPos);
+ }
+
+ /**
+
+ - 显示距离(根据位置状态显示不同文本/颜色)
+ */
+ private void updateDistanceDisplay(TextView distanceView, PositionModel posModel) {
+ if (distanceView == null || posModel == null) return;// 距离未启用
+ if (!posModel.isEnableRealPositionDistance()) {
+ distanceView.setText(DISTANCE_DISABLED);
+ distanceView.setTextColor(mContext.getResources().getColor(R.color.gray));
+ return;
+ }// 距离计算失败(值为-1标记)
+ double distance = posModel.getRealPositionDistance();
+ if (distance < 0) {
+ distanceView.setText(DISTANCE_ERROR);
+ distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
+ return;
+ }// 正常显示距离(根据距离范围设置颜色:近=绿、中=黄、远=红)
+ 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));
+ }
+ }
+
+ /**
+
+ - 通知服务:位置可见性变化(控制是否计算距离,优化性能)
+ */
+ private void notifyServicePositionVisible(String positionId, boolean isVisible) {
+ if (mService == null || positionId == null) return;if (isVisible) {
+ mService.addVisibleDistanceView(positionId);
+ } else {
+ mService.removeVisibleDistanceView(positionId);
+ }
+ }
+
+ /**
+
+ - 根据位置ID获取最新位置模型(从服务拉取,确保数据最新)
+ */
+ private PositionModel getPositionById(String positionId) {
+ if (mService == null || positionId == null) return null;ArrayList posList = mService.getPositionList();
+ Iterator iter = posList.iterator();
+ while (iter.hasNext()) {
+ PositionModel pos = (PositionModel)iter.next();
+ if (positionId.equals(pos.getPositionId())) {
+ return pos;
}
-
- mPositionTaskMap.put(validPosId, matchedTasks);
- mPositionIdToIndexMap.put(validPosId, mPositionList.indexOf(model));
}
-
-// 绑定服务
- bindDistanceRefreshService();
+ return null;
}
-// ---------------------- 服务绑定/解绑 ----------------------
- private void bindDistanceRefreshService() {
- if (!isServiceBound && mContext != null) {
- Intent serviceIntent = new Intent(mContext, DistanceRefreshService.class);
- mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ /**
+
+ - 根据列表索引获取位置模型(从服务拉取最新数据)
+ */
+ private PositionModel getPositionByIndex(int index) {
+ if (mService == null || index < 0 || index >= getItemCount()) {
+ LogUtils.w("PositionAdapter", "获取位置失败:索引无效(" + index + ")");
+ return null;
}
+ return mService.getPositionList().get(index);
}
- public void unbindDistanceRefreshService() {
- if (isServiceBound && mContext != null) {
- if (mDistanceService != null) {
- mDistanceService.clearVisibleDistanceViews();
- mDistanceService.setOnDistanceUpdateReceiver(null);
+ /**
+
+ - 过滤指定位置的任务(从所有任务中筛选当前位置的任务)
+ */
+ private ArrayList filterTasksByPosId(ArrayList allTasks, String posId) {
+ ArrayList targetTasks = new ArrayList();
+ if (allTasks == null || posId == null) return targetTasks;Iterator iter = allTasks.iterator();
+ while (iter.hasNext()) {
+ PositionTaskModel task = (PositionTaskModel)iter.next();
+ if (posId.equals(task.getPositionId())) {
+ targetTasks.add(task);
}
- mContext.unbindService(mServiceConnection);
- isServiceBound = false;
- mDistanceService = null;
- LogUtils.d(TAG, "DistanceRefreshService 解绑成功");
+ }
+ return targetTasks;
+ }
+
+ /**
+
+ - 刷新位置ID→索引映射(服务数据变化后调用,如新增/删除位置)
+ */
+ public void refreshPositionIndexMap() {
+ if (mService == null) return;try {
+ mPosIdToIndexMap.clear();
+ ArrayList posList = mService.getPositionList();
+ for (int i = 0; i < posList.size(); i++) {
+ mPosIdToIndexMap.put(((PositionModel)posList.get(i)).getPositionId(), i);
+ }
+ } catch (ConcurrentModificationException e) {
+ LogUtils.d(TAG, "刷新位置映射失败:并发修改异常" + e);
}
}
-// ---------------------- 对外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));
+ /**
+
+ - 刷新位置→任务映射(服务任务变化后调用,如新增/删除任务)
+ */
+ public void refreshPositionTaskMap() {
+ if (mService == null) return;try {
+ mPosToTasksMap.clear();
+ ArrayList allTasks = mService.getPositionTasksList();
+ Iterator iter = allTasks.iterator();
+ while (iter.hasNext()) {
+ PositionTaskModel task = (PositionTaskModel)iter.next();
+ String posId = task.getPositionId();
+ if (!mPosToTasksMap.containsKey(posId)) {
+ mPosToTasksMap.put(posId, new ArrayList());
+ }
+ mPosToTasksMap.get(posId).add(task);
+ }
+ } catch (ConcurrentModificationException e) {
+ LogUtils.d(TAG, "刷新任务映射失败:并发修改异常。" + e);
}
}
+ /**
+
+ - 全量更新列表(Activity 新增/删除位置后调用,同步服务数据)
+ */
+ public void updateAllPositions(ArrayList newPosList) {
+ if (newPosList == null) return;
+// 刷新本地缓存
+ refreshPositionIndexMap();
+ refreshPositionTaskMap();
+// 通知RecyclerView刷新所有项
+ 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);
+ }
+ }
+
+ /**
+
+ - 释放资源(Activity 销毁时调用,避免内存泄漏)
+ */
+ public void release() {
+// 清空本地缓存
+ mPosIdToIndexMap.clear();
+ mPosToTasksMap.clear();
+ mPosDistanceViewMap.clear();
+// 告诉服务清空可见位置(停止计算距离)
+ if (mService != null) {
+ mService.clearVisibleDistanceViews();
+ }
+// 置空回调(避免Activity内存泄漏)
+ mOnDeleteListener = null;
+ mOnSavePosListener = null;
+ mOnSaveTaskListener = null;
+ LogUtils.d("PositionAdapter", "资源已释放");
+ }
+
+// =========================================================================
+// 8. 回调设置方法(Activity 调用此方法设置回调)
+// =========================================================================
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
- this.mOnDeleteClickListener = listener;
+ this.mOnDeleteListener = listener;
}
public void setOnSavePositionClickListener(OnSavePositionClickListener listener) {
- this.mOnSavePositionClickListener = listener;
+ this.mOnSavePosListener = listener;
}
public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) {
- this.mOnSavePositionTaskClickListener = listener;
- }
-
- public void stopTimer() {
- unbindDistanceRefreshService();
- mVisibleDistanceViews.clear();
- }
-
-// ---------------------- 服务消息接收(仅接收位置ID,从mPositionList读数据) ----------------------
- @Override
- public void onDistanceUpdate(final String positionId) {
-// 保障主线程更新UI(Java7:匿名内部类实现Runnable)
- if (Looper.myLooper() != Looper.getMainLooper()) {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- updateDistanceViewByPositionId(positionId);
- }
- });
- } else {
- updateDistanceViewByPositionId(positionId);
- }
+ this.mOnSaveTaskListener = listener;
}
+// =========================================================================
+// 9. 视图Holder类(静态内部类,避免内存泄漏)
+// =========================================================================
/**
- - 核心修改:通过位置ID从mPositionList读取realPositionDistance,生成UI数据并更新
+ - 简单视图Holder(仅显示数据,无编辑控件)
*/
- private void updateDistanceViewByPositionId(String positionId) {
-// 1. 前置校验(避免无效更新)
- if (positionId == null || mPositionList.isEmpty() || mVisibleDistanceViews.isEmpty()) {
- LogUtils.w(TAG, "更新UI失败:参数无效/列表为空(位置ID=" + positionId + ")");
- return;
- }// 2. 从mPositionList找到对应位置模型(Java7:迭代器遍历)
- PositionModel targetModel = null;
- Iterator modelIter = mPositionList.iterator();
- while (modelIter.hasNext()) {
- PositionModel model = (PositionModel)modelIter.next();
- if (positionId.equals(model.getPositionId())) {
- targetModel = model;
- break;
- }
- }
- if (targetModel == null) {
- LogUtils.w(TAG, "更新UI失败:位置ID=" + positionId + " 未找到对应模型");
- return;
- }// 3. 读取realPositionDistance,生成距离文本和颜色
- double distanceM = targetModel.getRealPositionDistance();
- String distanceText;
- int distanceColor;if (distanceM == -1) {
-// 距离为-1:区分“未启用”和“计算异常”
- if (targetModel.isEnableRealPositionDistance()) {
- distanceText = "实时距离:计算异常";
- distanceColor = getColorRes(R.color.colorRed);
- } else {
- distanceText = "实时距离:未启用";
- distanceColor = getColorRes(R.color.colorGrayText);
- }
- } else {
-// 距离有效:格式化显示(米/千米)
- if (distanceM < 1000) {
- distanceText = String.format("实时距离:%.1f 米", distanceM);
- } else {
- distanceText = String.format("实时距离:%.1f 千米", distanceM / 1000);
- }
- distanceColor = getColorRes(R.color.colorEnableGreen);
- }// 4. 刷新可见控件(仅更新屏幕上显示的控件)
- TextView distanceView = mVisibleDistanceViews.get(positionId);
- if (distanceView != null && distanceView.isShown()) {
- distanceView.setText(distanceText);
- distanceView.setTextColor(distanceColor);
- LogUtils.d(TAG, "更新UI成功:位置ID=" + positionId + ",文本=" + distanceText);
-// 同步刷新任务状态(原逻辑保留)
- refreshTaskBingoStatus(positionId);
- } else {
-// 控件不存在/不可见:清理映射+同步服务
- if (mVisibleDistanceViews.containsKey(positionId)) {
- mVisibleDistanceViews.remove(positionId);
- LogUtils.w(TAG, "更新UI失败:位置ID=" + positionId + " 控件不可见/已移除");
- }
- if (isServiceBound && mDistanceService != null) {
- mDistanceService.removeVisibleDistanceView(positionId);
- }
- }
- }
-
- /**
-
- - 保留:从上下文获取颜色资源(异常时返回默认灰色)
- */
- @Override
- public int getColorRes(int resId) {
- if (mContext != null) {
- try {
- 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);
- }
-
-// ---------------------- 任务状态刷新(原逻辑保留) ----------------------
- 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 核心方法(原逻辑保留,适配数据来源) ----------------------
- @Override
- public int getItemViewType(int position) {
- 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;
- }
-
- @NonNull
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- if (viewType == VIEW_TYPE_SIMPLE) {
- View view = inflater.inflate(R.layout.item_position_simple, parent, false);
- return new SimpleViewHolder(view);
- } else {
- View view = inflater.inflate(R.layout.item_position_edit, parent, false);
- return new EditViewHolder(view);
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
- if (position < 0 || position >= mPositionList.size()) {
- LogUtils.w(TAG, "onBindViewHolder:无效列表位置(position=" + position + ")");
- return;
- }
- PositionModel model = mPositionList.get(position);
- mPositionIdToIndexMap.put(model.getPositionId(), position);
-
- if (holder instanceof SimpleViewHolder) {
- bindSimpleView((SimpleViewHolder) holder, model, position);
- } else if (holder instanceof EditViewHolder) {
- bindEditView((EditViewHolder) holder, model, position);
- }
- }
-
- @Override
- public int getItemCount() {
- return mPositionList.size();
- }
-
-// ---------------------- 视图附加/分离/回收(原逻辑保留,优化性能) ----------------------
- @Override
- public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
- super.onViewAttachedToWindow(holder);
- int position = holder.getAdapterPosition();
- if (position < 0 || position >= 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);
- }
- }
- }
-
- @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);
- }
- }
-
-// 清理任务列表资源
- if (holder instanceof SimpleViewHolder) {
- SimpleViewHolder simpleHolder = (SimpleViewHolder) holder;
- if (simpleHolder.ptlvSimpleTasks != null) {
- simpleHolder.ptlvSimpleTasks.clearData();
- simpleHolder.ptlvSimpleTasks.setOnTaskUpdatedListener(null);
- }
- } else if (holder instanceof EditViewHolder) {
- 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) {
-// 绑定基础数据(保留6位小数)
- holder.tvSimpleLongitude.setText(String.format("经度:%.6f", model.getLongitude()));
- holder.tvSimpleLatitude.setText(String.format("纬度:%.6f", model.getLatitude()));
- holder.tvSimpleMemo.setText(String.format("备注:%s", model.getMemo()));// 初始化距离文本(从mPositionList读realPositionDistance)
- double initDistance = model.getRealPositionDistance();
- if (initDistance == -1) {
- holder.tvSimpleRealDistance.setText(model.isEnableRealPositionDistance() ? "实时距离:计算异常" : "实时距离:未启用");
- holder.tvSimpleRealDistance.setTextColor(getColorRes(model.isEnableRealPositionDistance() ? R.color.colorRed : R.color.colorGrayText));
- } else {
- String initText = initDistance < 1000 ?
- String.format("实时距离:%.1f 米", initDistance) :
- String.format("实时距离:%.1f 千米", initDistance / 1000);
- holder.tvSimpleRealDistance.setText(initText);
- holder.tvSimpleRealDistance.setTextColor(getColorRes(R.color.colorEnableGreen));
- }
- LogUtils.d(TAG, "绑定简单视图:初始化距离(位置ID=" + model.getPositionId() + ")");// 长按编辑逻辑(匿名内部类)
- holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- PopupMenu popupMenu = new PopupMenu(mContext, v);
- MenuInflater menuInflater = popupMenu.getMenuInflater();
- menuInflater.inflate(R.menu.menu_item_edit, popupMenu.getMenu());popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if (item.getItemId() == R.id.menu_edit) {
- model.setIsSimpleView(false);
- notifyItemChanged(position);
- return true;
- }
- return false;
- }
- });
- popupMenu.show();
- return true;
- }
- });// 绑定任务列表
- String currentPosId = model.getPositionId();
- ArrayList matchedTasks = getSafeTasks(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());// 初始化距离文本(从mPositionList读realPositionDistance)
- double initDistance = model.getRealPositionDistance();
- if (initDistance == -1) {
- holder.tvEditRealDistance.setText(model.isEnableRealPositionDistance() ? "实时距离:计算异常" : "实时距离:未启用");
- holder.tvEditRealDistance.setTextColor(getColorRes(model.isEnableRealPositionDistance() ? R.color.colorRed : R.color.colorGrayText));
- } else {
- String initText = initDistance < 1000 ?
- String.format("实时距离:%.1f 米", initDistance) :
- String.format("实时距离:%.1f 千米", initDistance / 1000);
- holder.tvEditRealDistance.setText(initText);
- holder.tvEditRealDistance.setTextColor(getColorRes(R.color.colorEnableGreen));
- }
- LogUtils.d(TAG, "绑定编辑视图:初始化距离(位置ID=" + model.getPositionId() + ")");
-
-// 实时距离开关状态(显式判断设置选中)
- if (model.isEnableRealPositionDistance()) {
- holder.rgRealDistanceSwitch.check(R.id.rb_enable);
- } else {
- holder.rgRealDistanceSwitch.check(R.id.rb_disable);
- }
-
-// 删除按钮逻辑(匿名内部类)
- 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());
- mDistanceService.syncPositionList(mPositionList); // 同步更新后的列表
- }
- }
- }
- });
-
-// 取消编辑逻辑
- holder.btnEditCancel.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- model.setIsSimpleView(true);
- notifyItemChanged(position);
- holder.etEditMemo.clearFocus();
- }
- });
-
-// 确认保存逻辑
- holder.btnEditConfirm.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
-// 获取输入数据(显式trim避免空字符串)
- String newMemo = holder.etEditMemo.getText().toString().trim();
- model.setMemo(newMemo);
- boolean isEnableDistance = holder.rgRealDistanceSwitch.getCheckedRadioButtonId() == R.id.rb_enable;
- model.setIsEnableRealPositionDistance(isEnableDistance);
-
-// 绑定任务到当前位置
- String currentPosId = model.getPositionId();
- ArrayList allTasks = (holder.ptlvEditTasks != null) ? holder.ptlvEditTasks.getAllTasks() : new ArrayList();
- ArrayList boundTasks = new ArrayList();
-
-// 迭代器遍历:绑定位置ID到任务
- Iterator taskIterator = allTasks.iterator();
- while (taskIterator.hasNext()) {
- PositionTaskModel task = (PositionTaskModel)taskIterator.next();
- if (task != null) {
- task.setPositionId(currentPosId);
- boundTasks.add(task);
- }
- }
-
-// 更新本地任务映射
- mPositionTaskMap.put(currentPosId, boundTasks);
-// 清理旧任务(迭代器安全删除,避免并发异常)
- Iterator globalTaskIter = mAllPositionTasks.iterator();
- while (globalTaskIter.hasNext()) {
- PositionTaskModel task = (PositionTaskModel)globalTaskIter.next();
- if (task != null && currentPosId.equals(task.getPositionId())) {
- globalTaskIter.remove();
- }
- }
- 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) {
- mOnSavePositionClickListener.onSavePositionClick();
- }
- Toast.makeText(mContext, "位置信息已保存", Toast.LENGTH_SHORT).show();
- }
- });
-
-// 任务列表绑定与更新逻辑(匿名内部类实现监听)
- final String currentPosId = model.getPositionId();
- ArrayList matchedTasks = getSafeTasks(currentPosId);
- 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();
-// 绑定位置ID到任务
- Iterator taskIterator = updatedTasks.iterator();
- while (taskIterator.hasNext()) {
- PositionTaskModel task = (PositionTaskModel)taskIterator.next();
- if (task != null) {
- task.setPositionId(currentPosId);
- boundTasks.add(task);
- }
- }
-
-// 更新本地映射
- mPositionTaskMap.put(currentPosId, boundTasks);
-// 清理旧任务+添加新任务
- Iterator globalTaskIter = mAllPositionTasks.iterator();
- while (globalTaskIter.hasNext()) {
- PositionTaskModel task = (PositionTaskModel)globalTaskIter.next();
- if (task != null && currentPosId.equals(task.getPositionId())) {
- globalTaskIter.remove();
- }
- }
- mAllPositionTasks.addAll(boundTasks);
-
-// 同步服务
- if (isServiceBound && mDistanceService != null) {
- mDistanceService.syncAllPositionTasks(mAllPositionTasks);
- LogUtils.d(TAG, "任务更新:同步到服务(位置ID=" + currentPosId + ",任务数=" + boundTasks.size() + ")");
- }
-
-// 回调通知
- 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.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;
- }
-
-// 创建新任务(显式调用构造函数)
- PositionTaskModel newTask = new PositionTaskModel(
- PositionTaskModel.genTaskId(),
- currentPosId,
- DEFAULT_TASK_DESC,
- true,
- DEFAULT_TASK_DISTANCE,
- true
- );
-
-// 更新本地任务列表
- ArrayList 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);
- Toast.makeText(mContext, "已新增1个任务(绑定当前位置)", Toast.LENGTH_SHORT).show();
- }
- });
- }
-
-// ---------------------- 工具方法(原逻辑保留,适配数据来源) ----------------------
- /**
-
- - 安全获取任务列表(避免空指针)
- */
- private ArrayList 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);
- }
-
- /**
-
- - 新增位置(同步服务时传递原列表引用)
- */
- 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 + ",列表总数=" + mPositionList.size() + ")");
- }notifyItemInserted(mPositionList.size() - 1);
- }
-
- /**
-
- - 删除位置(同步服务时传递更新后的列表)
- */
- 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;
- }// 1. 清理本地映射
- mVisibleDistanceViews.remove(removedPosId);
- mPositionIdToIndexMap.remove(removedPosId);
- LogUtils.d(TAG, "删除位置:清理本地映射(位置ID=" + removedPosId + ")");// 2. 重新同步剩余位置的索引
- Iterator modelIterator = mPositionList.iterator();
- int index = 0;
- while (modelIterator.hasNext()) {
- PositionModel remainingModel = (PositionModel)modelIterator.next();
- mPositionIdToIndexMap.put(remainingModel.getPositionId(), index);
- index++;
- }// 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);
- LogUtils.d(TAG, "删除位置:清理任务数据(剩余列表数=" + mPositionList.size() + ")");// 4. 同步服务(传递更新后的列表)
- if (isServiceBound && mDistanceService != null) {
- mDistanceService.syncPositionList(mPositionList); // 同步删除后的列表
- mDistanceService.syncAllPositionTasks(mAllPositionTasks);
- mDistanceService.removeVisibleDistanceView(removedPosId);
- LogUtils.d(TAG, "删除位置:同步到服务(位置ID=" + removedPosId + ")");
- }notifyItemRemoved(position);
- notifyItemRangeChanged(position, mPositionList.size());
- }
-
- /**
-
- - 更新所有位置(传递原列表引用,清理无效映射)
- */
- public void updateAllPositions(ArrayList newPositionList) {
- if (newPositionList == null) {
- LogUtils.e(TAG, "更新所有位置失败:新列表为空");
- return;
- }
- mPositionList.clear();
- mPositionList.addAll(newPositionList);
- LogUtils.d(TAG, "更新所有位置:接收新列表(数量=" + newPositionList.size() + ")");// 1. 清理无效可见控件
- Iterator distanceViewIter = mVisibleDistanceViews.keySet().iterator();
- while (distanceViewIter.hasNext()) {
- String posId = (String)distanceViewIter.next();
- boolean isExist = false;
- Iterator modelIter = newPositionList.iterator();
- while (modelIter.hasNext()) {
- if (posId.equals(((PositionModel)modelIter.next()).getPositionId())) {
- isExist = true;
- break;
- }
- }
- if (!isExist) {
- distanceViewIter.remove();
- LogUtils.d(TAG, "更新所有位置:清理无效控件(位置ID=" + posId + ")");
- }
- }// 2. 清理无效任务映射
- Iterator taskMapIter = mPositionTaskMap.keySet().iterator();
- while (taskMapIter.hasNext()) {
- String posId = (String)taskMapIter.next();
- boolean isExist = false;
- Iterator modelIter = newPositionList.iterator();
- while (modelIter.hasNext()) {
- if (posId.equals(((PositionModel)modelIter.next()).getPositionId())) {
- isExist = true;
- break;
- }
- }
- if (!isExist) {
- taskMapIter.remove();
- LogUtils.d(TAG, "更新所有位置:清理无效任务映射(位置ID=" + posId + ")");
- }
- }// 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());
- LogUtils.d(TAG, "更新所有位置:初始化新任务列表(位置ID=" + posId + ")");
- }
- }// 4. 同步服务(传递原列表引用)
- if (isServiceBound && mDistanceService != null) {
- mDistanceService.syncPositionList(mPositionList); // 同步更新后的列表
- mDistanceService.clearVisibleDistanceViews();
-// 同步有效可见控件
- Iterator validPosIter = mVisibleDistanceViews.keySet().iterator();
- while (validPosIter.hasNext()) {
- mDistanceService.addVisibleDistanceView((String)validPosIter.next());
- }
- LogUtils.d(TAG, "更新所有位置:同步到服务(可见控件数=" + mVisibleDistanceViews.size() + ")");
- }notifyDataSetChanged();
- }
-
- /**
-
- - 批量切换简单视图
- */
- public void switchAllToSimpleView() {
- Iterator modelIterator = mPositionList.iterator();
- while (modelIterator.hasNext()) {
- ((PositionModel)modelIterator.next()).setIsSimpleView(true);
- }
- notifyDataSetChanged();
- LogUtils.d(TAG, "批量切换简单视图:已切换所有位置(总数=" + mPositionList.size() + ")");
- }
-
- /**
-
- - 获取位置列表(返回新列表,避免外部修改原引用)
- */
- public ArrayList getPositionList() {
- return new ArrayList(mPositionList);
- }
-
-// ---------------------- ViewHolder 定义(完全兼容Java7) ----------------------
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
- TextView tvSimpleLongitude;
- TextView tvSimpleLatitude;
- TextView tvSimpleMemo;
- TextView tvSimpleRealDistance;
- PositionTaskListView ptlvSimpleTasks;
-
- public SimpleViewHolder(@NonNull View itemView) {
+ TextView tvSimpleLon; // 经度
+ TextView tvSimpleLat; // 纬度
+ TextView tvSimpleMemo; // 备注
+ TextView tvSimpleDistance; // 实时距离
+ public SimpleViewHolder(View itemView) {
super(itemView);
-// 显式findViewById+类型转换(Java7兼容)
- tvSimpleLongitude = (TextView) itemView.findViewById(R.id.tv_simple_longitude);
- tvSimpleLatitude = (TextView) itemView.findViewById(R.id.tv_simple_latitude);
+// 绑定布局控件(对应 item_position_simple.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);
- tvSimpleRealDistance = (TextView) itemView.findViewById(R.id.tv_simple_real_distance);
- ptlvSimpleTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_simple_tasks);
+ tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance);
}
}
+ /**
+
+ - 编辑视图Holder(含编辑控件、按钮等)
+ */
public static class EditViewHolder extends RecyclerView.ViewHolder {
- TextView tvEditLongitude;
- TextView tvEditLatitude;
- EditText etEditMemo;
- TextView tvEditRealDistance;
- RadioGroup rgRealDistanceSwitch;
- RadioButton rbEnable;
- RadioButton rbDisable;
- PositionTaskListView ptlvEditTasks;
- Button btnAddTask;
- Button btnEditConfirm;
- Button btnEditCancel;
- Button btnEditDelete;
-
- public EditViewHolder(@NonNull View itemView) {
+ TextView tvEditLon; // 经度
+ TextView tvEditLat; // 纬度
+ EditText etEditMemo; // 备注编辑框
+ TextView tvEditDistance;// 实时距离
+ RadioGroup rgDistanceSwitch; // 距离开关(启用/禁用)
+ RadioButton rbEnable; // 启用距离
+ RadioButton rbDisable; // 禁用距离
+ Button btnCancel; // 取消编辑
+ Button btnDelete; // 删除位置
+ Button btnSave; // 保存位置
+ Button btnAddTask; // 新增任务
+ TextView tvTaskCount; // 任务数量显示(示例用,实际替换为任务列表)
+ public EditViewHolder(View itemView) {
super(itemView);
-// 显式findViewById+类型转换(Java7兼容,避免空指针)
- tvEditLongitude = (TextView) itemView.findViewById(R.id.tv_edit_longitude);
- tvEditLatitude = (TextView) itemView.findViewById(R.id.tv_edit_latitude);
+// 绑定布局控件(对应 item_position_edit.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);
- tvEditRealDistance = (TextView) itemView.findViewById(R.id.tv_edit_real_distance);
- rgRealDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_real_distance_switch);
- rbEnable = (RadioButton) itemView.findViewById(R.id.rb_enable);
- rbDisable = (RadioButton) itemView.findViewById(R.id.rb_disable);
- ptlvEditTasks = (PositionTaskListView) itemView.findViewById(R.id.ptlv_edit_tasks);
+ tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance);
+ rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch);
+ rbEnable = (RadioButton) itemView.findViewById(R.id.rb_distance_enable);
+ rbDisable = (RadioButton) itemView.findViewById(R.id.rb_distance_disable);
+ 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);
- btnEditConfirm = (Button) itemView.findViewById(R.id.btn_edit_confirm);
- btnEditCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel);
- btnEditDelete = (Button) itemView.findViewById(R.id.btn_edit_delete);
+ tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count);
}
}
-
- /**
-
- - 辅助方法:获取指定位置ID的任务列表(对外提供只读访问)
- */
- public ArrayList getTasksByPositionId(String positionId) {
- ArrayList tasks = getSafeTasks(positionId);
- return new ArrayList(tasks); // 返回拷贝,避免外部修改原数据
- }
-
- /**
-
- - 辅助方法:清空所有数据(适配服务同步)
- */
- public void clearAllData() {
- mPositionList.clear();
- mAllPositionTasks.clear();
- mPositionTaskMap.clear();
- mVisibleDistanceViews.clear();
- mPositionIdToIndexMap.clear();// 同步服务清理
- if (isServiceBound && mDistanceService != null) {
- mDistanceService.syncPositionList(mPositionList);
- mDistanceService.syncAllPositionTasks(mAllPositionTasks);
- mDistanceService.clearVisibleDistanceViews();
- LogUtils.d(TAG, "清空所有数据:同步服务完成");
- }notifyDataSetChanged();
- LogUtils.d(TAG, "清空所有数据:本地数据已重置");
- }
-
- /**
-
- - 生命周期方法:解绑服务(避免内存泄漏,供外部调用)
- */
- public void onDestroy() {
- unbindDistanceRefreshService();
-// 释放强引用
- mContext = null;
- mCurrentGpsPosition = null;
- mOnDeleteClickListener = null;
- mOnSavePositionClickListener = null;
- mOnSavePositionTaskClickListener = null;
- LogUtils.d(TAG, "Adapter生命周期结束:已解绑服务并释放资源");
- }
}
+
diff --git a/positions/src/main/java/cc/winboll/studio/positions/models/AppConfigsModel.java b/positions/src/main/java/cc/winboll/studio/positions/models/AppConfigsModel.java
new file mode 100644
index 00000000..1b53b61a
--- /dev/null
+++ b/positions/src/main/java/cc/winboll/studio/positions/models/AppConfigsModel.java
@@ -0,0 +1,71 @@
+package cc.winboll.studio.positions.models;
+
+/**
+ * @Author ZhanGSKen&豆包大模型
+ * @Date 2025/10/01 04:50
+ * @Describe AppConfigsModel
+ */
+ import cc.winboll.studio.libappbase.BaseBean;
+import android.util.JsonWriter;
+import android.util.JsonReader;
+import java.io.IOException;
+
+public class AppConfigsModel extends BaseBean {
+
+ public static final String TAG = "AppConfigsModel";
+
+ boolean isEnableDistanceRefreshService = false;
+
+ public AppConfigsModel(boolean isEnableDistanceRefreshService) {
+ this.isEnableDistanceRefreshService = isEnableDistanceRefreshService;
+ }
+
+ public void setIsEnableDistanceRefreshService(boolean isEnableDistanceRefreshService) {
+ this.isEnableDistanceRefreshService = isEnableDistanceRefreshService;
+ }
+
+ public boolean isEnableDistanceRefreshService() {
+ return isEnableDistanceRefreshService;
+ }
+
+ @Override
+ public String getName() {
+ return AppConfigsModel.class.getName();
+ }
+
+ // JSON序列化(保存位置数据)
+ @Override
+ public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
+ super.writeThisToJsonWriter(jsonWriter);
+ jsonWriter.name("isEnableDistanceRefreshService").value(isEnableDistanceRefreshService());
+ }
+
+ // JSON反序列化(加载位置数据,校验字段)
+ @Override
+ public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
+ if (super.initObjectsFromJsonReader(jsonReader, name)) {
+ return true;
+ } else {
+ if (name.equals("isEnableDistanceRefreshService")) {
+ setIsEnableDistanceRefreshService(jsonReader.nextBoolean());
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // 从JSON读取位置数据
+ @Override
+ public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String name = jsonReader.nextName();
+ if (!initObjectsFromJsonReader(jsonReader, name)) {
+ jsonReader.skipValue(); // 跳过未知字段
+ }
+ }
+ jsonReader.endObject();
+ return this;
+ }
+}
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
index b20d1f55..64530859 100644
--- a/positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java
+++ b/positions/src/main/java/cc/winboll/studio/positions/services/DistanceRefreshService.java
@@ -3,308 +3,458 @@ package cc.winboll.studio.positions.services;
/**
* @Author ZhanGSKen&豆包大模型
* @Date 2025/09/30 19:53
- * @Describe DistanceRefreshService
+ * @Describe 位置距离服务:管理数据+定时计算距离+适配Adapter(Java 7 兼容)
*/
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
-import android.os.Bundle;
-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.adapters.PositionAdapter;
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.HashSet;
import java.util.Iterator;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import cc.winboll.studio.positions.models.AppConfigsModel;
/**
- * 距离刷新服务:独立管理定时器,负责实时距离计算、任务触发判断、发送UI更新消息
- * 特性:1. 自启动(绑定后自动启动定时器) 2. 数据与Activity/Adapter同步 3. 本地消息通知UI更新
- * Java 7 适配:移除Lambda、Stream,使用匿名内部类+迭代器,明确泛型声明
+ * 核心职责:
+ * 1. 实现 PositionAdapter.DistanceServiceInterface 接口,解耦Adapter与服务
+ * 2. 单例式管理位置/任务数据,提供安全增删改查接口
+ * 3. 后台单线程定时计算可见位置距离,主线程回调更新UI
+ * 4. 严格Java 7语法:无Lambda/Stream,显式迭代器/匿名内部类
*/
-public class DistanceRefreshService extends Service {
- // 常量定义
+public class DistanceRefreshService extends Service implements PositionAdapter.DistanceServiceInterface {
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";
+ // 服务状态与配置
+ private boolean isServiceRunning = false;
+ private final ScheduledExecutorService distanceExecutor; // 定时计算线程池(单线程)
+ private static final int REFRESH_INTERVAL = 3; // 距离刷新间隔(秒)
- // 核心成员变量(Java7:明确泛型初始化)
- private Timer mDistanceTimer;
- private Handler mMainHandler;
- private PositionModel mCurrentGpsPosition;
- private ArrayList mPositionList; // 持有Adapter传递的原列表引用
- private ArrayList mAllPositionTasks;
- private Map mVisibleDistanceViewTags = new HashMap();
- private OnDistanceUpdateReceiver mUpdateReceiver;
- private boolean isPositionListSynced = false; // 新增:标记位置列表是否已同步
+ // 核心数据存储(服务内唯一数据源,避免外部直接修改)
+ private final ArrayList mPositionList = new ArrayList();
+ private final ArrayList mTaskList = new ArrayList();
+ private final Set mVisiblePositionIds = new HashSet(); // 可见位置ID(优化性能)
+ private PositionModel mCurrentGpsPosition; // 当前GPS位置(外部传入)
- // 数据同步与消息接收接口(Activity/Adapter实现)
- public interface OnDistanceUpdateReceiver {
- void onDistanceUpdate(String positionId); // 简化:仅传递位置ID
- int getColorRes(int resId);
+ // 服务绑定与UI回调
+ private final IBinder mBinder = new DistanceBinder();
+ private PositionAdapter.OnDistanceUpdateReceiver mDistanceReceiver; // Adapter回调接收器
+
+ // ---------------------- 构造初始化(线程池提前创建) ----------------------
+ public DistanceRefreshService() {
+ // Java 7 显式初始化线程池(单线程,避免并发修改数据)
+ distanceExecutor = Executors.newSingleThreadScheduledExecutor();
}
- // 服务绑定器(用于外部获取服务实例)
+ // ---------------------- Binder 内部类(供外部绑定服务) ----------------------
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) {
- // 解析消息:仅获取位置ID
- Bundle data = msg.getData();
- String positionId = data.getString(KEY_POSITION_ID);
- if (positionId != null) {
- LogUtils.d(TAG, "接收消息→转发更新:位置ID=" + positionId);
- mUpdateReceiver.onDistanceUpdate(positionId);
- }
- }
- }
- };
- // 初始化数据集(Java7:明确泛型类型)
- mPositionList = new ArrayList();
- mAllPositionTasks = new ArrayList();
+ LogUtils.d(TAG, "服务 onCreate:初始化完成,等待启动命令");
+ run();
}
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ run();
+ AppConfigsModel bean = AppConfigsModel.loadBean(DistanceRefreshService.this, AppConfigsModel.class);
+ boolean isEnableService = (bean == null) ? false : bean.isEnableDistanceRefreshService();
+ return isEnableService ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
+ }
+
+ void run() {
+ // 仅服务未运行时启动(避免重复启动)
+ if (!isServiceRunning) {
+ isServiceRunning = true;
+ startDistanceRefreshTask(); // 启动定时距离计算
+ LogUtils.d(TAG, "服务 onStartCommand:启动成功,刷新间隔=" + REFRESH_INTERVAL + "秒");
+ } else {
+ LogUtils.w(TAG, "服务 onStartCommand:已在运行,无需重复启动");
+ }
+ }
+
@Override
public IBinder onBind(Intent intent) {
- // 绑定服务时启动定时器(确保仅启动一次)
- if (mDistanceTimer == null) {
- startDistanceTimer();
- Log.d(TAG, "DistanceRefreshService onBind - 定时器首次启动");
- } else {
- Log.d(TAG, "DistanceRefreshService onBind - 定时器已在运行,无需重复启动");
- }
- return mBinder;
+ LogUtils.d(TAG, "服务 onBind:外部绑定成功(运行状态:" + (isServiceRunning ? "是" : "否") + ")");
+ return mBinder; // 返回Binder实例,供外部获取服务
}
- /**
- * 启动定时器:核心逻辑(距离计算、任务触发、发送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, "定时器触发→开始计算距离,位置列表同步状态=" + isPositionListSynced);
- calculateAndSendDistanceUpdates();
- checkAndTriggerTasks();
- }
- }, 0, REFRESH_INTERVAL); // 立即执行,之后每5秒执行一次
- }
-
- /**
- * 核心修改:计算距离并更新到mPositionList,仅发送位置ID通知
- * Java7:使用迭代器遍历,显式空判断
- */
- private void calculateAndSendDistanceUpdates() {
- // 前置校验:位置列表未同步/为空/无GPS,直接返回
- if (!isPositionListSynced || mPositionList.isEmpty()) {
- LogUtils.d(TAG, "位置列表未同步/为空,跳过距离计算");
- return;
- }
- if (mCurrentGpsPosition == null) {
- LogUtils.d(TAG, "无当前GPS位置,跳过距离计算");
- return;
- }
-
- // 遍历所有位置项,计算并设置realPositionDistance
- Iterator positionIter = mPositionList.iterator();
- while (positionIter.hasNext()) {
- PositionModel targetModel = positionIter.next();
- String positionId = targetModel.getPositionId();
-
- if (targetModel.isEnableRealPositionDistance()) {
- // 状态为true:计算距离(米),设置到realPositionDistance
- try {
- double distanceM = PositionModel.calculatePositionDistance(
- mCurrentGpsPosition, targetModel, false
- );
- targetModel.setRealPositionDistance(distanceM); // 存储计算结果到列表项
- LogUtils.d(TAG, "位置ID=" + positionId + " 计算距离:" + distanceM + "米");
- } catch (IllegalArgumentException e) {
- // 计算异常时,设置为-1(标记无效)
- targetModel.setRealPositionDistance(-1);
- LogUtils.e(TAG, "位置ID=" + positionId + " 距离计算异常:" + e.getMessage());
- }
- } else {
- // 状态为false:强制设置realPositionDistance为-1
- targetModel.setRealPositionDistance(-1);
- LogUtils.d(TAG, "位置ID=" + positionId + " 未启用距离计算,设置距离为-1");
- }
-
- // 发送更新通知(仅传位置ID)
- sendDistanceUpdateMessage(positionId);
- }
- }
-
- /**
- * 检查任务触发状态并发送通知
- * Java7:使用迭代器遍历任务列表(替代forEach Lambda)
- */
- private void checkAndTriggerTasks() {
- if (mAllPositionTasks.isEmpty() || !isPositionListSynced || mPositionList.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());
- }
- }
- }
-
- /**
- * 新增:判断任务触发状态(基于mPositionList中的realPositionDistance)
- * Java7:迭代器遍历任务列表,显式状态判断
- */
- private void judgeTaskBingoStatus() {
- if (!isPositionListSynced || mPositionList.isEmpty() || mAllPositionTasks.isEmpty()) {
- return;
- }
-
- Iterator posIter = mPositionList.iterator();
- while (posIter.hasNext()) {
- PositionModel posModel = posIter.next();
- String posId = posModel.getPositionId();
- double distanceM = posModel.getRealPositionDistance();
-
- // 遍历绑定当前位置的任务
- Iterator taskIter = mAllPositionTasks.iterator();
- while (taskIter.hasNext()) {
- PositionTaskModel task = taskIter.next();
- if (posId.equals(task.getPositionId()) && distanceM != -1) {
- boolean oldBingoState = task.isBingo();
- boolean newBingoState = false;
-
- // 根据任务条件判断新状态
- if (task.isGreaterThan()) {
- newBingoState = task.isEnable() && distanceM > task.getDiscussDistance();
- } else if (task.isLessThan()) {
- newBingoState = task.isEnable() && distanceM < task.getDiscussDistance();
- }
-
- // 仅状态变化时更新
- if (newBingoState != oldBingoState) {
- task.setIsBingo(newBingoState);
- }
- }
- }
- }
- }
-
- /**
- * 简化:仅发送位置ID通知(Adapter从mPositionList读取数据)
- * Java7:显式创建Message,避免obtainMessage链式调用
- */
- private void sendDistanceUpdateMessage(String positionId) {
- Message msg = mMainHandler.obtainMessage(MSG_UPDATE_DISTANCE);
- Bundle data = new Bundle();
- data.putString(KEY_POSITION_ID, positionId);
- msg.setData(data);
- mMainHandler.sendMessage(msg);
- LogUtils.d(TAG, "发送更新通知:位置ID=" + positionId);
- }
-
- /**
- * 核心修改:同步位置列表(持有原引用,不拷贝)
- */
- public void syncPositionList(ArrayList positionList) {
- if (positionList != null) {
- this.mPositionList = positionList; // 持有外部列表引用
- this.isPositionListSynced = true;
- LogUtils.d(TAG, "同步位置列表(持有引用):数量=" + positionList.size());
- } else {
- this.isPositionListSynced = false;
- LogUtils.w(TAG, "同步位置列表失败:传入列表为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 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 boolean onUnbind(Intent intent) {
+ LogUtils.d(TAG, "服务 onUnbind:外部解绑,清理回调与可见位置");
+ // 解绑后清理资源,避免内存泄漏
+ mDistanceReceiver = null;
+ mVisiblePositionIds.clear();
+ return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
- Log.d(TAG, "DistanceRefreshService onDestroy - 定时器销毁");
- // 销毁定时器,避免内存泄漏(Java7:显式判断非空)
- if (mDistanceTimer != null) {
- mDistanceTimer.cancel();
- mDistanceTimer.purge();
- }
- // 清空数据,解除引用(避免内存泄漏)
- mCurrentGpsPosition = null;
- mPositionList = null; // 释放列表引用
- mAllPositionTasks.clear();
- mVisibleDistanceViewTags.clear();
- mUpdateReceiver = null;
- isPositionListSynced = false;
+ // 停止线程池+清空数据(服务销毁后释放资源)
+ stopDistanceRefreshTask();
+ clearAllData();
+ isServiceRunning = false;
+ LogUtils.d(TAG, "服务 onDestroy:销毁完成,资源已释放");
}
+
+ // ---------------------- 核心:定时距离计算(后台线程+主线程回调) ----------------------
+ /**
+ * 启动定时距离计算任务(延迟1秒开始,周期执行)
+ */
+ private void startDistanceRefreshTask() {
+ if (distanceExecutor == null || distanceExecutor.isShutdown()) {
+ LogUtils.e(TAG, "启动计算失败:线程池未初始化/已关闭");
+ return;
+ }
+
+ // Java 7:匿名内部类实现 Runnable(不使用Lambda)
+ distanceExecutor.scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ // 仅满足所有条件时计算:服务运行+GPS有效+有可见位置
+ if (isServiceRunning && mCurrentGpsPosition != null && !mVisiblePositionIds.isEmpty()) {
+ calculateVisiblePositionDistance();
+ } else {
+ // 打印跳过原因(便于调试)
+ String reason = "";
+ if (!isServiceRunning) reason = "服务未运行";
+ else if (mCurrentGpsPosition == null) reason = "GPS无效";
+ else if (mVisiblePositionIds.isEmpty()) reason = "无可见位置";
+ LogUtils.d(TAG, "跳过距离计算:" + reason);
+ }
+ }
+ }, 1, REFRESH_INTERVAL, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 停止定时计算任务(强制关闭线程池)
+ */
+ private void stopDistanceRefreshTask() {
+ if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
+ distanceExecutor.shutdownNow(); // 立即停止所有任务
+ LogUtils.d(TAG, "距离计算任务已停止");
+ }
+ }
+
+ /**
+ * 计算可见位置与GPS的距离(Haversine公式,后台线程执行)
+ */
+ private void calculateVisiblePositionDistance() {
+ // 拷贝可见ID集合:避免遍历中修改引发 ConcurrentModificationException
+ Set tempVisibleIds = new HashSet(mVisiblePositionIds);
+ if (tempVisibleIds.isEmpty()) return;
+
+ // Java 7:迭代器遍历位置列表(安全删除/修改)
+ Iterator posIter = mPositionList.iterator();
+ while (posIter.hasNext()) {
+ PositionModel pos = posIter.next();
+ String posId = pos.getPositionId();
+
+ // 仅计算“可见+启用距离”的位置
+ if (tempVisibleIds.contains(posId) && pos.isEnableRealPositionDistance()) {
+ try {
+ // 调用Haversine公式计算距离(经纬度→米)
+ double distanceM = calculateHaversineDistance(
+ mCurrentGpsPosition.getLatitude(), mCurrentGpsPosition.getLongitude(),
+ pos.getLatitude(), pos.getLongitude()
+ );
+ // 更新位置距离(服务内数据实时同步)
+ pos.setRealPositionDistance(distanceM);
+ LogUtils.d(TAG, "计算完成:位置ID=" + posId + ",距离=" + String.format("%.1f", distanceM) + "米");
+
+ // 回调Adapter更新UI(确保主线程)
+ notifyDistanceUpdateToUI(posId);
+ } catch (Exception e) {
+ // 计算异常时标记距离为-1(Adapter识别为“计算异常”)
+ pos.setRealPositionDistance(-1);
+ notifyDistanceUpdateToUI(posId);
+ LogUtils.e(TAG, "计算失败(位置ID=" + posId + "):" + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * 主线程回调Adapter更新UI(避免跨线程操作UI异常)
+ */
+ private void notifyDistanceUpdateToUI(final String positionId) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // 已在主线程:直接回调
+ if (mDistanceReceiver != null) {
+ mDistanceReceiver.onDistanceUpdate(positionId);
+ }
+ } else {
+ // 子线程:通过主线程Handler切换(Java 7 显式创建Handler)
+ new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ if (mDistanceReceiver != null) {
+ mDistanceReceiver.onDistanceUpdate(positionId);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Haversine公式:计算两点间直线距离(经纬度→米,精度满足日常需求)
+ */
+ private double calculateHaversineDistance(double gpsLat, double gpsLon, double posLat, double posLon) {
+ final double EARTH_RADIUS = 6371000; // 地球半径(米)
+ double latDiff = Math.toRadians(posLat - gpsLat);
+ double lonDiff = Math.toRadians(posLon - gpsLon);
+
+ double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
+ + Math.cos(Math.toRadians(gpsLat)) * Math.cos(Math.toRadians(posLat))
+ * Math.sin(lonDiff / 2) * Math.sin(lonDiff / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return EARTH_RADIUS * c; // 返回距离(米)
+ }
+
+ // ---------------------- 实现 PositionAdapter.DistanceServiceInterface 接口 ----------------------
+ @Override
+ public ArrayList getPositionList() {
+ // 服务未运行返回空列表;运行中返回拷贝(避免外部修改原列表)
+ if (!isServiceRunning) {
+ LogUtils.w(TAG, "getPositionList:服务未运行,返回空列表");
+ return new ArrayList();
+ }
+ return new ArrayList(mPositionList);
+ }
+
+ @Override
+ public ArrayList getPositionTasksList() {
+ if (!isServiceRunning) {
+ LogUtils.w(TAG, "getPositionTasksList:服务未运行,返回空列表");
+ return new ArrayList();
+ }
+ return new ArrayList(mTaskList);
+ }
+
+ @Override
+ 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() + ")");
+ }
+
+ @Override
+ public void setOnDistanceUpdateReceiver(PositionAdapter.OnDistanceUpdateReceiver receiver) {
+ this.mDistanceReceiver = receiver;
+ LogUtils.d(TAG, "setOnDistanceUpdateReceiver:回调接收器已设置(" + (receiver != null ? "有效" : "无效") + ")");
+ }
+
+ @Override
+ public void addVisibleDistanceView(String positionId) {
+ if (!isServiceRunning || positionId == null) {
+ LogUtils.w(TAG, "addVisibleDistanceView:服务未运行/位置ID无效,添加失败");
+ return;
+ }
+ if (mVisiblePositionIds.add(positionId)) {
+ LogUtils.d(TAG, "addVisibleDistanceView:添加成功(位置ID=" + positionId + ",当前可见数=" + mVisiblePositionIds.size() + ")");
+ }
+ }
+
+ @Override
+ public void removeVisibleDistanceView(String positionId) {
+ if (positionId == null) {
+ LogUtils.w(TAG, "removeVisibleDistanceView:位置ID为空,移除失败");
+ return;
+ }
+ if (mVisiblePositionIds.remove(positionId)) {
+ LogUtils.d(TAG, "removeVisibleDistanceView:移除成功(位置ID=" + positionId + ",当前可见数=" + mVisiblePositionIds.size() + ")");
+ }
+ }
+
+ @Override
+ public void clearVisibleDistanceViews() {
+ mVisiblePositionIds.clear();
+ LogUtils.d(TAG, "clearVisibleDistanceViews:所有可见位置已清空");
+ }
+
+ // ---------------------- 数据管理接口(给外部/Adapter调用,安全校验) ----------------------
+ /**
+ * 获取服务运行状态(供Activity/Adapter判断是否加载数据)
+ */
+ public boolean isServiceRunning() {
+ return isServiceRunning;
+ }
+
+ /**
+ * 添加位置(Adapter新增位置时调用,自动去重)
+ */
+ public void addPosition(PositionModel position) {
+ if (!isServiceRunning || position == null || position.getPositionId() == null) {
+ LogUtils.w(TAG, "addPosition:服务未运行/数据无效,添加失败");
+ return;
+ }
+
+ // 去重校验(根据位置ID)
+ boolean isDuplicate = false;
+ Iterator posIter = mPositionList.iterator();
+ while (posIter.hasNext()) {
+ if (position.getPositionId().equals(posIter.next().getPositionId())) {
+ isDuplicate = true;
+ break;
+ }
+ }
+
+ if (!isDuplicate) {
+ mPositionList.add(position);
+ LogUtils.d(TAG, "addPosition:添加成功(位置ID=" + position.getPositionId() + ",总数=" + mPositionList.size() + ")");
+ } else {
+ LogUtils.w(TAG, "addPosition:位置ID=" + position.getPositionId() + "已存在,添加失败");
+ }
+ }
+
+ /**
+ * 删除位置(连带删除关联任务,Adapter删除时调用)
+ */
+ public void removePosition(String positionId) {
+ if (!isServiceRunning || positionId == null) {
+ LogUtils.w(TAG, "removePosition:服务未运行/位置ID无效,删除失败");
+ return;
+ }
+
+ // 1. 删除位置
+ boolean isRemoved = false;
+ Iterator posIter = mPositionList.iterator();
+ while (posIter.hasNext()) {
+ PositionModel pos = posIter.next();
+ if (positionId.equals(pos.getPositionId())) {
+ posIter.remove();
+ isRemoved = true;
+ break;
+ }
+ }
+
+ if (isRemoved) {
+ // 2. 删除关联任务
+ Iterator taskIter = mTaskList.iterator();
+ while (taskIter.hasNext()) {
+ if (positionId.equals(taskIter.next().getPositionId())) {
+ taskIter.remove();
+ }
+ }
+ // 3. 移除可见位置(避免继续计算已删除位置)
+ mVisiblePositionIds.remove(positionId);
+ LogUtils.d(TAG, "removePosition:删除成功(位置ID=" + positionId + ",剩余位置数=" + mPositionList.size() + ")");
+ } else {
+ LogUtils.w(TAG, "removePosition:位置ID=" + positionId + "不存在,删除失败");
+ }
+ }
+
+ /**
+ * 更新位置信息(Adapter编辑后调用,仅更新备注/距离开关)
+ */
+ public void updatePosition(PositionModel updatedPosition) {
+ if (!isServiceRunning || updatedPosition == null || updatedPosition.getPositionId() == null) {
+ LogUtils.w(TAG, "updatePosition:服务未运行/数据无效,更新失败");
+ return;
+ }
+
+ boolean isUpdated = false;
+ Iterator posIter = mPositionList.iterator();
+ while (posIter.hasNext()) {
+ PositionModel pos = posIter.next();
+ if (updatedPosition.getPositionId().equals(pos.getPositionId())) {
+ // 仅更新允许修改的字段(备注+距离开关)
+ pos.setMemo(updatedPosition.getMemo());
+ pos.setIsEnableRealPositionDistance(updatedPosition.isEnableRealPositionDistance());
+ // 关闭距离时重置距离值
+ if (!updatedPosition.isEnableRealPositionDistance()) {
+ pos.setRealPositionDistance(-1);
+ notifyDistanceUpdateToUI(pos.getPositionId()); // 通知UI更新状态
+ }
+ isUpdated = true;
+ break;
+ }
+ }
+
+ if (isUpdated) {
+ LogUtils.d(TAG, "updatePosition:更新成功(位置ID=" + updatedPosition.getPositionId() + ")");
+ } else {
+ LogUtils.w(TAG, "updatePosition:位置ID=" + updatedPosition.getPositionId() + "不存在,更新失败");
+ }
+ }
+
+ /**
+ * 同步任务列表(Adapter编辑任务后调用,全量覆盖+去重)
+ */
+ public void syncAllPositionTasks(ArrayList tasks) {
+ if (!isServiceRunning || tasks == null) {
+ LogUtils.w(TAG, "syncAllPositionTasks:服务未运行/任务列表为空,同步失败");
+ return;
+ }
+
+// 1. 清空旧任务(全量同步,避免增量逻辑复杂)
+ mTaskList.clear();
+// 2. 添加新任务(根据任务ID去重,避免重复)
+ Set taskIdSet = new HashSet();
+ Iterator taskIter = tasks.iterator();
+ while (taskIter.hasNext()) {
+ PositionTaskModel task = (PositionTaskModel)taskIter.next();
+ if (task != null && task.getTaskId() != null && !taskIdSet.contains(task.getTaskId())) {
+ taskIdSet.add(task.getTaskId());
+ mTaskList.add(task);
+ }
+ }
+
+ LogUtils.d(TAG, "syncAllPositionTasks:同步成功(接收任务数=" + tasks.size() + ",去重后=" + mTaskList.size() + ")");
+ }
+
+ /**
+ * 清空所有数据(服务销毁/调试时调用,重置所有状态)
+ */
+ public void clearAllData() {
+ mPositionList.clear();
+ mTaskList.clear();
+ mVisiblePositionIds.clear();
+ mCurrentGpsPosition = null;
+ LogUtils.d(TAG, "clearAllData:所有数据已清空(位置/任务/GPS/可见位置)");
+ }
+
+ /**
+ * 强制刷新距离(外部主动触发,如GPS位置突变时)
+ */
+ public void forceRefreshDistance() {
+ if (!isServiceRunning) {
+ LogUtils.w(TAG, "forceRefreshDistance:服务未运行,刷新失败");
+ return;
+ }
+ if (distanceExecutor != null && !distanceExecutor.isShutdown()) {
+// 提交即时任务(不等待定时周期)
+ distanceExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ calculateVisiblePositionDistance();
+ }
+ });
+ LogUtils.d(TAG, "forceRefreshDistance:已触发即时距离计算");
+ }
+ }
}
diff --git a/positions/src/main/res/layout/activity_main.xml b/positions/src/main/res/layout/activity_main.xml
index 09f11dcd..4336dbab 100644
--- a/positions/src/main/res/layout/activity_main.xml
+++ b/positions/src/main/res/layout/activity_main.xml
@@ -6,39 +6,38 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+
+
+
+
+
+
+
-
-
+
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:onClick="onLog"
+ android:text="查看操作日志"/>
+
diff --git a/positions/src/main/res/layout/item_position_edit.xml b/positions/src/main/res/layout/item_position_edit.xml
index 811690d3..f1d8980b 100644
--- a/positions/src/main/res/layout/item_position_edit.xml
+++ b/positions/src/main/res/layout/item_position_edit.xml
@@ -43,17 +43,17 @@
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#333333"
- android:id="@+id/tv_edit_real_distance"/>
+ android:id="@+id/tv_edit_distance"/>
+
+
+
+
+
+
+
+
diff --git a/positions/src/main/res/layout/item_position_simple.xml b/positions/src/main/res/layout/item_position_simple.xml
index db3de81f..ec4e69fa 100644
--- a/positions/src/main/res/layout/item_position_simple.xml
+++ b/positions/src/main/res/layout/item_position_simple.xml
@@ -29,7 +29,7 @@
android:layout_marginTop="4dp"/>