Compare commits

...

5 Commits

Author SHA1 Message Date
ZhanGSKen
269e7b30be Merge remote-tracking branch 'origin/positions' into appbase 2025-10-03 00:55:32 +08:00
ZhanGSKen
ca953adec2 20251002_212237_535 2025-10-02 21:22:41 +08:00
ZhanGSKen
916e807f1b 窗口基本加载服务数据 2025-10-02 16:03:50 +08:00
ZhanGSKen
1f57ac5401 20251002_141326_715 2025-10-02 14:13:31 +08:00
ZhanGSKen
1ba19d8f77 添加示例位置增加功能 2025-10-02 11:10:55 +08:00
5 changed files with 1393 additions and 1054 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Oct 02 02:36:00 GMT 2025
#Thu Oct 02 13:16:14 GMT 2025
stageCount=8
libraryProject=
baseVersion=15.0
publishVersion=15.0.7
buildCount=9
buildCount=29
baseBetaVersion=15.0.8

View File

@@ -3,379 +3,225 @@ package cc.winboll.studio.positions.activities;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/29 18:22
* @Describe 位置列表页面(直连服务数据+移除绑定,兼容服务私有字段
* @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期
*/
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.widget.Toast;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.adapters.PositionAdapter;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
import cc.winboll.studio.positions.services.MainService;
import cc.winboll.studio.positions.R;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
/**
* 核心调整说明
* 1. 移除服务绑定逻辑但不直接访问服务私有字段解决mPositionList未知问题
* 2. 通过服务提供的 PUBLIC 方法如getPositionList、addPosition操作数据符合封装规范
* 3. 保留“直连服务实例”的核心需求,避免绑定,但尊重服务数据私有性
* 4. 兼容Java 7语法无Lambda、显式类型转换
* Java 7 语法适配
* 1. 服务绑定用匿名内部类实现 ServiceConnection
* 2. Adapter 初始化传入 MainService 实例,确保数据来源唯一
* 3. 所有位置/任务操作通过 MainService 接口执行
*/
public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "LocationActivity";
// SP配置常量判断服务是否运行
private static final String SP_SERVICE_CONFIG = "service_config";
private static final String KEY_SERVICE_RUNNING = "is_service_running";
// 页面核心控件与变量
private RecyclerView mRecyclerView;
private PositionAdapter mAdapter;
// 直连服务实例(替代绑定,通过单例/全局初始化获取)
private MainService mMainService;
// 缓存服务数据从服务PUBLIC方法获取避免重复调用
private ArrayList<PositionModel> mCachedPositionList;
private ArrayList<PositionTaskModel> mCachedTaskList;
public class LocationActivity extends Activity {
private static final String TAG = "LocationActivity";
@Override
public Activity getActivity() {
return this;
}
private RecyclerView mRvPosition;
private PositionAdapter mPositionAdapter;
private ArrayList<PositionModel> mLocalPosCache; // 本地位置缓存与MainService同步
@Override
public String getTag() {
return TAG;
}
// MainService 引用+绑定状态
private MainService mMainService;
private boolean isServiceBound = false;
// 服务连接Java 7 匿名内部类实现)
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 假设 MainService 用 LocalBinder 暴露实例Java 7 强转)
MainService.LocalBinder binder = (MainService.LocalBinder) service;
mMainService = binder.getService();
isServiceBound = true;
LogUtils.d(TAG, "MainService绑定成功开始同步数据");
// 从MainService同步初始数据位置+任务)
syncDataFromMainService();
// 初始化Adapter传入MainService实例确保任务数据从服务获取
initPositionAdapter();
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.w(TAG, "MainService断开连接清空引用");
mMainService = null;
isServiceBound = false;
}
};
// ---------------------- 页面生命周期(简化资源管理,无服务绑定) ----------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);
LogUtils.d(TAG, "onCreate");
// 1. 初始化服务实例(通过单例/全局方式避免直接new导致数据重置
initServiceInstance();
// 2. 检查服务状态(未运行/未初始化则提示关闭)
checkServiceStatus();
// 3. 初始化RecyclerView布局+性能优化
initRecyclerViewConfig();
// 4. 缓存服务数据从服务PUBLIC方法获取不访问私有字段
cacheServiceData();
// 5. 初始化Adapter传缓存数据而非直接访问服务私有字段
initAdapter();
// 初始化视图+本地缓存
initView();
mLocalPosCache = new ArrayList<PositionModel>();
// 绑定MainService确保Activity启动时就拿到服务实例
bindMainService();
}
/**
* 初始化视图RecyclerView
*/
private void initView() {
mRvPosition = (RecyclerView) findViewById(R.id.rv_position_list);
// Java 7 显式设置布局管理器LinearLayoutManager
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRvPosition.setLayoutManager(layoutManager);
}
/**
* 绑定MainServiceJava 7 显式Intent
*/
private void bindMainService() {
Intent serviceIntent = new Intent(this, MainService.class);
// 绑定服务BIND_AUTO_CREATE服务不存在时自动创建
bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE);
LogUtils.d(TAG, "发起MainService绑定请求");
}
/**
* 从MainService同步数据位置+任务)
*/
private void syncDataFromMainService() {
if (!isServiceBound || mMainService == null) {
LogUtils.w(TAG, "同步数据失败MainService未绑定");
showToast("服务未就绪,无法加载数据");
return;
}
// 同步位置数据(从服务获取最新列表)
ArrayList<PositionModel> servicePosList = mMainService.getPositionList();
if (servicePosList != null && !servicePosList.isEmpty()) {
mLocalPosCache.clear();
mLocalPosCache.addAll(servicePosList);
LogUtils.d(TAG, "从MainService同步位置数据完成数量=" + mLocalPosCache.size());
}
// 同步任务数据无需本地缓存Adapter直接从服务获取
ArrayList<PositionTaskModel> serviceTaskList = mMainService.getAllTasks();
LogUtils.d(TAG, "从MainService同步任务数据完成数量=" + serviceTaskList.size());
}
/**
* 初始化PositionAdapter核心传入MainService实例
*/
private void initPositionAdapter() {
if (mMainService == null) {
LogUtils.e(TAG, "初始化Adapter失败MainService为空");
return;
}
// Java 7 显式初始化Adapter传入上下文+本地位置缓存+MainService实例
mPositionAdapter = new PositionAdapter(this, mLocalPosCache, mMainService);
// 设置Adapter回调处理位置删除/保存最终同步到MainService
mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
@Override
public void onDeleteClick(int position) {
// 删除逻辑先删本地缓存再调用MainService接口删服务数据
if (position < 0 || position >= mLocalPosCache.size()) {
LogUtils.w(TAG, "删除位置失败:无效索引=" + position);
return;
}
PositionModel deletePos = mLocalPosCache.get(position);
if (deletePos != null && !deletePos.getPositionId().isEmpty()) {
// 1. 调用MainService接口删除服务端数据
mMainService.removePosition(deletePos.getPositionId());
// 2. 删除本地缓存数据
mLocalPosCache.remove(position);
// 3. 通知Adapter刷新
mPositionAdapter.notifyItemRemoved(position);
showToast("删除位置成功:" + deletePos.getMemo());
LogUtils.d(TAG, "删除位置完成ID=" + deletePos.getPositionId() + "已同步MainService");
}
}
});
mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 保存逻辑先更本地缓存再调用MainService接口更新服务数据
if (!isServiceBound || mMainService == null) {
LogUtils.w(TAG, "保存位置失败MainService未绑定");
showToast("服务未就绪,保存失败");
return;
}
if (position < 0 || position >= mLocalPosCache.size()) {
LogUtils.w(TAG, "保存位置失败:无效索引=" + position);
return;
}
// 1. 调用MainService接口更新服务端数据
mMainService.updatePosition(updatedPos);
// 2. 更新本地缓存数据
mLocalPosCache.set(position, updatedPos);
// 3. 通知Adapter刷新可选Adapter已本地同步
mPositionAdapter.notifyItemChanged(position);
showToast("保存位置成功:" + updatedPos.getMemo());
LogUtils.d(TAG, "保存位置完成ID=" + updatedPos.getPositionId() + "已同步MainService");
}
});
// 设置Adapter到RecyclerView
mRvPosition.setAdapter(mPositionAdapter);
LogUtils.d(TAG, "PositionAdapter初始化完成已绑定MainService");
}
/**
* 显示ToastJava 7 显式Toast.makeText
*/
private void showToast(String content) {
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 1. 清理Adapter资源
if (mAdapter != null) {
mAdapter.release();
mAdapter = null;
}
// 2. 置空服务实例+缓存数据帮助GC回收
mMainService = null;
mCachedPositionList = null;
mCachedTaskList = null;
mRecyclerView = null;
LogUtils.d(TAG, "页面销毁:已清理服务实例及缓存数据");
}
// ---------------------- 核心初始化(服务实例+数据缓存+状态检查) ----------------------
/**
* 初始化服务实例(关键:通过单例获取,确保全局唯一,避免数据重复)
* 注需在DistanceRefreshService中实现单例getInstance()方法)
*/
private void initServiceInstance() {
// 步骤1先启动Service确保Service已创建实例能被初始化
// 步骤2初始化Service实例延迟100-200ms确保Service onCreate执行完成
// 或在startService后通过ServiceConnection绑定获取实例更可靠
//initServiceInstance();
// 方式通过服务PUBLIC单例方法获取实例不直接new避免私有数据初始化重复
mMainService = MainService.getInstance();
// 容错:若单例未初始化(如首次启动),提示并处理
if (mMainService == null) {
LogUtils.e(TAG, "服务实例初始化失败:单例未创建");
Toast.makeText(this, "位置服务初始化失败,请重启应用", Toast.LENGTH_SHORT).show();
finish();
// 1. 释放Adapter资源反注册服务监听避免内存泄漏
if (mPositionAdapter != null) {
mPositionAdapter.release();
}
// 2. 解绑MainService避免Activity销毁后服务仍被持有
if (isServiceBound) {
unbindService(mServiceConnection);
LogUtils.d(TAG, "MainService解绑完成");
}
}
public static class LocalBinder extends android.os.Binder {
// 持有 MainService 实例引用
private MainService mService;
/**
* 检查服务状态替代原绑定检查通过服务PUBLIC方法判断
*/
private void checkServiceStatus() {
LogUtils.d(TAG, "checkServiceStatus");
// 1. 服务实例未初始化
if (mMainService == null) {
return; // 已在initServiceInstance中处理此处避免重复finish
// 构造时传入服务实例
public LocalBinder(MainService service) {
this.mService = service;
}
// 2. 检查服务运行状态通过服务PUBLIC方法isServiceRunning()获取,不访问私有字段
if (!mMainService.isServiceRunning()) {
// 服务未运行手动触发启动调用服务PUBLIC方法run()
mMainService.run();
LogUtils.d(TAG, "服务未运行已通过PUBLIC方法触发启动");
}
// 3. 检查SP中服务配置双重校验确保一致性
if (!AppConfigsUtil.getInstance(LocationActivity.this).isEnableMainService(true)) {
Toast.makeText(this, "位置服务配置未启用,数据可能无法更新", Toast.LENGTH_SHORT).show();
// 对外提供获取服务实例的方法供Activity调用
public MainService getService() {
return mService;
}
}
/**
* 缓存服务数据从服务PUBLIC方法获取解决私有字段未知问题
*/
private void cacheServiceData() {
if (mMainService == null) {
ToastUtils.show("缓存数据失败:服务实例为空");
LogUtils.e(TAG, "缓存数据失败:服务实例为空");
mCachedPositionList = new ArrayList<PositionModel>();
mCachedTaskList = new ArrayList<PositionTaskModel>();
return;
}
// 从服务PUBLIC方法获取数据不访问mPositionList而是调用getPositionList()
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 容错若服务返回null初始化空列表避免空指针
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
ToastUtils.show("缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
LogUtils.d(TAG, "缓存服务数据成功:位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
}
/**
* 初始化RecyclerView保留原性能优化
*/
private void initRecyclerViewConfig() {
mRecyclerView = (RecyclerView) findViewById(R.id.rv_position_list);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true); // 固定大小,优化绘制
}
// ---------------------- Adapter初始化与数据交互全通过服务PUBLIC方法 ----------------------
/**
* 初始化Adapter通过缓存数据初始化数据更新时同步调用服务方法
*/
private void initAdapter() {
LogUtils.d(TAG, "initAdapter");
if (mCachedPositionList == null || mCachedTaskList == null) {
LogUtils.e(TAG, "初始化Adapter失败缓存数据为空");
return;
}
// 1. 初始化Adapter传缓存数据+服务实例Adapter数据从缓存取操作通过服务
mAdapter = new PositionAdapter(this, mCachedPositionList, mCachedTaskList);
mRecyclerView.setAdapter(mAdapter);
// 2. 删除回调通过服务PUBLIC方法removePosition()删除,不直接操作私有字段
mAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
@Override
public void onDeleteClick(int position) {
// 校验:服务实例有效+索引合法
if (mMainService == null || mCachedPositionList == null) {
Toast.makeText(LocationActivity.this, "删除失败:服务未就绪", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "删除失败:索引无效(" + position + "");
Toast.makeText(LocationActivity.this, "删除失败:数据位置异常", Toast.LENGTH_SHORT).show();
return;
}
// 1. 获取要删除的位置(从缓存取,与服务数据一致)
PositionModel targetPos = mCachedPositionList.get(position);
String targetPosId = targetPos.getPositionId();
// 2. 调用服务PUBLIC方法删除服务内部处理mPositionList/mTaskList符合封装
mMainService.removePosition(targetPosId);
LogUtils.d(TAG, "通过服务删除位置ID=" + targetPosId);
// 3. 刷新缓存+Adapter从服务重新获取数据确保与服务一致
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置已删除(含关联任务)", Toast.LENGTH_SHORT).show();
}
});
// 3. 位置保存回调通过服务PUBLIC方法updatePosition()更新
mAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 校验:服务+数据+参数有效
if (mMainService == null || mCachedPositionList == null || updatedPos == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或数据异常", Toast.LENGTH_SHORT).show();
return;
}
if (position < 0 || position >= mCachedPositionList.size()) {
LogUtils.w(TAG, "保存失败:位置索引无效");
return;
}
// 1. 调用服务PUBLIC方法更新数据服务内部修改mPositionList
mMainService.updatePosition(updatedPos);
LogUtils.d(TAG, "通过服务保存位置ID=" + updatedPos.getPositionId());
// 2. 刷新缓存+Adapter确保本地显示与服务一致
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "位置信息已保存", Toast.LENGTH_SHORT).show();
}
});
// 4. 任务保存回调通过服务PUBLIC方法syncAllPositionTasks()同步
mAdapter.setOnSavePositionTaskClickListener(new PositionAdapter.OnSavePositionTaskClickListener() {
@Override
public void onSavePositionTaskClick(PositionTaskModel newTask) {
if (mMainService == null || newTask == null) {
Toast.makeText(LocationActivity.this, "保存失败:服务或任务数据为空", Toast.LENGTH_SHORT).show();
return;
}
// 1. 构建新任务列表(缓存任务+新任务,去重)
ArrayList<PositionTaskModel> newTaskList = new ArrayList<PositionTaskModel>(mCachedTaskList);
// 先移除同ID旧任务避免重复
for (int i = 0; i < newTaskList.size(); i++) {
PositionTaskModel oldTask = newTaskList.get(i);
if (newTask.getTaskId().equals(oldTask.getTaskId())) {
newTaskList.remove(i);
break;
}
}
// 添加新任务
newTaskList.add(newTask);
// 2. 调用服务PUBLIC方法同步任务服务内部更新mTaskList
mMainService.syncAllPositionTasks(newTaskList);
LogUtils.d(TAG, "通过服务保存任务ID=" + newTask.getTaskId());
// 3. 刷新缓存+Adapter
refreshCachedDataAndAdapter();
Toast.makeText(LocationActivity.this, "任务信息已保存", Toast.LENGTH_SHORT).show();
}
});
}
// ---------------------- 页面交互新增位置、同步GPS全走服务PUBLIC方法 ----------------------
/**
* 新增位置调用服务addPosition()不直接操作mPositionList
*/
public void addNewPosition(View view) {
// 1. 隐藏软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && getCurrentFocus() != null) {
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
// 2. 校验服务状态
if (mMainService == null) {
Toast.makeText(this, "新增失败:服务未初始化", Toast.LENGTH_SHORT).show();
return;
}
// 3. 创建新位置模型
PositionModel newPos = new PositionModel();
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一IDPositionModel需实现
newPos.setLongitude(116.404267); // 示例经度(北京)
newPos.setLatitude(39.915119); // 示例纬度
newPos.setMemo("测试位置(可编辑备注)");
newPos.setIsSimpleView(true); // 默认简单视图
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算
// 4. 调用服务PUBLIC方法新增服务内部处理mPositionList去重+添加)
mMainService.addPosition(newPos);
LogUtils.d(TAG, "通过服务新增位置ID=" + newPos.getPositionId());
// 5. 刷新缓存+Adapter显示新增结果
refreshCachedDataAndAdapter();
Toast.makeText(this, "新增位置成功(已启用距离计算)", Toast.LENGTH_SHORT).show();
}
/**
* 同步GPS位置到服务调用服务syncCurrentGpsPosition(),不访问私有字段)
*/
public void syncGpsPositionToService(PositionModel gpsModel) {
if (mMainService == null || gpsModel == null) {
LogUtils.w(TAG, "同步GPS失败服务未就绪或GPS数据无效");
return;
}
// 调用服务PUBLIC方法同步GPS服务内部更新mCurrentGpsPosition
mMainService.syncCurrentGpsPosition(gpsModel);
// 强制刷新距离通过服务PUBLIC方法触发mPositionList距离计算
mMainService.forceRefreshDistance();
LogUtils.d(TAG, "GPS位置已同步通过服务触发距离计算");
// 刷新缓存+Adapter显示最新距离
refreshCachedDataAndAdapter();
}
// ---------------------- 辅助方法统一刷新缓存与Adapter确保数据一致 ----------------------
/**
* 刷新缓存数据+Adapter从服务重新获取数据避免本地缓存与服务不一致
*/
private void refreshCachedDataAndAdapter() {
if (mMainService == null) {
LogUtils.w(TAG, "刷新失败:服务实例为空");
return;
}
// 1. 从服务重新获取数据(更新缓存,不访问私有字段)
mCachedPositionList = mMainService.getPositionList();
mCachedTaskList = mMainService.getPositionTasksList();
// 容错处理
if (mCachedPositionList == null) mCachedPositionList = new ArrayList<PositionModel>();
if (mCachedTaskList == null) mCachedTaskList = new ArrayList<PositionTaskModel>();
// 2. 刷新Adapter全量刷新简单可靠若需性能优化可改为局部刷新
if (mAdapter != null) {
mAdapter.updateAllData(mCachedPositionList, mCachedTaskList);
}
LogUtils.d(TAG, "刷新完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
}
// ---------------------- 补充DistanceRefreshService单例实现需在服务中添加 ----------------------
// 注:以下代码需复制到 DistanceRefreshService 类中确保实例唯一解决直接new的问题
/*
// 服务单例实例(私有静态)
private static DistanceRefreshService sInstance;
// 单例PUBLIC方法供外部获取实例确保全局唯一
public static synchronized DistanceRefreshService getInstance() {
if (sInstance == null) {
sInstance = new DistanceRefreshService();
// 初始化服务核心逻辑如GPS管理器、线程池避免重复初始化
sInstance.mLocationManager = (LocationManager) sInstance.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
sInstance.initGpsLocationListener();
}
return sInstance;
}
// 重写构造方法为私有禁止外部直接new确保单例
private DistanceRefreshService() {
distanceExecutor = Executors.newSingleThreadScheduledExecutor();
}
*/
}

