Compare commits
	
		
			16 Commits
		
	
	
		
			positions-
			...
			269e7b30be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					269e7b30be | ||
| 
						 | 
					ca953adec2 | ||
| 
						 | 
					916e807f1b | ||
| 
						 | 
					1f57ac5401 | ||
| 
						 | 
					1ba19d8f77 | ||
| 
						 | 
					435f7ced79 | ||
| 
						 | 
					bd3270f8dd | ||
| 
						 | 
					5489977a1a | ||
| 0bb909427c | |||
| 
						 | 
					d4f054a86b | ||
| caef679d9a | |||
| 
						 | 
					5194d7a188 | ||
| 
						 | 
					0ede961391 | ||
| e9bb789daa | |||
| dbff19e7f4 | |||
| 
						 | 
					44679d0c8a | 
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Fri Sep 26 05:36:14 HKT 2025
 | 
			
		||||
stageCount=9
 | 
			
		||||
#Sat Sep 27 21:03:20 HKT 2025
 | 
			
		||||
stageCount=10
 | 
			
		||||
libraryProject=libappbase
 | 
			
		||||
baseVersion=15.10
 | 
			
		||||
publishVersion=15.10.8
 | 
			
		||||
publishVersion=15.10.9
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.10.9
 | 
			
		||||
baseBetaVersion=15.10.10
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Fri Sep 26 05:36:14 HKT 2025
 | 
			
		||||
stageCount=9
 | 
			
		||||
#Sat Sep 27 21:03:08 HKT 2025
 | 
			
		||||
stageCount=10
 | 
			
		||||
libraryProject=libappbase
 | 
			
		||||
baseVersion=15.10
 | 
			
		||||
publishVersion=15.10.8
 | 
			
		||||
publishVersion=15.10.9
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.10.9
 | 
			
		||||
baseBetaVersion=15.10.10
 | 
			
		||||
 
 | 
			
		||||
