From cf6209e2b4ba0e345bf7d1c481edcffd2a738265 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 1 Oct 2025 05:20:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=9C=8D=E5=8A=A1=E7=89=88?= =?UTF-8?q?=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- positions/build.properties | 4 +- positions/src/main/AndroidManifest.xml | 3 + .../studio/positions/MainActivity.java | 174 +- .../activities/LocationActivity.java | 564 +++---- .../positions/adapters/PositionAdapter.java | 1469 +++++++---------- .../positions/models/AppConfigsModel.java | 71 + .../services/DistanceRefreshService.java | 672 +++++--- .../src/main/res/layout/activity_main.xml | 57 +- .../main/res/layout/item_position_edit.xml | 18 +- .../main/res/layout/item_position_empty.xml | 16 + .../main/res/layout/item_position_simple.xml | 2 +- 11 files changed, 1491 insertions(+), 1559 deletions(-) create mode 100644 positions/src/main/java/cc/winboll/studio/positions/models/AppConfigsModel.java create mode 100644 positions/src/main/res/layout/item_position_empty.xml diff --git a/positions/build.properties b/positions/build.properties index 4d4420c..8e7279f 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 1b9f180..f305454 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 8d489c2..58694ed 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 74cb561..54d0154 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 ddfbf37..49fd787 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 0000000..1b53b61 --- /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 b20d1f5..6453085 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 09f11dc..4336dba 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"> - + + + + + + +