View File

@@ -5,7 +5,6 @@ package cc.winboll.studio.positions.adapters;
* @Date 2025/09/29 20:25
* @Describe 位置数据适配器完全独立无未知接口依赖仅用LocationActivity缓存数据
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -15,30 +14,36 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.text.TextUtils;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.models.PositionModel;
import cc.winboll.studio.positions.models.PositionTaskModel;
import cc.winboll.studio.positions.services.MainService;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
/**
* 核心调整
* 1. 彻底删除所有与 DistanceServiceInterface 相关的代码(解决未知类型问题)
* 2. 完全基于 LocationActivity 传入的缓存数据mCachedPositionList/mCachedTaskList实现所有功能
* 3. 移除服务回调、可见位置通知等冗余逻辑,仅保留“数据显示+用户交互→通知Activity处理”的核心流程
* 4. 确保无未定义接口、无外部服务依赖,代码可直接编译运行
* Java 7 语法适配
* 1. 移除 Lambda/方法引用,用匿名内部类替代
* 2. 集合操作使用迭代器(避免 ConcurrentModificationException
* 3. 弱引用管理 MainService避免内存泄漏
* 4. 所有任务数据从 MainService 获取,更新通过 MainService 接口
*/
public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements MainService.TaskUpdateListener {
public static final String TAG = "PositionAdapter";
// 1. 视图类型常量(简单视图/编辑视图,无魔法值
// 视图类型常量(Java 7 静态常量定义
private static final int VIEW_TYPE_SIMPLE = 0;
private static final int VIEW_TYPE_EDIT = 1;
// 2. 默认配置(文本显示/任务默认值,统一管理)
// 默认配置常量(统一管理,避免魔法值)
private static final String DEFAULT_MEMO = "无备注";
private static final String DEFAULT_TASK_DESC = "新任务";
private static final int DEFAULT_TASK_DISTANCE = 50; // 单位:米
@@ -46,81 +51,61 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final String DISTANCE_DISABLED = "实时距离:未启用";
private static final String DISTANCE_ERROR = "实时距离:计算失败";
// 3. 唯一数据源(完全依赖 LocationActivity 传入的缓存数据,无其他来源
// 核心依赖Java 7 弱引用+集合定义
private final Context mContext;
private ArrayList<PositionModel> mCachedPositionList; // 位置缓存(来自Activity
private ArrayList<PositionTaskModel> mCachedTaskList; // 任务缓存来自Activity
private final ArrayList<PositionModel> mCachedPositionList; // 位置缓存(Activity传入最终需与MainService同步
private final WeakReference<MainService> mMainServiceRef; // 弱引用MainService避免内存泄漏
private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 距离控件缓存优化UI更新
// 4. 本地辅助缓存(优化性能:避免重复遍历,不依赖外部服务
private final ConcurrentHashMap<String, ArrayList<PositionTaskModel>> mPosToTasksMap; // 位置ID→关联任务本地分组
private final ConcurrentHashMap<String, TextView> mPosDistanceViewMap; // 位置ID→距离控件更新UI用
// =========================================================================
// 与 LocationActivity 交互的回调接口(仅定义必要功能,无冗余)
// =========================================================================
// 删除位置通知Activity删除指定索引的位置数据
// 回调接口与Activity交互仅处理位置逻辑任务逻辑直接调用MainService
public interface OnDeleteClickListener {
void onDeleteClick(int position);
}
// 保存位置通知Activity保存更新后的位置模型带索引方便Activity定位修改
public interface OnSavePositionClickListener {
void onSavePositionClick(int position, PositionModel updatedPos);
}
// 保存任务通知Activity保存新增/修改的任务带任务模型Activity处理数据同步
public interface OnSavePositionTaskClickListener {
void onSavePositionTaskClick(PositionTaskModel newTask);
}
// 回调实例由LocationActivity初始化时设置解耦Activity与Adapter
private OnDeleteClickListener mOnDeleteListener;
private OnSavePositionClickListener mOnSavePosListener;
private OnSavePositionTaskClickListener mOnSaveTaskListener;
// =========================================================================
// 构造函数(仅接收上下文+Activity缓存数据无其他依赖解决接口未定义问题
// 构造函数(Java 7 风格:初始化依赖+注册任务监听
// =========================================================================
/**
* 唯一构造函数确保无未知接口依赖直接使用Activity提供的数据
* @param context 上下文(加载布局、操作软键盘、获取资源)
* @param cachedPositionList LocationActivity中的位置缓存唯一位置数据源
* @param cachedTaskList LocationActivity中的任务缓存唯一任务数据源
*/
public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, ArrayList<PositionTaskModel> cachedTaskList) {
public PositionAdapter(Context context, ArrayList<PositionModel> cachedPositionList, MainService mainService) {
this.mContext = context;
// 初始化数据源容错处理若Activity传入null初始化空列表避免空指针
// 容错处理避免传入null导致空指针
this.mCachedPositionList = (cachedPositionList != null) ? cachedPositionList : new ArrayList<PositionModel>();
this.mCachedTaskList = (cachedTaskList != null) ? cachedTaskList : new ArrayList<PositionTaskModel>();
// 初始化本地辅助缓存基于Activity传入的数据构建提升后续操作性能
this.mPosToTasksMap = new ConcurrentHashMap<String, ArrayList<PositionTaskModel>>();
// 弱引用MainService防止Adapter持有Service导致内存泄漏Java 7 弱引用语法)
this.mMainServiceRef = new WeakReference<MainService>(mainService);
// 初始化距离控件缓存(线程安全集合,适配多线程更新场景
this.mPosDistanceViewMap = new ConcurrentHashMap<String, TextView>();
// 初始化“位置→任务”映射从Activity任务缓存中分组避免每次显示都遍历全量任务
refreshPositionTaskMap();
LogUtils.d(TAG, "Adapter初始化完成位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
// 注册MainService任务监听服务任务变化时自动刷新AdapterJava 7 接口实现
if (mainService != null) {
mainService.registerTaskUpdateListener(this);
LogUtils.d(TAG, "已注册MainService任务监听确保任务数据与服务同步");
} else {
LogUtils.w(TAG, "构造函数MainService为空任务数据无法同步");
}
LogUtils.d(TAG, "Adapter初始化完成位置数量=" + mCachedPositionList.size());
}
// =========================================================================
// RecyclerView 核心方法(完全基于Activity缓存数据实现无接口调用
// RecyclerView 核心方法(Java 7 语法适配
// =========================================================================
/**
* 判断当前项的视图类型(简单/编辑从Activity缓存数据中读取状态
*/
@Override
public int getItemViewType(int position) {
// 从位置缓存获取状态,判断视图类型(简单/编辑)
PositionModel posModel = getPositionByIndex(position);
// 若位置模型为空或标记为简单视图,返回简单视图类型
return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
}
/**
* 创建视图Holder根据类型加载布局无外部依赖
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
// 根据视图类型加载对应布局Java 7 条件判断)
if (viewType == VIEW_TYPE_SIMPLE) {
View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false);
return new SimpleViewHolder(simpleView);
@@ -130,111 +115,117 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
}
/**
* 绑定视图数据核心逻辑从Activity缓存取数不调用任何外部接口
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
PositionModel posModel = getPositionByIndex(position);
if (posModel == null) {
LogUtils.w(TAG, "绑定数据失败:位置模型为空(索引=" + position + "");
LogUtils.w(TAG, "onBindViewHolder:位置模型为空(索引=" + position + ",跳过绑定");
return;
}
String posId = posModel.getPositionId();
// 根据视图类型绑定数据(简单视图/编辑视图分别处理
// 视图类型绑定数据(Java 7 类型判断
if (holder instanceof SimpleViewHolder) {
bindSimpleView((SimpleViewHolder) holder, posModel);
} else if (holder instanceof EditViewHolder) {
bindEditView((EditViewHolder) holder, posModel, position);
}
// 缓存当前位置的距离控件(后续更新距离UI时直接使用,避免重新查找
TextView distanceView = (holder instanceof SimpleViewHolder)
? ((SimpleViewHolder) holder).tvSimpleDistance
// 缓存当前位置的距离控件(后续局部更新距离时直接使用)
TextView distanceView = (holder instanceof SimpleViewHolder)
? ((SimpleViewHolder) holder).tvSimpleDistance
: ((EditViewHolder) holder).tvEditDistance;
mPosDistanceViewMap.put(posId, distanceView);
}
/**
* 视图离开屏幕(清理距离控件缓存,避免内存泄漏)
*/
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
PositionModel posModel = getPositionByIndex(holder.getAdapterPosition());
if (posModel != null) {
mPosDistanceViewMap.remove(posModel.getPositionId()); // 移除不再显示的控件引用
if (distanceView != null && !TextUtils.isEmpty(posId)) {
mPosDistanceViewMap.put(posId, distanceView);
}
}
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
// 视图离开屏幕时,移除距离控件缓存(避免内存泄漏+引用失效控件)
PositionModel posModel = getPositionByIndex(holder.getAdapterPosition());
if (posModel != null && !TextUtils.isEmpty(posModel.getPositionId())) {
mPosDistanceViewMap.remove(posModel.getPositionId());
LogUtils.d(TAG, "视图脱离屏幕移除位置ID=" + posModel.getPositionId() + "的距离控件缓存");
}
}
/**
* 获取列表项数量直接从Activity位置缓存中取无中间层
*/
@Override
public int getItemCount() {
// 直接从位置缓存获取数量(数据源唯一)
return mCachedPositionList.size();
}
// =========================================================================
// 视图绑定细节完全基于缓存数据操作后通过回调通知Activity
// 视图绑定逻辑Java 7 风格任务数据从MainService获取
// =========================================================================
/**
* 绑定简单视图(仅显示数据,点击切换到编辑视图)
* 绑定简单视图(仅显示数据,点击切换到编辑视图)
*/
private void bindSimpleView(final SimpleViewHolder holder, final PositionModel posModel) {
// 1. 显示经纬度(从Activity缓存数据中取格式化6位小数提升可读性
// 1. 显示经纬度(Java 7 String.format格式化
holder.tvSimpleLon.setText(String.format("经度:%.6f", posModel.getLongitude()));
holder.tvSimpleLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
// 2. 显示备注(无备注时显示默认文本,避免空白)
// 2. 显示备注(无备注时显示默认文本)
String memo = posModel.getMemo();
holder.tvSimpleMemo.setText("备注:" + (memo != null && !memo.isEmpty() ? memo : DEFAULT_MEMO));
// 3. 显示实时距离从缓存数据的distance字段取数按状态显示不同内容
holder.tvSimpleMemo.setText("备注:" + (TextUtils.isEmpty(memo) ? DEFAULT_MEMO : memo));
// 3. 显示实时距离(从位置模型取数,调用工具方法更新显示)
updateDistanceDisplay(holder.tvSimpleDistance, posModel);
// 4. 点击视图→切换到编辑模式修改缓存数据中的状态通知RecyclerView刷新
// 4. 点击切换到编辑视图Java 7 匿名内部类实现点击事件)
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
posModel.setIsSimpleView(false); // 修改缓存数据中的视图状态
notifyItemChanged(getPositionIndexById(posModel.getPositionId())); // 刷新对应项
posModel.setIsSimpleView(false); // 修改位置缓存状态
// 通知RecyclerView刷新当前项精准更新避免全量刷新
notifyItemChanged(getPositionIndexById(posModel.getPositionId()));
LogUtils.d(TAG, "简单视图点击位置ID=" + posModel.getPositionId() + ",切换到编辑视图");
}
});
}
/**
* 绑定编辑视图(支持修改备注、开关距离、删除/保存位置、新增任务)
* 绑定编辑视图(支持修改备注、开关距离、删除/保存位置、新增任务)
*/
private void bindEditView(final EditViewHolder holder, final PositionModel posModel, final int position) {
final String posId = posModel.getPositionId();
// 1. 显示经纬度(不可编辑,仅展示,从缓存数据取
// 1. 显示经纬度(不可编辑,仅展示)
holder.tvEditLon.setText(String.format("经度:%.6f", posModel.getLongitude()));
holder.tvEditLat.setText(String.format("纬度:%.6f", posModel.getLatitude()));
// 2. 显示备注(编辑框赋值,光标定位到末尾,方便用户直接编辑)
// 2. 显示备注(编辑框赋值,光标定位到末尾)
String memo = posModel.getMemo();
if (memo != null && !memo.isEmpty()) {
if (!TextUtils.isEmpty(memo)) {
holder.etEditMemo.setText(memo);
holder.etEditMemo.setSelection(memo.length());
holder.etEditMemo.setSelection(memo.length()); // 光标定位到文本末尾
} else {
holder.etEditMemo.setText(""); // 无备注时清空编辑框
}
// 3. 显示实时距离(与简单视图逻辑一致,从缓存数据取数)
updateDistanceDisplay(holder.tvEditDistance, posModel);
// 4. 设置距离开关状态匹配缓存数据中的启用状态确保UI与数据一致
holder.rgDistanceSwitch.check(posModel.isEnableRealPositionDistance()
? R.id.rb_distance_enable : R.id.rb_distance_disable);
// 5. 取消编辑→切换回简单视图修改缓存状态刷新UI隐藏软键盘
// 3. 显示实时距离(与简单视图逻辑一致
updateDistanceDisplay(holder.tvEditDistance, posModel);
// 4. 设置距离开关状态(匹配位置缓存中的启用状态)
holder.rgDistanceSwitch.check(posModel.isEnableRealPositionDistance()
? R.id.rb_distance_enable
: R.id.rb_distance_disable);
// 5. 取消编辑切换回简单视图Java 7 匿名内部类)
holder.btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
posModel.setIsSimpleView(true);
notifyItemChanged(position);
hideSoftKeyboard(v);
hideSoftKeyboard(v); // 隐藏软键盘(提升用户体验)
LogUtils.d(TAG, "取消编辑位置ID=" + posId + ",切换回简单视图");
}
});
// 6. 删除位置回调Activity处理Adapter不直接删数据由Activity操作缓存+服务
// 6. 删除位置回调Activity处理Adapter不直接删数据由Activity同步MainService
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -242,10 +233,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
mOnDeleteListener.onDeleteClick(position); // 通知Activity删除指定索引
}
hideSoftKeyboard(v);
LogUtils.d(TAG, "触发删除通知Activity处理位置ID=" + posId + "的删除逻辑");
}
});
// 7. 保存位置回调Activity保存收集编辑后的参数,传更新后的模型
// 7. 保存位置回调Activity保存收集参数→构建更新模型→通知Activity
holder.btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -262,298 +254,303 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
updatedPos.setIsEnableRealPositionDistance(isDistanceEnable); // 更新距离状态
updatedPos.setIsSimpleView(true); // 切换回简单视图
// 回调Activity保存由Activity同步缓存数据+服务数据Adapter不处理逻辑
// 回调Activity保存由Activity同步MainService+位置缓存Adapter不处理逻辑
if (mOnSavePosListener != null) {
mOnSavePosListener.onSavePositionClick(position, updatedPos);
}
// 本地同步状态(避免刷新延迟,直接修改Activity传入的缓存数据
// 本地同步状态(避免刷新延迟,直接修改位置缓存
posModel.setMemo(newMemo);
posModel.setIsEnableRealPositionDistance(isDistanceEnable);
posModel.setIsSimpleView(true);
notifyItemChanged(position); // 刷新当前项,显示更新后的状态
hideSoftKeyboard(v);
LogUtils.d(TAG, "触发位置保存ID=" + posId + ",新备注=" + newMemo);
LogUtils.d(TAG, "触发保存:位置ID=" + posId + ",新备注=" + newMemo + ",距离启用=" + isDistanceEnable);
}
});
// 8. 绑定任务相关视图(显示任务数量+新增任务,基于Activity任务缓存
// 8. 绑定任务视图(显示任务数量+新增任务,数据从MainService获取
bindTaskView(holder, posId);
}
/**
* 绑定任务视图(编辑模式专属:显示当前位置的任务数量+新增任务)
* 绑定任务视图(编辑模式专属:从MainService获取任务数据,新增任务调用服务接口
*/
private void bindTaskView(final EditViewHolder holder, final String posId) {
// 从本地映射获取当前位置的任务基于Activity缓存数据构建避免重复遍历
ArrayList<PositionTaskModel> posTasks = mPosToTasksMap.get(posId);
if (posTasks == null) {
posTasks = new ArrayList<PositionTaskModel>();
// 1. 从MainService获取当前位置的任务数量Java 7 迭代器遍历服务数据
int taskCount = 0;
MainService mainService = mMainServiceRef.get();
if (mainService != null) {
ArrayList<PositionTaskModel> posTasks = mainService.getTasksByPositionId(posId);
taskCount = (posTasks != null) ? posTasks.size() : 0;
}
// 显示任务数量(简化设计,实际可扩展为任务列表)
holder.tvTaskCount.setText("任务数量:" + taskCount);
// 显示当前位置的任务数量简化设计实际项目可替换为任务列表RecyclerView
holder.tvTaskCount.setText("任务数量:" + posTasks.size());
// 新增任务→回调Activity处理Adapter不直接操作任务数据由Activity同步
// 2. 新增任务调用MainService接口不操作本地缓存数据直接写入服务
holder.btnAddTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建默认任务模型生成唯一ID关联当前位置
MainService mainService = mMainServiceRef.get();
if (mainService == null) {
LogUtils.e(TAG, "新增任务失败MainService已回收弱引用失效");
return;
}
// 构建默认任务模型Java 7 显式初始化)
PositionTaskModel newTask = new PositionTaskModel();
newTask.setTaskId(PositionTaskModel.genTaskId()); // 需在PositionTaskModel实现静态生成ID方法
newTask.setPositionId(posId); // 关联当前位置ID(确保任务归属正确)
newTask.setTaskId(PositionTaskModel.genTaskId()); // 生成唯一任务ID需在PositionTaskModel实现静态方法
newTask.setPositionId(posId); // 绑定当前位置ID
newTask.setTaskDescription(DEFAULT_TASK_DESC); // 默认任务描述
newTask.setIsEnable(true); // 默认启用任务
newTask.setDiscussDistance(DEFAULT_TASK_DISTANCE);// 默认任务距离50米
// 回调Activity保存任务由Activity添加到缓存+同步服务Adapter仅通知
if (mOnSaveTaskListener != null) {
mOnSaveTaskListener.onSavePositionTaskClick(newTask);
}
// 本地同步任务数量(避免刷新延迟,更新本地映射+UI
ArrayList<PositionTaskModel> updatedTasks = mPosToTasksMap.get(posId);
if (updatedTasks == null) {
updatedTasks = new ArrayList<PositionTaskModel>();
}
updatedTasks.add(newTask);
mPosToTasksMap.put(posId, updatedTasks);
holder.tvTaskCount.setText("任务数量:" + updatedTasks.size()); // 实时更新显示
LogUtils.d(TAG, "触发任务新增位置ID=" + posId + "任务ID=" + newTask.getTaskId());
// 调用MainService接口新增任务数据写入服务由服务处理持久化+通知刷新
mainService.addPositionTask(newTask);
hideSoftKeyboard(v);
LogUtils.d(TAG, "触发新增任务调用MainService接口位置ID=" + posId + "任务ID=" + newTask.getTaskId());
}
});
}
// =========================================================================
// 核心工具方法(无接口依赖,基于缓存数据实现,确保功能完整
// 工具方法(Java 7 风格无Lambda纯匿名内部类+迭代器
// =========================================================================
/**
* 更新距离显示(根据位置模型状态,显示不同文本颜色,无外部依赖
* 更新距离显示(根据位置模型状态,显示不同文本+颜色)
*/
private void updateDistanceDisplay(TextView distanceView, PositionModel posModel) {
if (distanceView == null || posModel == null) return;
if (distanceView == null || posModel == null) {
LogUtils.w(TAG, "updateDistanceDisplay参数为空控件/位置模型)");
return;
}
// 场景1距离计算未启用(从缓存数据状态判断)
// 场景1距离未启用
if (!posModel.isEnableRealPositionDistance()) {
distanceView.setText(DISTANCE_DISABLED);
distanceView.setTextColor(mContext.getResources().getColor(R.color.gray));
return;
}
// 场景2距离计算失败用-1标记失败从缓存数据取distance字段
// 场景2距离计算失败用-1标记失败状态
double distance = posModel.getRealPositionDistance();
if (distance < 0) {
distanceView.setText(DISTANCE_ERROR);
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
return;
}
distanceView.setText(DISTANCE_ERROR);
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
return;
}
// 场景3正常显示距离按距离范围设置颜色提升视觉区分度
distanceView.setText(String.format(DISTANCE_FORMAT, distance));
if (distance <= 100) { // 近距离≤100米绿色
distanceView.setTextColor(mContext.getResources().getColor(R.color.green));
} else if (distance <= 500) { // 中距离≤500米黄色
distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));
} else { // 远距离(>500米红色
distanceView.setTextColor(mContext.getResources().getColor(R.color.red));
}
}
// 场景3正常显示距离按距离范围设置颜色提升视觉区分度
distanceView.setText(String.format(DISTANCE_FORMAT, distance));
if (distance <= 100) {
distanceView.setTextColor(mContext.getResources().getColor(R.color.green)); // 近距离≤100米
} else if (distance <= 500) {
distanceView.setTextColor(mContext.getResources().getColor(R.color.yellow));// 中距离≤500米
} else {
distanceView.setTextColor(mContext.getResources().getColor(R.color.red)); // 远距离(>500米
}
}
/**
/**
* 根据索引获取位置模型(从位置缓存取数,容错处理)
*/
private PositionModel getPositionByIndex(int index) {
if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) {
LogUtils.w(TAG, "getPositionByIndex无效索引" + index + ")或位置缓存为空");
return null;
}
return mCachedPositionList.get(index);
}
- 根据索引获取位置模型从Activity缓存数据直接读取无中间层避免数据不一致
*/
private PositionModel getPositionByIndex(int index) {
if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) {
LogUtils.w(TAG, "获取位置失败:索引无效(" + index + ")或缓存数据为空");
return null;
}
return mCachedPositionList.get(index);
}
/**
* 根据位置ID获取列表索引用于精准刷新视图
*/
private int getPositionIndexById(String positionId) {
if (TextUtils.isEmpty(positionId) || mCachedPositionList == null || mCachedPositionList.isEmpty()) {
LogUtils.w(TAG, "getPositionIndexById参数无效位置ID/缓存为空)");
return -1;
}
/**
// Java 7 增强for循环遍历替代Lambda适配Java 7语法
for (int i = 0; i < mCachedPositionList.size(); i++) {
PositionModel pos = mCachedPositionList.get(i);
if (positionId.equals(pos.getPositionId())) {
return i; // 找到匹配ID返回索引
}
}
LogUtils.w(TAG, "getPositionIndexById未找到位置ID=" + positionId);
return -1;
}
- 根据位置ID获取列表索引从Activity缓存数据遍历用于视图切换后精准刷新
*/
private int getPositionIndexById(String positionId) {
if (mCachedPositionList == null || positionId == null || positionId.isEmpty()) {
LogUtils.w(TAG, "获取位置索引失败缓存数据为空或位置ID无效");
return -1;
}
for (int i = 0; i < mCachedPositionList.size(); i++) {
PositionModel pos = mCachedPositionList.get(i);
if (positionId.equals(pos.getPositionId())) {
return i; // 找到匹配ID返回对应索引
}
}
LogUtils.w(TAG, "未找到位置ID" + positionId + ",返回无效索引-1");
return -1;
}
/**
* 局部更新距离UI仅更新指定位置的距离避免全量刷新卡顿
*/
public void updateSinglePositionDistance(String positionId) {
// 校验参数位置ID无效或控件未缓存直接返回
if (TextUtils.isEmpty(positionId) || !mPosDistanceViewMap.containsKey(positionId)) {
LogUtils.w(TAG, "updateSinglePositionDistance位置ID无效或控件未缓存ID=" + positionId + "");
return;
}
/**
// 从MainService获取最新位置模型确保距离值是服务端最新
PositionModel latestPos = null;
MainService mainService = mMainServiceRef.get();
if (mainService != null) {
ArrayList<PositionModel> servicePosList = mainService.getPositionList();
if (servicePosList != null && !servicePosList.isEmpty()) {
// Java 7 迭代器遍历服务端位置列表,找到目标位置
Iterator<PositionModel> posIter = servicePosList.iterator();
while (posIter.hasNext()) {
PositionModel pos = posIter.next();
if (positionId.equals(pos.getPositionId())) {
latestPos = pos;
break;
}
}
}
}
- 刷新“位置→任务”映射基于Activity最新任务缓存重建确保本地映射与Activity数据一致
- Activity任务数据更新后调用避免本地映射滞后
*/
public void refreshPositionTaskMap() {
if (mCachedTaskList == null) {
mPosToTasksMap.clear(); // 任务缓存为空时,清空本地映射
LogUtils.d(TAG, "刷新任务映射Activity任务缓存为空已清空本地映射");
return;
}try {
mPosToTasksMap.clear(); // 先清空旧映射,避免数据残留
Iterator taskIter = mCachedTaskList.iterator();
while (taskIter.hasNext()) {
PositionTaskModel task = (PositionTaskModel)taskIter.next();
String posId = task.getPositionId();
// 按位置ID分组同一位置的任务放入同一个列表
if (!mPosToTasksMap.containsKey(posId)) {
mPosToTasksMap.put(posId, new ArrayList());
}
mPosToTasksMap.get(posId).add(task);
}
LogUtils.d(TAG, "刷新任务映射完成:已分组" + mPosToTasksMap.size() + "个位置的任务");
} catch (ConcurrentModificationException e) {
LogUtils.d(TAG, "刷新任务映射失败并发修改Activity任务缓存。" + e);
}
}
// 用服务端最新距离更新UI直接操作缓存的距离控件无需刷新整个项
if (latestPos != null) {
TextView distanceView = mPosDistanceViewMap.get(positionId);
updateDistanceDisplay(distanceView, latestPos);
LogUtils.d(TAG, "局部更新距离完成位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "");
} else {
LogUtils.w(TAG, "局部更新距离失败未在MainService找到位置ID=" + positionId);
}
}
/**
/**
* 全量更新位置数据从MainService同步最新位置列表刷新UI
*/
public void updateAllPositionData(ArrayList<PositionModel> newPosList) {
if (newPosList == null) {
LogUtils.w(TAG, "updateAllPositionData新位置列表为空跳过更新");
return;
}
- 全量更新数据接收Activity最新缓存数据同步本地数据源+辅助缓存刷新UI
- Activity新增/删除位置/任务后调用确保Adapter与Activity数据100%一致)
*/
public void updateAllData(ArrayList newPosList, ArrayList newTaskList) {
// 同步Activity最新缓存数据容错处理避免接收null导致空指针
this.mCachedPositionList = (newPosList != null) ? newPosList : new ArrayList();
this.mCachedTaskList = (newTaskList != null) ? newTaskList : new ArrayList();// 刷新本地辅助缓存(确保映射与最新数据同步)
refreshPositionTaskMap();
mPosDistanceViewMap.clear(); // 清空旧距离控件缓存,避免引用失效控件// 通知RecyclerView全量刷新数据已同步更新UI显示
notifyDataSetChanged();
LogUtils.d(TAG, "全量更新数据完成:当前位置数=" + mCachedPositionList.size() + ",任务数=" + mCachedTaskList.size());
}
// 同步服务端最新位置数据到本地缓存
this.mCachedPositionList.clear();
this.mCachedPositionList.addAll(newPosList);
// 清空旧距离控件缓存(避免引用失效控件)
mPosDistanceViewMap.clear();
// 通知RecyclerView全量刷新UI
notifyDataSetChanged();
LogUtils.d(TAG, "全量更新位置数据完成:当前位置数量=" + mCachedPositionList.size() + "数据来源MainService");
}
/**
/**
* 隐藏软键盘(编辑完成后调用,提升用户体验)
*/
private void hideSoftKeyboard(View view) {
if (mContext == null || view == null) {
LogUtils.w(TAG, "hideSoftKeyboard参数为空上下文/视图),无法隐藏键盘");
return;
}
- 局部更新距离UI仅更新指定位置的距离显示避免全量刷新卡顿
- Activity同步服务计算的最新距离后调用精准更新受影响的位置
*/
public void updateSinglePositionDistance(String positionId) {
// 校验参数位置ID无效或控件未缓存时直接返回
if (positionId == null || positionId.isEmpty() || !mPosDistanceViewMap.containsKey(positionId)) {
LogUtils.w(TAG, "局部更新距离失败位置ID无效或距离控件未缓存ID=" + positionId + "");
return;
}// 从Activity缓存数据中获取最新位置模型确保距离值是最新的
PositionModel latestPos = null;
for (PositionModel pos : mCachedPositionList) {
if (positionId.equals(pos.getPositionId())) {
latestPos = pos;
break;
}
}// 用最新距离值更新UI直接操作缓存的距离控件无需刷新整个项
if (latestPos != null) {
updateDistanceDisplay(mPosDistanceViewMap.get(positionId), latestPos);
LogUtils.d(TAG, "局部更新距离完成位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "");
} else {
LogUtils.w(TAG, "局部更新距离失败未在Activity缓存中找到位置ID=" + positionId);
}
}
// Java 7 显式获取输入法服务避免Lambda
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘
}
}
/**
// =========================================================================
// 实现 MainService.TaskUpdateListener 接口(服务任务变化时回调)
// =========================================================================
@Override
public void onTaskUpdated() {
LogUtils.d(TAG, "收到MainService任务更新通知任务新增/删除/状态变化刷新UI");
// 任务数据变化时全量刷新Adapter确保任务数量等显示同步
notifyDataSetChanged();
}
- 隐藏软键盘(编辑完成后调用,提升用户体验,避免键盘遮挡)
*/
private void hideSoftKeyboard(View view) {
if (mContext == null || view == null) return;
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘
}
}
// =========================================================================
// 回调设置方法供LocationActivity调用绑定交互逻辑
// =========================================================================
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
this.mOnDeleteListener = listener;
}
/**
public void setOnSavePositionClickListener(OnSavePositionClickListener listener) {
this.mOnSavePosListener = listener;
}
- 释放资源Activity销毁时调用彻底避免内存泄漏
*/
public void release() {
// 清空本地辅助缓存(解除控件、数据映射引用)
mPosToTasksMap.clear();
mPosDistanceViewMap.clear();
// 置空回调实例避免持有Activity引用导致内存泄漏
mOnDeleteListener = null;
mOnSavePosListener = null;
mOnSaveTaskListener = null;
// 置空数据源引用帮助GC回收避免残留数据占用内存
mCachedPositionList = null;
mCachedTaskList = null;
LogUtils.d(TAG, "Adapter资源已完全释放");
}
// =========================================================================
// 资源释放Activity销毁时调用避免内存泄漏
// =========================================================================
public void release() {
// 1. 反注册MainService任务监听解除与服务的绑定避免内存泄漏
MainService mainService = mMainServiceRef.get();
if (mainService != null) {
mainService.unregisterTaskUpdateListener(this);
LogUtils.d(TAG, "已反注册MainService任务监听避免内存泄漏");
}
// =========================================================================
// 回调设置方法LocationActivity调用绑定交互逻辑无冗余参数
// =========================================================================
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
this.mOnDeleteListener = listener;
}
// 2. 清空本地缓存(解除控件/数据引用帮助GC回收
mPosDistanceViewMap.clear();
if (mCachedPositionList != null) {
mCachedPositionList.clear();
}
public void setOnSavePositionClickListener(OnSavePositionClickListener listener) {
this.mOnSavePosListener = listener;
}
// 3. 置空回调实例避免持有Activity引用导致内存泄漏
mOnDeleteListener = null;
mOnSavePosListener = null;
public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) {
this.mOnSaveTaskListener = listener;
}
LogUtils.d(TAG, "Adapter资源已完全释放缓存清空+监听反注册)");
}
// =========================================================================
// 视图Holder类静态内部类,不持有外部引用,彻底避免内存泄漏)
// =========================================================================
/**
// =========================================================================
// 静态内部类视图HolderJava 7 静态内部类,不持有外部引用,避免内存泄漏)
// =========================================================================
/**
* 简单视图Holder仅显示数据对应布局item_position_simple.xml
*/
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
TextView tvSimpleLon; // 经度显示控件
TextView tvSimpleLat; // 纬度显示控件
TextView tvSimpleMemo; // 备注显示控件
TextView tvSimpleDistance;// 实时距离显示控件
- 简单视图Holder仅显示数据对应布局item_position_simple.xml
*/
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
TextView tvSimpleLon; // 经度显示控件
TextView tvSimpleLat; // 纬度显示控件
TextView tvSimpleMemo; // 备注显示控件
TextView tvSimpleDistance;// 实时距离显示控件
public SimpleViewHolder(View itemView) {
super(itemView);
// 绑定布局控件与XML中ID严格对应避免运行时空指针
tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude);
tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude);
tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo);
tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance);
}
}
public SimpleViewHolder(View itemView) {
super(itemView);
// 绑定布局控件与XML中ID严格对应避免运行时空指针
tvSimpleLon = (TextView) itemView.findViewById(R.id.tv_simple_longitude);
tvSimpleLat = (TextView) itemView.findViewById(R.id.tv_simple_latitude);
tvSimpleMemo = (TextView) itemView.findViewById(R.id.tv_simple_memo);
tvSimpleDistance = (TextView) itemView.findViewById(R.id.tv_simple_distance);
}
}
/**
/**
* 编辑视图Holder含编辑控件+功能按钮对应布局item_position_edit.xml
*/
public static class EditViewHolder extends RecyclerView.ViewHolder {
TextView tvEditLon; // 经度显示控件(不可编辑)
TextView tvEditLat; // 纬度显示控件(不可编辑)
EditText etEditMemo; // 备注编辑控件
TextView tvEditDistance; // 实时距离显示控件
RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组
Button btnCancel; // 取消编辑按钮
Button btnDelete; // 删除位置按钮
Button btnSave; // 保存位置按钮
Button btnAddTask; // 新增任务按钮
TextView tvTaskCount; // 任务数量显示控件
- 编辑视图Holder含编辑控件+功能按钮对应布局item_position_edit.xml
*/
public static class EditViewHolder extends RecyclerView.ViewHolder {
TextView tvEditLon; // 经度显示控件(不可编辑)
TextView tvEditLat; // 纬度显示控件(不可编辑)
EditText etEditMemo; // 备注编辑控件
TextView tvEditDistance; // 实时距离显示控件
RadioGroup rgDistanceSwitch; // 距离启用/禁用开关组
Button btnCancel; // 取消编辑按钮
Button btnDelete; // 删除位置按钮
Button btnSave; // 保存位置按钮
Button btnAddTask; // 新增任务按钮
TextView tvTaskCount; // 任务数量显示控件(简化设计)
public EditViewHolder(View itemView) {
super(itemView);
// 绑定布局控件与XML中ID严格对应避免运行时空指针
tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude);
tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude);
etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo);
tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance);
rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch);
btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel);
btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete);
btnSave = (Button) itemView.findViewById(R.id.btn_edit_save);
btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task);
tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count);
}
}
public EditViewHolder(View itemView) {
super(itemView);
// 绑定布局控件与XML中ID严格对应避免运行时空指针
tvEditLon = (TextView) itemView.findViewById(R.id.tv_edit_longitude);
tvEditLat = (TextView) itemView.findViewById(R.id.tv_edit_latitude);
etEditMemo = (EditText) itemView.findViewById(R.id.et_edit_memo);
tvEditDistance = (TextView) itemView.findViewById(R.id.tv_edit_distance);
rgDistanceSwitch = (RadioGroup) itemView.findViewById(R.id.rg_distance_switch);
btnCancel = (Button) itemView.findViewById(R.id.btn_edit_cancel);
btnDelete = (Button) itemView.findViewById(R.id.btn_edit_delete);
btnSave = (Button) itemView.findViewById(R.id.btn_edit_save);
btnAddTask = (Button) itemView.findViewById(R.id.btn_add_task);
tvTaskCount = (TextView) itemView.findViewById(R.id.tv_task_count);
}
}
}