@@ -22,12 +22,12 @@ public class GlobalApplication extends Application {
 | 
			
		||||
        GlobalApplication.isDebuging = isDebuging;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static void saveDebugStatus(GlobalApplication application) {
 | 
			
		||||
        APPModel.saveBeanToFile(application.getAPPModelFilePath(application), new APPModel(GlobalApplication.isDebuging));
 | 
			
		||||
    public static void saveDebugStatus(Context context) {
 | 
			
		||||
        APPModel.saveBeanToFile(getAPPModelFilePath(context), new APPModel(GlobalApplication.isDebuging));
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
    static String getAPPModelFilePath(GlobalApplication application) {
 | 
			
		||||
        return application.getDataDir().getPath() + "/APPModel.json";
 | 
			
		||||
    static String getAPPModelFilePath(Context context) {
 | 
			
		||||
        return context.getDataDir().getPath() + "/APPModel.json";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isDebuging() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Wed Oct 01 21:03:35 HKT 2025
 | 
			
		||||
stageCount=6
 | 
			
		||||
#Thu Oct 02 13:16:14 GMT 2025
 | 
			
		||||
stageCount=8
 | 
			
		||||
libraryProject=
 | 
			
		||||
baseVersion=15.0
 | 
			
		||||
publishVersion=15.0.5
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.0.6
 | 
			
		||||
publishVersion=15.0.7
 | 
			
		||||
buildCount=29
 | 
			
		||||
baseBetaVersion=15.0.8
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="app_name">@笨笨龙@</string>
 | 
			
		||||
    <string name="app_name">寻龙记#</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package cc.winboll.studio.positions;
 | 
			
		||||
 | 
			
		||||
import android.Manifest;
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
@@ -9,7 +10,10 @@ import android.view.View;
 | 
			
		||||
import android.widget.Button;
 | 
			
		||||
import android.widget.CompoundButton;
 | 
			
		||||
import android.widget.Switch;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.appcompat.widget.Toolbar;
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
 | 
			
		||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
@@ -17,7 +21,6 @@ import cc.winboll.studio.positions.activities.LocationActivity;
 | 
			
		||||
import cc.winboll.studio.positions.activities.WinBoLLActivity;
 | 
			
		||||
import cc.winboll.studio.positions.services.MainService;
 | 
			
		||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
 | 
			
		||||
import com.hjq.toast.ToastUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 主页面:仅负责
 | 
			
		||||
@@ -27,8 +30,9 @@ import com.hjq.toast.ToastUtils;
 | 
			
		||||
 */
 | 
			
		||||
public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
 | 
			
		||||
    public static final String TAG = "MainActivity";
 | 
			
		||||
    // 位置权限请求码(自定义唯一标识)
 | 
			
		||||
    // 权限请求码(建议定义为类常量,避免魔法值)
 | 
			
		||||
	private static final int REQUEST_LOCATION_PERMISSIONS = 1001;
 | 
			
		||||
	private static final int REQUEST_BACKGROUND_LOCATION_PERMISSION = 1002;
 | 
			
		||||
	
 | 
			
		||||
    // UI 控件:服务控制开关、顶部工具栏
 | 
			
		||||
    private Switch mServiceSwitch;
 | 
			
		||||
@@ -110,7 +114,7 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
 | 
			
		||||
        setSupportActionBar(mToolbar);
 | 
			
		||||
        // 给ActionBar设置标题(先判断非空,避免空指针异常)
 | 
			
		||||
        if (getSupportActionBar() != null) {
 | 
			
		||||
            getSupportActionBar().setTitle("位置管理");
 | 
			
		||||
            getSupportActionBar().setTitle(getString(R.string.app_name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -206,49 +210,57 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
 | 
			
		||||
        return hasForegroundPerm && hasBackgroundPerm;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 申请「前台+后台」位置权限(弹出系统权限弹窗,适配版本差异)
 | 
			
		||||
     */
 | 
			
		||||
	private void requestLocationPermissions() {
 | 
			
		||||
        String[] permissions;
 | 
			
		||||
        // Java7 显式版本判断:Android 10+ 需同时申请前台+后台权限
 | 
			
		||||
		// 1. 先判断前台定位权限(ACCESS_FINE_LOCATION)是否已授予
 | 
			
		||||
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
 | 
			
		||||
            != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
			// 1.1 未授予前台权限:先申请前台权限(API 30+ 后台权限依赖前台权限)
 | 
			
		||||
			String[] foregroundPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
 | 
			
		||||
			// 对API 23+(Android 6.0)动态申请,低版本会直接授予(清单已声明前提下)
 | 
			
		||||
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
				requestPermissions(foregroundPermissions, REQUEST_LOCATION_PERMISSIONS);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// 2. 已授予前台权限:判断是否需要申请后台权限(仅API 29+需要)
 | 
			
		||||
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
 | 
			
		||||
            permissions = new String[]{
 | 
			
		||||
				android.Manifest.permission.ACCESS_FINE_LOCATION,
 | 
			
		||||
				android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            // 低版本仅需申请前台权限
 | 
			
		||||
            permissions = new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION};
 | 
			
		||||
				// 2.1 检查后台权限是否未授予
 | 
			
		||||
				if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
 | 
			
		||||
                    != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
					// 2.2 API 30+ 必须单独申请后台权限(不能和前台权限一起弹框)
 | 
			
		||||
					requestPermissions(
 | 
			
		||||
                        new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
 | 
			
		||||
                        REQUEST_BACKGROUND_LOCATION_PERMISSION
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			// 3. 前台权限已授予(+ 后台权限按需授予):此处可执行定位相关逻辑
 | 
			
		||||
			// doLocationRelatedLogic(); 
 | 
			
		||||
		}
 | 
			
		||||
        // 触发系统权限申请(Java7 显式调用requestPermissions,无方法引用)
 | 
			
		||||
        requestPermissions(permissions, REQUEST_LOCATION_PERMISSIONS);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理权限申请结果(系统弹窗用户选择后回调,Java7 显式重写方法)
 | 
			
		||||
     */
 | 
			
		||||
// 【必须补充】权限申请结果回调(处理用户同意/拒绝逻辑)
 | 
			
		||||
	@Override
 | 
			
		||||
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 | 
			
		||||
	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 | 
			
		||||
		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 | 
			
		||||
        // 仅处理位置权限的申请结果
 | 
			
		||||
		// 处理前台权限申请结果
 | 
			
		||||
		if (requestCode == REQUEST_LOCATION_PERMISSIONS) {
 | 
			
		||||
            boolean allGranted = true;
 | 
			
		||||
            // Java7 显式for循环遍历结果(无增强for简化写法)
 | 
			
		||||
            for (int i = 0; i < grantResults.length; i++) {
 | 
			
		||||
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                    allGranted = false;
 | 
			
		||||
                    break; // 有一个权限未通过,直接终止判断
 | 
			
		||||
			if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
				// 前台权限同意:自动尝试申请后台权限(如果是API 29+)
 | 
			
		||||
				requestLocationPermissions();
 | 
			
		||||
			} else {
 | 
			
		||||
				// 前台权限拒绝:提示用户(可选:引导跳转到应用设置页)
 | 
			
		||||
				Toast.makeText(this, "需要前台定位权限才能使用该功能", Toast.LENGTH_SHORT).show();
 | 
			
		||||
			}
 | 
			
		||||
		} else if (requestCode == REQUEST_BACKGROUND_LOCATION_PERMISSION) {
 | 
			
		||||
			if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
				// 后台权限同意:可执行后台定位逻辑
 | 
			
		||||
				Toast.makeText(this, "已获得后台定位权限", Toast.LENGTH_SHORT).show();
 | 
			
		||||
			} else {
 | 
			
		||||
				// 后台权限拒绝:提示用户(可选:说明后台定位的用途,引导手动开启)
 | 
			
		||||
				Toast.makeText(this, "拒绝后台权限将无法在后台持续定位", Toast.LENGTH_SHORT).show();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
            // 提示用户权限结果,并处理开关状态
 | 
			
		||||
            if (allGranted) {
 | 
			
		||||
                ToastUtils.show("已获取位置权限(含后台),可正常使用定位功能");
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("位置权限未完全授予,无法后台获取GPS数据,请在设置中开启");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,372 +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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 核心调整说明:
 | 
			
		||||
 * 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";
 | 
			
		||||
public class LocationActivity extends Activity {
 | 
			
		||||
    private static final String TAG = "LocationActivity";
 | 
			
		||||
 | 
			
		||||
	// SP配置常量(判断服务是否运行)
 | 
			
		||||
    private static final String SP_SERVICE_CONFIG = "service_config";
 | 
			
		||||
    private static final String KEY_SERVICE_RUNNING = "is_service_running";
 | 
			
		||||
    // 页面核心控件与变量
 | 
			
		||||
    private RecyclerView mRecyclerView;
 | 
			
		||||
    private PositionAdapter mAdapter;
 | 
			
		||||
    // 直连服务实例(替代绑定,通过单例/全局初始化获取)
 | 
			
		||||
    private RecyclerView mRvPosition;
 | 
			
		||||
    private PositionAdapter mPositionAdapter;
 | 
			
		||||
    private ArrayList<PositionModel> mLocalPosCache; // 本地位置缓存(与MainService同步)
 | 
			
		||||
 | 
			
		||||
    // MainService 引用+绑定状态
 | 
			
		||||
    private MainService mMainService;
 | 
			
		||||
    // 缓存服务数据(从服务PUBLIC方法获取,避免重复调用)
 | 
			
		||||
    private ArrayList<PositionModel> mCachedPositionList; 
 | 
			
		||||
    private ArrayList<PositionTaskModel> mCachedTaskList;
 | 
			
		||||
	
 | 
			
		||||
    private boolean isServiceBound = false;
 | 
			
		||||
 | 
			
		||||
    // 服务连接(Java 7 匿名内部类实现)
 | 
			
		||||
    private ServiceConnection mServiceConnection = new ServiceConnection() {
 | 
			
		||||
        @Override
 | 
			
		||||
    public Activity getActivity() {
 | 
			
		||||
        return this;
 | 
			
		||||
        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 String getTag() {
 | 
			
		||||
        return TAG;
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        // 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 绑定MainService(Java 7 显式Intent)
 | 
			
		||||
     */
 | 
			
		||||
    private void bindMainService() {
 | 
			
		||||
        Intent serviceIntent = new Intent(this, MainService.class);
 | 
			
		||||
        // 绑定服务(BIND_AUTO_CREATE:服务不存在时自动创建)
 | 
			
		||||
        bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE);
 | 
			
		||||
        LogUtils.d(TAG, "发起MainService绑定请求");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从MainService同步数据(位置+任务)
 | 
			
		||||
     */
 | 
			
		||||
    private void syncDataFromMainService() {
 | 
			
		||||
        if (!isServiceBound || mMainService == null) {
 | 
			
		||||
            LogUtils.w(TAG, "同步数据失败:MainService未绑定");
 | 
			
		||||
            showToast("服务未就绪,无法加载数据");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 同步位置数据(从服务获取最新列表)
 | 
			
		||||
        ArrayList<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)");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 显示Toast(Java 7 显式Toast.makeText)
 | 
			
		||||
     */
 | 
			
		||||
    private void showToast(String content) {
 | 
			
		||||
        Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDestroy() {
 | 
			
		||||
        super.onDestroy();
 | 
			
		||||
        // 1. 清理Adapter资源
 | 
			
		||||
        if (mAdapter != null) {
 | 
			
		||||
            mAdapter.release();
 | 
			
		||||
            mAdapter = null;
 | 
			
		||||
        }
 | 
			
		||||
        // 2. 置空服务实例+缓存数据(帮助GC回收)
 | 
			
		||||
        mMainService = null;
 | 
			
		||||
        mCachedPositionList = null;
 | 
			
		||||
        mCachedTaskList = null;
 | 
			
		||||
        mRecyclerView = null;
 | 
			
		||||
        LogUtils.d(TAG, "页面销毁:已清理服务实例及缓存数据");
 | 
			
		||||
 | 
			
		||||
        // 1. 释放Adapter资源(反注册服务监听,避免内存泄漏)
 | 
			
		||||
        if (mPositionAdapter != null) {
 | 
			
		||||
            mPositionAdapter.release();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    // ---------------------- 核心初始化(服务实例+数据缓存+状态检查) ----------------------
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化服务实例(关键:通过单例获取,确保全局唯一,避免数据重复)
 | 
			
		||||
     * 注:需在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();
 | 
			
		||||
        // 2. 解绑MainService(避免Activity销毁后服务仍被持有)
 | 
			
		||||
        if (isServiceBound) {
 | 
			
		||||
            unbindService(mServiceConnection);
 | 
			
		||||
            LogUtils.d(TAG, "MainService解绑完成");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
    /**
 | 
			
		||||
     * 检查服务状态(替代原绑定检查,通过服务PUBLIC方法判断)
 | 
			
		||||
     */
 | 
			
		||||
    private void checkServiceStatus() {
 | 
			
		||||
        // 1. 服务实例未初始化
 | 
			
		||||
        if (mMainService == null) {
 | 
			
		||||
            return; // 已在initServiceInstance中处理,此处避免重复finish
 | 
			
		||||
	public static class LocalBinder extends android.os.Binder {
 | 
			
		||||
        // 持有 MainService 实例引用
 | 
			
		||||
        private MainService mService;
 | 
			
		||||
 | 
			
		||||
        // 构造时传入服务实例
 | 
			
		||||
        public LocalBinder(MainService service) {
 | 
			
		||||
            this.mService = service;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2. 检查服务运行状态(通过服务PUBLIC方法isServiceRunning()获取,不访问私有字段)
 | 
			
		||||
//        if (!mMainService.isServiceRunning()) {
 | 
			
		||||
//            // 服务未运行:手动触发启动(调用服务PUBLIC方法run())
 | 
			
		||||
//            mMainService.run();
 | 
			
		||||
//            LogUtils.d(TAG, "服务未运行,已通过PUBLIC方法触发启动");
 | 
			
		||||
//        }
 | 
			
		||||
 | 
			
		||||
        // 3. 检查SP中服务配置(双重校验,确保一致性)
 | 
			
		||||
        SharedPreferences sp = getSharedPreferences(SP_SERVICE_CONFIG, Context.MODE_PRIVATE);
 | 
			
		||||
        boolean isServiceConfigRunning = sp.getBoolean(KEY_SERVICE_RUNNING, false);
 | 
			
		||||
        if (!isServiceConfigRunning) {
 | 
			
		||||
            Toast.makeText(this, "位置服务配置未启用,数据可能无法更新", Toast.LENGTH_SHORT).show();
 | 
			
		||||
        // 对外提供获取服务实例的方法(供Activity调用)
 | 
			
		||||
        public MainService getService() {
 | 
			
		||||
            return mService;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 缓存服务数据(从服务PUBLIC方法获取,解决私有字段未知问题)
 | 
			
		||||
     */
 | 
			
		||||
    private void cacheServiceData() {
 | 
			
		||||
        if (mMainService == null) {
 | 
			
		||||
            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>();
 | 
			
		||||
        
 | 
			
		||||
        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() {
 | 
			
		||||
        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()); // 生成唯一ID(PositionModel需实现)
 | 
			
		||||
        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();
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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任务监听:服务任务变化时自动刷新Adapter(Java 7 接口实现)
 | 
			
		||||
        if (mainService != null) {
 | 
			
		||||
            mainService.registerTaskUpdateListener(this);
 | 
			
		||||
            LogUtils.d(TAG, "已注册MainService任务监听,确保任务数据与服务同步");
 | 
			
		||||
        } else {
 | 
			
		||||
            LogUtils.w(TAG, "构造函数:MainService为空,任务数据无法同步");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LogUtils.d(TAG, "Adapter初始化完成:位置数量=" + mCachedPositionList.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // RecyclerView 核心方法(完全基于Activity缓存数据实现,无接口调用)
 | 
			
		||||
    // RecyclerView 核心方法(Java 7 语法适配)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断当前项的视图类型(简单/编辑):从Activity缓存数据中读取状态
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getItemViewType(int position) {
 | 
			
		||||
        // 从位置缓存获取状态,判断视图类型(简单/编辑)
 | 
			
		||||
        PositionModel posModel = getPositionByIndex(position);
 | 
			
		||||
        // 若位置模型为空或标记为简单视图,返回简单视图类型
 | 
			
		||||
        return (posModel != null && posModel.isSimpleView()) ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建视图Holder(根据类型加载布局,无外部依赖)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
 | 
			
		||||
        LayoutInflater inflater = LayoutInflater.from(mContext);
 | 
			
		||||
        // 根据视图类型加载对应布局(Java 7 条件判断)
 | 
			
		||||
        if (viewType == VIEW_TYPE_SIMPLE) {
 | 
			
		||||
            View simpleView = inflater.inflate(R.layout.item_position_simple, parent, false);
 | 
			
		||||
            return new SimpleViewHolder(simpleView);
 | 
			
		||||
@@ -130,111 +115,117 @@ public class PositionAdapter extends RecyclerView.Adapter<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
 | 
			
		||||
			: ((EditViewHolder) holder).tvEditDistance;
 | 
			
		||||
        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) {
 | 
			
		||||
            mPosDistanceViewMap.remove(posModel.getPositionId()); // 移除不再显示的控件引用
 | 
			
		||||
        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,85 +254,85 @@ 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);
 | 
			
		||||
@@ -348,150 +340,134 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
// 场景3:正常显示距离(按距离范围设置颜色,提升视觉区分度)
 | 
			
		||||
        // 场景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));
 | 
			
		||||
        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米)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
	 - 根据索引获取位置模型(从Activity缓存数据直接读取,无中间层,避免数据不一致)
 | 
			
		||||
     * 根据索引获取位置模型(从位置缓存取数,容错处理)
 | 
			
		||||
     */
 | 
			
		||||
    private PositionModel getPositionByIndex(int index) {
 | 
			
		||||
        if (mCachedPositionList == null || index < 0 || index >= mCachedPositionList.size()) {
 | 
			
		||||
			LogUtils.w(TAG, "获取位置失败:索引无效(" + index + ")或缓存数据为空");
 | 
			
		||||
            LogUtils.w(TAG, "getPositionByIndex:无效索引(" + index + ")或位置缓存为空");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return mCachedPositionList.get(index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
	 - 根据位置ID获取列表索引(从Activity缓存数据遍历,用于视图切换后精准刷新)
 | 
			
		||||
     * 根据位置ID获取列表索引(用于精准刷新视图)
 | 
			
		||||
     */
 | 
			
		||||
    private int getPositionIndexById(String positionId) {
 | 
			
		||||
		if (mCachedPositionList == null || positionId == null || positionId.isEmpty()) {
 | 
			
		||||
			LogUtils.w(TAG, "获取位置索引失败:缓存数据为空或位置ID无效");
 | 
			
		||||
        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,返回对应索引
 | 
			
		||||
                return i; // 找到匹配ID,返回索引
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
		LogUtils.w(TAG, "未找到位置ID:" + positionId + ",返回无效索引-1");
 | 
			
		||||
        LogUtils.w(TAG, "getPositionIndexById:未找到位置ID=" + positionId);
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
	 - 刷新“位置→任务”映射(基于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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 | 
			
		||||
	 - 全量更新数据(接收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());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 | 
			
		||||
	 - 局部更新距离UI(仅更新指定位置的距离显示,避免全量刷新卡顿)
 | 
			
		||||
	 - (Activity同步服务计算的最新距离后调用,精准更新受影响的位置)
 | 
			
		||||
     * 局部更新距离UI(仅更新指定位置的距离,避免全量刷新卡顿)
 | 
			
		||||
     */
 | 
			
		||||
    public void updateSinglePositionDistance(String positionId) {
 | 
			
		||||
// 校验参数:位置ID无效或控件未缓存时,直接返回
 | 
			
		||||
		if (positionId == null || positionId.isEmpty() || !mPosDistanceViewMap.containsKey(positionId)) {
 | 
			
		||||
			LogUtils.w(TAG, "局部更新距离失败:位置ID无效或距离控件未缓存(ID=" + positionId + ")");
 | 
			
		||||
        // 校验参数:位置ID无效或控件未缓存,直接返回
 | 
			
		||||
        if (TextUtils.isEmpty(positionId) || !mPosDistanceViewMap.containsKey(positionId)) {
 | 
			
		||||
            LogUtils.w(TAG, "updateSinglePositionDistance:位置ID无效或控件未缓存(ID=" + positionId + ")");
 | 
			
		||||
            return;
 | 
			
		||||
		}// 从Activity缓存数据中获取最新位置模型(确保距离值是最新的)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 从MainService获取最新位置模型(确保距离值是服务端最新)
 | 
			
		||||
        PositionModel latestPos = null;
 | 
			
		||||
		for (PositionModel pos : mCachedPositionList) {
 | 
			
		||||
        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;
 | 
			
		||||
                    }
 | 
			
		||||
		}// 用最新距离值更新UI(直接操作缓存的距离控件,无需刷新整个项)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 用服务端最新距离更新UI(直接操作缓存的距离控件,无需刷新整个项)
 | 
			
		||||
        if (latestPos != null) {
 | 
			
		||||
			updateDistanceDisplay(mPosDistanceViewMap.get(positionId), latestPos);
 | 
			
		||||
            TextView distanceView = mPosDistanceViewMap.get(positionId);
 | 
			
		||||
            updateDistanceDisplay(distanceView, latestPos);
 | 
			
		||||
            LogUtils.d(TAG, "局部更新距离完成:位置ID=" + positionId + ",最新距离=" + latestPos.getRealPositionDistance() + "米");
 | 
			
		||||
        } else {
 | 
			
		||||
			LogUtils.w(TAG, "局部更新距离失败:未在Activity缓存中找到位置ID=" + positionId);
 | 
			
		||||
            LogUtils.w(TAG, "局部更新距离失败:未在MainService找到位置ID=" + positionId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 全量更新位置数据(从MainService同步最新位置列表,刷新UI)
 | 
			
		||||
     */
 | 
			
		||||
    public void updateAllPositionData(ArrayList<PositionModel> newPosList) {
 | 
			
		||||
        if (newPosList == null) {
 | 
			
		||||
            LogUtils.w(TAG, "updateAllPositionData:新位置列表为空,跳过更新");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
	 - 隐藏软键盘(编辑完成后调用,提升用户体验,避免键盘遮挡)
 | 
			
		||||
        // 同步服务端最新位置数据到本地缓存
 | 
			
		||||
        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) return;
 | 
			
		||||
        if (mContext == null || view == null) {
 | 
			
		||||
            LogUtils.w(TAG, "hideSoftKeyboard:参数为空(上下文/视图),无法隐藏键盘");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Java 7 显式获取输入法服务,避免Lambda
 | 
			
		||||
        InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
 | 
			
		||||
        if (imm != null) {
 | 
			
		||||
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏软键盘
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 | 
			
		||||
	 - 释放资源(Activity销毁时调用,彻底避免内存泄漏)
 | 
			
		||||
	 */
 | 
			
		||||
	public void release() {
 | 
			
		||||
// 清空本地辅助缓存(解除控件、数据映射引用)
 | 
			
		||||
		mPosToTasksMap.clear();
 | 
			
		||||
		mPosDistanceViewMap.clear();
 | 
			
		||||
// 置空回调实例(避免持有Activity引用导致内存泄漏)
 | 
			
		||||
		mOnDeleteListener = null;
 | 
			
		||||
		mOnSavePosListener = null;
 | 
			
		||||
		mOnSaveTaskListener = null;
 | 
			
		||||
// 置空数据源引用(帮助GC回收,避免残留数据占用内存)
 | 
			
		||||
		mCachedPositionList = null;
 | 
			
		||||
		mCachedTaskList = null;
 | 
			
		||||
		LogUtils.d(TAG, "Adapter资源已完全释放");
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // 实现 MainService.TaskUpdateListener 接口(服务任务变化时回调)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onTaskUpdated() {
 | 
			
		||||
        LogUtils.d(TAG, "收到MainService任务更新通知(任务新增/删除/状态变化),刷新UI");
 | 
			
		||||
        // 任务数据变化时,全量刷新Adapter(确保任务数量等显示同步)
 | 
			
		||||
        notifyDataSetChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
// =========================================================================
 | 
			
		||||
// 回调设置方法(LocationActivity调用,绑定交互逻辑,无冗余参数)
 | 
			
		||||
// =========================================================================
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // 回调设置方法(供LocationActivity调用,绑定交互逻辑)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    public void setOnDeleteClickListener(OnDeleteClickListener listener) {
 | 
			
		||||
        this.mOnDeleteListener = listener;
 | 
			
		||||
    }
 | 
			
		||||
@@ -500,25 +476,45 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
 | 
			
		||||
        this.mOnSavePosListener = listener;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public void setOnSavePositionTaskClickListener(OnSavePositionTaskClickListener listener) {
 | 
			
		||||
		this.mOnSaveTaskListener = listener;
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // 资源释放(Activity销毁时调用,避免内存泄漏)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    public void release() {
 | 
			
		||||
        // 1. 反注册MainService任务监听(解除与服务的绑定,避免内存泄漏)
 | 
			
		||||
        MainService mainService = mMainServiceRef.get();
 | 
			
		||||
        if (mainService != null) {
 | 
			
		||||
            mainService.unregisterTaskUpdateListener(this);
 | 
			
		||||
            LogUtils.d(TAG, "已反注册MainService任务监听,避免内存泄漏");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
// =========================================================================
 | 
			
		||||
// 视图Holder类(静态内部类,不持有外部引用,彻底避免内存泄漏)
 | 
			
		||||
// =========================================================================
 | 
			
		||||
	/**
 | 
			
		||||
        // 2. 清空本地缓存(解除控件/数据引用,帮助GC回收)
 | 
			
		||||
        mPosDistanceViewMap.clear();
 | 
			
		||||
        if (mCachedPositionList != null) {
 | 
			
		||||
            mCachedPositionList.clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
	 - 简单视图Holder(仅显示数据,对应布局:item_position_simple.xml)
 | 
			
		||||
        // 3. 置空回调实例(避免持有Activity引用导致内存泄漏)
 | 
			
		||||
        mOnDeleteListener = null;
 | 
			
		||||
        mOnSavePosListener = null;
 | 
			
		||||
 | 
			
		||||
        LogUtils.d(TAG, "Adapter资源已完全释放(缓存清空+监听反注册)");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    // 静态内部类:视图Holder(Java 7 静态内部类,不持有外部引用,避免内存泄漏)
 | 
			
		||||
    // =========================================================================
 | 
			
		||||
    /**
 | 
			
		||||
     * 简单视图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严格对应,避免运行时空指针)
 | 
			
		||||
            // 绑定布局控件(与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);
 | 
			
		||||
@@ -527,8 +523,7 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
	 - 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml)
 | 
			
		||||
     * 编辑视图Holder(含编辑控件+功能按钮,对应布局:item_position_edit.xml)
 | 
			
		||||
     */
 | 
			
		||||
    public static class EditViewHolder extends RecyclerView.ViewHolder {
 | 
			
		||||
        TextView tvEditLon;       // 经度显示控件(不可编辑)
 | 
			
		||||
@@ -540,10 +535,11 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
 | 
			
		||||
        Button btnDelete;         // 删除位置按钮
 | 
			
		||||
        Button btnSave;           // 保存位置按钮
 | 
			
		||||
        Button btnAddTask;        // 新增任务按钮
 | 
			
		||||
		TextView tvTaskCount;     // 任务数量显示控件(简化设计)
 | 
			
		||||
        TextView tvTaskCount;     // 任务数量显示控件
 | 
			
		||||
 | 
			
		||||
        public EditViewHolder(View itemView) {
 | 
			
		||||
            super(itemView);
 | 
			
		||||
// 绑定布局控件(与XML中ID严格对应,避免运行时空指针)
 | 
			
		||||
            // 绑定布局控件(与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);
 | 
			
		||||
@@ -557,3 +553,4 @@ public class PositionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -54,10 +54,13 @@ public class AppConfigsUtil {
 | 
			
		||||
			loadConfigs();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return mAppConfigsModel.isEnableMainService();
 | 
			
		||||
		return (mAppConfigsModel == null) ?false: mAppConfigsModel.isEnableMainService();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setIsEnableMainService(boolean isEnableMainService) {
 | 
			
		||||
		if(mAppConfigsModel == null) {
 | 
			
		||||
			mAppConfigsModel = new AppConfigsModel();
 | 
			
		||||
		}
 | 
			
		||||
		mAppConfigsModel.setIsEnableMainService(isEnableMainService);
 | 
			
		||||
		saveConfigs();
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
<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"
 | 
			
		||||
@@ -14,7 +14,6 @@
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:gravity="center_horizontal">
 | 
			
		||||
 | 
			
		||||
        <!-- 标题 -->
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
@@ -22,7 +21,6 @@
 | 
			
		||||
			android:textSize="22sp"
 | 
			
		||||
			android:textStyle="bold"/>
 | 
			
		||||
 | 
			
		||||
        <!-- 经度显示 -->
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:id="@+id/tv_longitude"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
@@ -31,7 +29,6 @@
 | 
			
		||||
			android:textSize="18sp"
 | 
			
		||||
			android:layout_marginTop="15dp"/>
 | 
			
		||||
 | 
			
		||||
        <!-- 纬度显示 -->
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:id="@+id/tv_latitude"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
@@ -42,7 +39,6 @@
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
    <!-- 2. 新增:位置列表(RecyclerView)- 位于经纬度下方,悬浮按钮上方 -->
 | 
			
		||||
	<androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
		android:id="@+id/rv_position_list"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
@@ -52,7 +48,6 @@
 | 
			
		||||
		android:layout_marginTop="20dp"
 | 
			
		||||
		android:paddingBottom="10dp"/>
 | 
			
		||||
 | 
			
		||||
    <!-- 3. 右下角圆形悬浮按钮(不变) -->
 | 
			
		||||
	<Button
 | 
			
		||||
		android:id="@+id/fab_p_button"
 | 
			
		||||
		android:layout_width="60dp"
 | 
			
		||||
@@ -65,7 +60,8 @@
 | 
			
		||||
		android:textColor="@android:color/white"
 | 
			
		||||
		android:textSize="24sp"
 | 
			
		||||
		android:elevation="6dp"
 | 
			
		||||
        android:padding="0dp"/>
 | 
			
		||||
		android:padding="0dp"
 | 
			
		||||
		android:onClick="addNewPosition"/>
 | 
			
		||||
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
	<Switch
 | 
			
		||||
		android:id="@+id/switch_service_control"
 | 
			
		||||
		android:layout_margin="16dp"
 | 
			
		||||
		android:text="主要服务开关"
 | 
			
		||||
		android:text="GPS服务开关"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"/>
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_margin="16dp"
 | 
			
		||||
		android:onClick="onPositions"
 | 
			
		||||
		android:text="进入位置管理"
 | 
			
		||||
		android:text="位置与任务管理"
 | 
			
		||||
		android:id="@+id/btn_manage_positions"/>
 | 
			
		||||
 | 
			
		||||
	<Button
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_margin="16dp"
 | 
			
		||||
		android:onClick="onLog"
 | 
			
		||||
		android:text="查看操作日志"/>
 | 
			
		||||
		android:text="查看应用日志"/>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="app_name">~笨笨龙~</string>
 | 
			
		||||
    <string name="app_name">寻龙记</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user