Compare commits
	
		
			19 Commits
		
	
	
		
			positions-
			...
			appbase
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 269e7b30be | ||
|   | ca953adec2 | ||
|   | 916e807f1b | ||
|   | 1f57ac5401 | ||
|   | 1ba19d8f77 | ||
|   | 435f7ced79 | ||
|   | bd3270f8dd | ||
|   | 5489977a1a | ||
| 0bb909427c | |||
|   | d4f054a86b | ||
| caef679d9a | |||
|   | 5194d7a188 | ||
| a0f88a423a | |||
|   | 81b5141608 | ||
|   | ddda54bd75 | ||
|   | 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:01:10 HKT 2025 | ||||
| stageCount=5 | ||||
| #Thu Oct 02 13:16:14 GMT 2025 | ||||
| stageCount=8 | ||||
| libraryProject= | ||||
| baseVersion=15.0 | ||||
| publishVersion=15.0.4 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.0.5 | ||||
| publishVersion=15.0.7 | ||||
| buildCount=29 | ||||
| baseBetaVersion=15.0.8 | ||||
|   | ||||
							
								
								
									
										4
									
								
								positions/src/beta/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								positions/src/beta/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">寻龙记#</string> | ||||
| </resources> | ||||
| @@ -41,6 +41,7 @@ import java.util.Arrays; | ||||
| import java.util.Date; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; | ||||
|  | ||||
| public class App extends GlobalApplication { | ||||
|  | ||||
| @@ -51,6 +52,8 @@ public class App extends GlobalApplication { | ||||
|         super.onCreate(); | ||||
| 		setIsDebuging(BuildConfig.DEBUG); | ||||
| 		 | ||||
| 		WinBoLLActivityManager.init(this); | ||||
| 		 | ||||
|         // 初始化 Toast 框架 | ||||
|         ToastUtils.init(this); | ||||
|         // 设置 Toast 布局样式 | ||||
|   | ||||
| @@ -1,24 +1,26 @@ | ||||
| package cc.winboll.studio.positions; | ||||
|  | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.Manifest; | ||||
| import android.app.Activity; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.IBinder; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.CompoundButton; | ||||
| import android.widget.Switch; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.LogActivity; | ||||
| 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; | ||||
| 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; | ||||
|  | ||||
| /** | ||||
|  * 主页面:仅负责 | ||||
| @@ -26,18 +28,31 @@ import com.hjq.toast.ToastUtils; | ||||
|  * 2. 跳转至“位置管理页(LocationActivity)”和“日志页(LogActivity)” | ||||
|  * 3. Java 7 语法适配:无 Lambda、显式接口实现、兼容低版本 | ||||
|  */ | ||||
| public class MainActivity extends AppCompatActivity { | ||||
| 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_LOCATION_PERMISSIONS = 1001; | ||||
| 	private static final int REQUEST_BACKGROUND_LOCATION_PERMISSION = 1002; | ||||
| 	 | ||||
|     // UI 控件:服务控制开关、顶部工具栏 | ||||
|     private Switch mServiceSwitch; | ||||
| 	private Button mManagePositionsButton; | ||||
|     private Toolbar mToolbar; | ||||
|     // 服务相关:服务实例、绑定状态标记 | ||||
|     //private DistanceRefreshService mDistanceService; | ||||
|     private boolean isServiceBound = false; | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     // ---------------------- 服务连接回调(仅用于获取服务状态,不依赖服务执行核心逻辑) ---------------------- | ||||
| //    private final ServiceConnection mServiceConn = new ServiceConnection() { | ||||
| //        /** | ||||
| @@ -69,8 +84,8 @@ public class MainActivity extends AppCompatActivity { | ||||
|  | ||||
|         // 1. 初始化顶部 Toolbar(保留原逻辑,设置页面标题) | ||||
|         initToolbar(); | ||||
|         // 2. 初始化服务控制开关(核心功能:绑定开关点击事件) | ||||
|         initServiceSwitch(); | ||||
|         // 2. 初始化其他控件 | ||||
|         initViews(); | ||||
|         // 3. 检查并申请位置权限(含后台GPS权限,确保服务启动前权限就绪) | ||||
|         if (!checkLocationPermissions()) { | ||||
|             requestLocationPermissions(); | ||||
| @@ -99,17 +114,20 @@ public class MainActivity extends AppCompatActivity { | ||||
|         setSupportActionBar(mToolbar); | ||||
|         // 给ActionBar设置标题(先判断非空,避免空指针异常) | ||||
|         if (getSupportActionBar() != null) { | ||||
|             getSupportActionBar().setTitle("位置管理"); | ||||
|             getSupportActionBar().setTitle(getString(R.string.app_name)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 初始化服务控制开关:读取SP状态、绑定点击事件(含权限检查) | ||||
|      */ | ||||
|     private void initServiceSwitch() { | ||||
|     private void initViews() { | ||||
|         mServiceSwitch = (Switch) findViewById(R.id.switch_service_control); // 显式强转 | ||||
| 		mServiceSwitch.setChecked(AppConfigsUtil.getInstance(this).isEnableMainService(true)); | ||||
|  | ||||
| 		mManagePositionsButton = (Button) findViewById(R.id.btn_manage_positions); | ||||
| 		mManagePositionsButton.setEnabled(mServiceSwitch.isChecked()); | ||||
|  | ||||
|         // Java 7 用匿名内部类实现 CompoundButton.OnCheckedChangeListener | ||||
|         mServiceSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { | ||||
| 				@Override | ||||
| @@ -136,6 +154,8 @@ public class MainActivity extends AppCompatActivity { | ||||
| //						} | ||||
| 						stopService(new Intent(MainActivity.this, MainService.class)); | ||||
| 					} | ||||
|  | ||||
| 					mManagePositionsButton.setEnabled(isChecked); | ||||
| 				} | ||||
| 			}); | ||||
|     } | ||||
| @@ -166,7 +186,7 @@ public class MainActivity extends AppCompatActivity { | ||||
|      * 无服务状态限制,直接跳转 | ||||
|      */ | ||||
|     public void onLog(View view) { | ||||
|         LogActivity.startLogActivity(this); // 调用LogActivity静态方法跳转(保留原逻辑) | ||||
|         WinBoLLActivityManager.getInstance().startLogActivity(this); // 调用LogActivity静态方法跳转(保留原逻辑) | ||||
|     } | ||||
|  | ||||
|     // ---------------------- 新增:位置权限处理(适配Java7 + 后台GPS权限) ---------------------- | ||||
| @@ -190,49 +210,57 @@ public class MainActivity extends AppCompatActivity { | ||||
|         return hasForegroundPerm && hasBackgroundPerm; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 申请「前台+后台」位置权限(弹出系统权限弹窗,适配版本差异) | ||||
|      */ | ||||
|     private void requestLocationPermissions() { | ||||
|         String[] permissions; | ||||
|         // Java7 显式版本判断:Android 10+ 需同时申请前台+后台权限 | ||||
|         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}; | ||||
|         } | ||||
|         // 触发系统权限申请(Java7 显式调用requestPermissions,无方法引用) | ||||
|         requestPermissions(permissions, REQUEST_LOCATION_PERMISSIONS); | ||||
|     } | ||||
| 	private void requestLocationPermissions() { | ||||
| 		// 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) { | ||||
| 				// 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 显式重写方法) | ||||
|      */ | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, String[] permissions, 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; // 有一个权限未通过,直接终止判断 | ||||
|                 } | ||||
|             } | ||||
| // 【必须补充】权限申请结果回调(处理用户同意/拒绝逻辑) | ||||
| 	@Override | ||||
| 	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||||
| 		super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
| 		// 处理前台权限申请结果 | ||||
| 		if (requestCode == REQUEST_LOCATION_PERMISSIONS) { | ||||
| 			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,360 +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.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.appcompat.app.AppCompatActivity; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
|  | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.positions.R; | ||||
| import cc.winboll.studio.positions.adapters.PositionAdapter; | ||||
| import cc.winboll.studio.positions.models.PositionModel; | ||||
| import 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 AppCompatActivity { | ||||
|     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 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); | ||||
|  | ||||
|         // 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; | ||||
|  | ||||
|         // 1. 释放Adapter资源(反注册服务监听,避免内存泄漏) | ||||
|         if (mPositionAdapter != null) { | ||||
|             mPositionAdapter.release(); | ||||
|         } | ||||
|         // 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(); | ||||
|         // 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(); | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,60 @@ | ||||
| package cc.winboll.studio.positions.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/09/29 00:11 | ||||
|  * @Describe WinBoLL 窗口基础类 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.view.MenuItem; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; | ||||
| import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "WinBoLLActivity"; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         LogUtils.d(TAG, String.format("onResume %s", getTag())); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         /*if (item.getItemId() == R.id.item_log) { | ||||
|             WinBoLLActivityManager.getInstance().startLogActivity(this); | ||||
|             return true; | ||||
|         } else if (item.getItemId() == R.id.item_home) { | ||||
| 			startActivity(new Intent(this, MainActivity.class)); | ||||
|             return true; | ||||
|         }*/ | ||||
|         // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|         WinBoLLActivityManager.getInstance().add(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         WinBoLLActivityManager.getInstance().registeRemove(this); | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|         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类(静态内部类,不持有外部引用,彻底避免内存泄漏) | ||||
| // ========================================================================= | ||||
| 	/** | ||||
|     // ========================================================================= | ||||
|     // 静态内部类:视图Holder(Java 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
										
											
												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,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> | ||||
|  | ||||
|   | ||||
| @@ -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,14 +25,15 @@ | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:layout_margin="16dp" | ||||
| 		android:onClick="onPositions" | ||||
| 		android:text="进入位置管理"/> | ||||
| 		android:text="位置与任务管理" | ||||
| 		android:id="@+id/btn_manage_positions"/> | ||||
|  | ||||
| 	<Button | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:layout_margin="16dp" | ||||
| 		android:onClick="onLog" | ||||
| 		android:text="查看操作日志"/> | ||||
| 		android:text="查看应用日志"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								positions/src/main/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								positions/src/main/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">寻龙记</string> | ||||
| </resources> | ||||
| @@ -1,4 +1,3 @@ | ||||
| <resources> | ||||
|     <string name="app_name">Positions</string> | ||||
|      | ||||
| </resources> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user