View File

@@ -1,71 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<!-- 1. 经纬度显示区域(保持居中,上方) -->
<LinearLayout
android:id="@+id/layout_location_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<LinearLayout
android:id="@+id/layout_location_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<!-- 标题 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="实时位置信息"
android:textSize="22sp"
android:textStyle="bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="实时位置信息"
android:textSize="22sp"
android:textStyle="bold"/>
<!-- 经度显示 -->
<TextView
android:id="@+id/tv_longitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前经度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="15dp"/>
<TextView
android:id="@+id/tv_longitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前经度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="15dp"/>
<!-- 纬度显示 -->
<TextView
android:id="@+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前纬度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前纬度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="10dp"/>
</LinearLayout>
</LinearLayout>
<!-- 2. 新增位置列表RecyclerView- 位于经纬度下方,悬浮按钮上方 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_position_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/layout_location_info"
android:layout_above="@id/fab_p_button"
android:layout_marginTop="20dp"
android:paddingBottom="10dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_position_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/layout_location_info"
android:layout_above="@id/fab_p_button"
android:layout_marginTop="20dp"
android:paddingBottom="10dp"/>
<!-- 3. 右下角圆形悬浮按钮(不变) -->
<Button
android:id="@+id/fab_p_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="@drawable/circle_button_bg"
android:text="P"
android:textColor="@android:color/white"
android:textSize="24sp"
android:elevation="6dp"
android:padding="0dp"/>
<Button
android:id="@+id/fab_p_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="@drawable/circle_button_bg"
android:text="P"
android:textColor="@android:color/white"
android:textSize="24sp"
android:elevation="6dp"
android:padding="0dp"
android:onClick="addNewPosition"/>
</RelativeLayout>