Selaa lähdekoodia

feat: 架构优化和功能完善

- Push 自动初始化:使用 Jetpack Startup Library 实现自动初始化
- Share 全局回调:添加 setGlobalCallback 支持全局分享回调
- MainActivity 架构:MainFragment 包含底部导航栏,点击跳转到其他 Activity
- 日志系统:移除 Timber,统一使用 LogcatViewer
- 权限管理:迁移到 XXPermissions,统一封装在 base-core
- 网络管理:合并 ApiManager 和 NetworkHelper,简化网络配置
- 图片加载:ImageLoader 重命名为 ImageLoadHelper
- 代码清理:删除未使用的 Fragment 和重复代码
wangmeng 2 kuukautta sitten
vanhempi
commit
dfbe1102a7
87 muutettua tiedostoa jossa 6280 lisäystä ja 5154 poistoa
  1. 3 18
      app/build.gradle
  2. 67 3
      app/src/main/AndroidManifest.xml
  3. 23 139
      app/src/main/java/com/narutohuo/xindazhou/MainActivity.kt
  4. 63 0
      app/src/main/java/com/narutohuo/xindazhou/MainFragment.kt
  5. 134 0
      app/src/main/java/com/narutohuo/xindazhou/community/ui/CommunityActivity.kt
  6. 0 92
      app/src/main/java/com/narutohuo/xindazhou/community/ui/CommunityFragment.kt
  7. 48 23
      app/src/main/java/com/narutohuo/xindazhou/launch/AppInitializer.kt
  8. 0 45
      app/src/main/java/com/narutohuo/xindazhou/main/ui/MainFragment.kt
  9. 74 0
      app/src/main/java/com/narutohuo/xindazhou/service/ui/ServiceActivity.kt
  10. 0 30
      app/src/main/java/com/narutohuo/xindazhou/service/ui/ServiceFragment.kt
  11. 74 0
      app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopActivity.kt
  12. 0 30
      app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopFragment.kt
  13. 167 0
      app/src/main/java/com/narutohuo/xindazhou/user/ui/UserActivity.kt
  14. 113 0
      app/src/main/java/com/narutohuo/xindazhou/user/ui/login/LoginActivity.kt
  15. 0 141
      app/src/main/java/com/narutohuo/xindazhou/user/ui/profile/UserFragment.kt
  16. 118 0
      app/src/main/java/com/narutohuo/xindazhou/user/ui/register/RegisterActivity.kt
  17. 3 3
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt
  18. 269 234
      app/src/main/java/com/narutohuo/xindazhou/vehicle/ui/VehicleFragment.kt
  19. 31 0
      app/src/main/res/layout/activity_community.xml
  20. 1 1
      app/src/main/res/layout/fragment_login.xml
  21. 5 12
      app/src/main/res/layout/activity_main.xml
  22. 1 1
      app/src/main/res/layout/fragment_register.xml
  23. 31 0
      app/src/main/res/layout/activity_service.xml
  24. 32 0
      app/src/main/res/layout/activity_shop.xml
  25. 32 0
      app/src/main/res/layout/activity_user.xml
  26. 744 0
      app/src/main/res/layout/activity_vehicle.xml
  27. 1 14
      app/src/main/res/layout/fragment_main.xml
  28. 6 2
      app/src/main/res/values/themes.xml
  29. 7 0
      app/src/main/res/xml/logcat_filepaths.xml
  30. 3 5
      base-common/build.gradle
  31. 33 34
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt
  32. 4 4
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt
  33. 0 104
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/LoginFragment.kt
  34. 0 107
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/RegisterFragment.kt
  35. 7 7
      base-common/src/main/java/com/narutohuo/xindazhou/common/image/ImageLoader.kt
  36. 5 5
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt
  37. 0 118
      base-common/src/main/java/com/narutohuo/xindazhou/common/log/LogHelper.kt
  38. 46 12
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiManager.kt
  39. 0 104
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/NetworkHelper.kt
  40. 0 934
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求MVVM封装方案.md
  41. 0 926
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求封装方案.html
  42. 0 277
      base-common/src/main/java/com/narutohuo/xindazhou/common/permission/PermissionHelper.kt
  43. 0 472
      base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/SocketIOManager.kt
  44. 0 243
      base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/impl/SocketIOClient.kt
  45. 0 40
      base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/model/SocketIOEvent.kt
  46. 0 17
      base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/model/SocketIOResponse.kt
  47. 16 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityManager.kt
  48. 62 73
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseActivity.kt
  49. 0 156
      base-common/src/main/java/com/narutohuo/xindazhou/common/util/UtilManager.kt
  50. 5 5
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt
  51. 2 2
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/repository/VersionRepository.kt
  52. 0 128
      base-common/src/main/res/layout/fragment_login.xml
  53. 0 134
      base-common/src/main/res/layout/fragment_register.xml
  54. 8 4
      base-core/build.gradle
  55. 162 0
      base-core/src/main/java/com/narutohuo/xindazhou/core/log/FloatingLogButton.kt
  56. 69 23
      base-core/src/main/java/com/narutohuo/xindazhou/core/log/ILog.kt
  57. 232 0
      base-core/src/main/java/com/narutohuo/xindazhou/core/log/impl/LogcatViewerLog.kt
  58. 0 75
      base-core/src/main/java/com/narutohuo/xindazhou/core/log/impl/TimberLog.kt
  59. 212 0
      base-core/src/main/java/com/narutohuo/xindazhou/core/permission/PermissionHelper.kt
  60. 5 0
      base-core/src/main/java/com/narutohuo/xindazhou/core/push/IPushService.kt
  61. 878 4
      base-core/src/main/java/com/narutohuo/xindazhou/core/util/IUtil.kt
  62. 153 76
      capability-ble/src/main/java/com/narutohuo/xindazhou/ble/impl/BLEServiceImpl.kt
  63. 2 2
      capability-ble/src/main/java/com/narutohuo/xindazhou/ble/util/BLEExtension.kt
  64. 9 0
      capability-ble/src/main/java/com/narutohuo/xindazhou/ble/util/BlePacketSender.kt
  65. 475 0
      capability-ble/集成说明.md
  66. 3 0
      capability-push/build.gradle
  67. 35 24
      capability-push/src/main/AndroidManifest.xml
  68. 2 4
      capability-push/src/main/java/com/narutohuo/xindazhou/push/api/PushService.kt
  69. 4 97
      capability-push/src/main/java/com/narutohuo/xindazhou/push/factory/PushServiceFactory.kt
  70. 135 0
      capability-push/src/main/java/com/narutohuo/xindazhou/push/helper/TagAliasOperatorHelper.kt
  71. 413 34
      capability-push/src/main/java/com/narutohuo/xindazhou/push/impl/PushServiceImpl.kt
  72. 0 65
      capability-push/src/main/java/com/narutohuo/xindazhou/push/model/PushConfig.kt
  73. 47 0
      capability-push/src/main/java/com/narutohuo/xindazhou/push/receiver/BootReceiver.kt
  74. 232 48
      capability-push/src/main/java/com/narutohuo/xindazhou/push/receiver/JPushReceiver.kt
  75. 14 0
      capability-push/src/main/java/com/narutohuo/xindazhou/push/service/UserService.java
  76. 42 0
      capability-push/src/main/java/com/narutohuo/xindazhou/push/startup/PushInitializer.kt
  77. 4 2
      capability-push/src/main/res/values/strings.xml
  78. 149 0
      capability-qrcode/使用说明.md
  79. 1 1
      capability-share/build.gradle
  80. 23 0
      capability-share/src/main/java/com/narutohuo/xindazhou/share/factory/ShareServiceFactory.kt
  81. 17 0
      capability-share/src/main/java/com/narutohuo/xindazhou/share/impl/ShareServiceImpl.kt
  82. 283 0
      capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/SocketIOManager.kt
  83. 6 0
      capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/model/SocketIOEvent.kt
  84. 165 0
      capability-socketio/内部处理方案分析.md
  85. 156 0
      capability-socketio/回调函数传递方案对比.md
  86. 113 0
      capability-socketio/架构调整说明.md
  87. 1 0
      gradle.properties

+ 3 - 18
app/build.gradle

@@ -103,8 +103,7 @@ dependencies {
     
     // SDK模块(能力层)- 只依赖模块本身,不要依赖模块内部的库!
     implementation project(':capability-ble')
-    // Socket.IO 已迁移到 base-common,不再需要 capability-socketio 模块
-    // implementation project(':capability-socketio')
+    implementation project(':capability-socketio')
     implementation project(':capability-push')
     implementation project(':capability-qrcode')
     implementation project(':capability-share')
@@ -129,7 +128,7 @@ dependencies {
     implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
     implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
     
-    // 注意:基础第三方库(Timber、Retrofit、OkHttp、Gson、Glide、Coroutines)
+    // 注意:基础第三方库(Retrofit、OkHttp、Gson、Glide、Coroutines)
     // 已通过 base-common 间接获得(base-common → base-core),不需要直接依赖
     
     // Room
@@ -145,21 +144,7 @@ dependencies {
     implementation libs.androidx.compose.ui.tooling.preview
     implementation libs.androidx.compose.material3
     
-    // 调试工具(只在Debug版本使用)
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-core:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-attr:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-measurement:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-disk:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-recorder:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-phoenix:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-crash:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-shared-preferences:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-geiger-counter:0.9.34'
-    debugImplementation 'com.willowtreeapps.hyperion:hyperion-timber:0.9.34'  // 日志查看
-    
-    // Chuck - 网络请求调试(只在Debug版本使用)
-    debugImplementation 'com.github.chuckerteam.chucker:library:4.0.0'
-    releaseImplementation 'com.github.chuckerteam.chucker:library-no-op:4.0.0'
+    // LogcatViewer 依赖已移至 base-core 模块,通过 ILog 统一管理
     
     // ARouter 编译器(用于生成路由代码)
     kapt 'com.alibaba:arouter-compiler:1.5.2'

+ 67 - 3
app/src/main/AndroidManifest.xml

@@ -35,6 +35,9 @@
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <!-- 悬浮窗权限(LogcatViewer 需要) -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    
     <!-- 安装APK权限 -->
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
     <!-- 写入外部存储权限(用于下载APK) -->
@@ -59,8 +62,8 @@
         <!-- 注意:极光推送配置已移到 capability-push 模块的 AndroidManifest.xml 中 -->
         
         
-        <!-- 主界面 Activity(启动 Activity, Activity 架构) -->
-        <!-- 使用 Navigation Component 管理所有页面导航,包括登录页和注册页 -->
+        <!-- 主界面 Activity(启动 Activity, Activity 架构) -->
+        <!-- 包含底部导航栏,点击时跳转到对应的业务模块 Activity -->
         <activity
             android:name=".MainActivity"
             android:exported="true"
@@ -71,7 +74,56 @@
             </intent-filter>
         </activity>
         
-        <!-- 注意:LoginActivity 和 RegisterActivity 已移除,改为使用 Fragment(单 Activity 架构) -->
+        <!-- 登录 Activity -->
+        <activity
+            android:name=".user.ui.login.LoginActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 注册 Activity -->
+        <activity
+            android:name=".user.ui.register.RegisterActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 用户模块 Activity -->
+        <activity
+            android:name=".user.ui.UserActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 车辆模块 Activity -->
+        <activity
+            android:name=".vehicle.ui.VehicleActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 商城模块 Activity -->
+        <activity
+            android:name=".shop.ui.ShopActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 社区模块 Activity -->
+        <activity
+            android:name=".community.ui.CommunityActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- 服务模块 Activity -->
+        <activity
+            android:name=".service.ui.ServiceActivity"
+            android:exported="false"
+            android:theme="@style/Theme.XinDaZhou" />
+        
+        <!-- LogcatViewer Activity(日志查看器) -->
+        <!-- singleTask 确保只有一个实例,多次启动会复用同一个 Activity -->
+        <!-- 使用库自带的主题 Theme.AppCompat.NoActionBar,确保文字可见 -->
+        <activity
+            android:name="com.weijiaxing.logviewer.LogcatActivity"
+            android:exported="false"
+            android:launchMode="singleTask"
+            tools:replace="android:launchMode" />
 
         <!-- FileProvider 用于 Android 7.0+ 安装APK -->
         <provider
@@ -84,6 +136,18 @@
                 android:resource="@xml/file_paths" />
         </provider>
         
+        <!-- LogcatViewer FileProvider -->
+        <!-- 注意:LogcatViewer 库使用的是 android.support 库,不是 androidx -->
+        <provider
+            android:name="com.weijiaxing.logviewer.LogcatFileProvider"
+            android:authorities="${applicationId}.logcat_fileprovider"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/logcat_filepaths" />
+        </provider>
+        
         <!-- 注意:QQ 和微博的回调 Activity 声明已移到 capability-share 模块的 AndroidManifest.xml 中 -->
     </application>
 

+ 23 - 139
app/src/main/java/com/narutohuo/xindazhou/MainActivity.kt

@@ -1,34 +1,28 @@
 package com.narutohuo.xindazhou
 
+import android.content.Intent
 import android.os.Bundle
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.NavHostFragment
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.ui.BaseActivity
 import com.narutohuo.xindazhou.common.version.VersionUpdateManager
 import com.narutohuo.xindazhou.databinding.ActivityMainBinding
-import kotlinx.coroutines.launch
 
 /**
- * 主界面 Activity(单 Activity 架构
+ * 主界面 Activity(架构A:每个页面一个Activity
  * 
  * 职责:
- * 1. 作为应用的唯一 Activity,使用 Navigation Component 管理所有页面导航
- * 2. 启动时检查登录状态,动态导航到相应页面
+ * 1. 作为应用的启动 Activity,包含底部导航栏和 MainFragment
+ * 2. 点击底部导航栏时跳转到对应的业务模块 Activity
  * 3. 版本检查(启动时检查是否有新版本)
  * 
  * 注意:
- * - 这是单 Activity 架构,所有页面都是 Fragment
- * - 登录页(LoginFragment)和注册页(RegisterFragment)都是 Fragment
- * - 主界面(MainFragment)使用 Navigation Component 管理内部导航
- * - 实现 NavigationCallback 接口,处理 Fragment 的导航请求
+ * - MainActivity 显示 MainFragment 作为主页内容
+ * - 底部导航栏用于在不同业务模块之间切换
+ * - 点击底部导航栏时,跳转到对应的 Activity(VehicleActivity, ShopActivity等)
  */
-class MainActivity : BaseActivity<ActivityMainBinding>(), NavigationCallback {
+class MainActivity : BaseActivity<ActivityMainBinding>() {
     
     private val TAG = "MainActivity"
-    private var navHostFragment: NavHostFragment? = null
     
     override fun getViewBinding(): ActivityMainBinding {
         return ActivityMainBinding.inflate(layoutInflater)
@@ -37,138 +31,28 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), NavigationCallback {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         
+        // 设置全屏模式(隐藏状态栏和导航栏)
+        setFullScreenMode()
+        
         // 版本检查(独立处理,不影响页面显示)
         VersionUpdateManager.checkUpdate(this)
     }
     
-    override fun onStart() {
-        super.onStart()
-        
-        // 在 onStart 中初始化导航并检查登录状态
-        // 此时 Fragment 已经完全初始化,NavController 已可用
-        initNavigationAndCheckLogin()
-    }
-    
-    override fun initView() {
-        // Navigation 初始化在 onStart 中完成
-        // 这里可以添加其他初始化逻辑
-    }
-    
     /**
-     * 初始化 Navigation Component 并检查登录状态
-     * 
-     * 默认起始目的地是 loginFragment(在 nav_graph.xml 中设置)
-     * 如果已登录,则导航到 mainFragment
+     * 设置全屏模式
      */
-    private fun initNavigationAndCheckLogin() {
-        // 获取 Navigation Host Fragment
-        if (navHostFragment == null) {
-            navHostFragment = supportFragmentManager.findFragmentById(R.id.navHostFragment) as? NavHostFragment
-        }
-        
-        val navController = navHostFragment?.navController ?: run {
-            LogHelper.w(TAG, "NavController 未找到,延迟检查登录状态")
-            // 如果 NavController 还未初始化,延迟执行
-            binding.root.post {
-                initNavigationAndCheckLogin()
-            }
-            return
-        }
-        
-        // 测试模式:直接导航到主界面,跳过登录检查
-        val currentDestination = navController.currentDestination?.id
-        if (currentDestination == R.id.loginFragment || currentDestination == R.id.registerFragment) {
-            LogHelper.d(TAG, "测试模式:直接导航到主界面,跳过登录检查")
-            navigateToMain()
-        } else {
-            LogHelper.d(TAG, "测试模式:当前已在主界面")
-        }
-        
-        /* 原来的登录检查逻辑(已注释,测试时使用上面的代码)
-        lifecycleScope.launch {
-            try {
-                // 检查登录状态(支持自动刷新 Token)
-                val isLoggedIn = AuthManager.isLoggedInAsync()
-                
-                // 检查当前目的地,避免重复导航
-                val currentDestination = navController.currentDestination?.id
-                
-                if (isLoggedIn) {
-                    // 已登录,如果当前在登录页或注册页,则导航到主界面
-                    if (currentDestination == R.id.loginFragment || currentDestination == R.id.registerFragment) {
-                        LogHelper.d(TAG, "用户已登录,从登录页/注册页导航到主界面")
-                        navigateToMain()
-                    } else {
-                        LogHelper.d(TAG, "用户已登录,当前已在主界面")
-                    }
-                } else {
-                    // 未登录,如果当前在主界面,则导航到登录页
-                    if (currentDestination == R.id.mainFragment) {
-                        LogHelper.d(TAG, "用户未登录,从主界面导航到登录页")
-                        navigateToLogin()
-                    } else {
-                        LogHelper.d(TAG, "用户未登录,当前已在登录页")
-                    }
-                }
-                
-            } catch (e: Exception) {
-                LogHelper.e(TAG, "检查登录状态失败", e)
-                // 出错时保持在当前页面
-            }
-        }
-        */
-    }
-    
-    // ==================== NavigationCallback 接口实现 ====================
-    
-    override fun navigateToMain() {
-        val navController = navHostFragment?.navController ?: return
-        try {
-            val currentDestination = navController.currentDestination?.id
-            // 根据当前目的地选择正确的 action
-            when (currentDestination) {
-                R.id.loginFragment -> {
-                    // 从登录页导航到主界面,清除返回栈
-                    navController.navigate(R.id.action_loginFragment_to_mainFragment)
-                }
-                R.id.registerFragment -> {
-                    // 从注册页导航到主界面,清除返回栈
-                    navController.navigate(R.id.action_registerFragment_to_mainFragment)
-                }
-                else -> {
-                    // 如果已经在主界面,不需要导航
-                    LogHelper.d(TAG, "已在主界面,跳过导航")
-                }
-            }
-        } catch (e: Exception) {
-            LogHelper.e(TAG, "导航到主界面失败", e)
-        }
-    }
-    
-    override fun navigateToLogin() {
-        val navController = navHostFragment?.navController ?: return
-        try {
-            // 导航到登录页,清除注册页的返回栈
-            val currentDestination = navController.currentDestination?.id
-            if (currentDestination == R.id.registerFragment) {
-                navController.navigate(R.id.action_registerFragment_to_loginFragment)
-            } else {
-                // 如果已经在登录页,不需要导航
-                LogHelper.d(TAG, "已在登录页,跳过导航")
-            }
-        } catch (e: Exception) {
-            LogHelper.e(TAG, "导航到登录页失败", e)
-        }
+    private fun setFullScreenMode() {
+        // 隐藏状态栏和导航栏
+        window.decorView.systemUiVisibility = (
+            android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
+            or android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+            or android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+        )
     }
     
-    override fun navigateToRegister() {
-        val navController = navHostFragment?.navController ?: return
-        try {
-            // 导航到注册页
-            navController.navigate(R.id.action_loginFragment_to_registerFragment)
-        } catch (e: Exception) {
-            LogHelper.e(TAG, "导航到注册页失败", e)
-        }
+    override fun initView() {
+        // MainFragment 中已经处理了底部导航栏的点击事件
+        // 这里不需要额外处理
     }
 }
 

+ 63 - 0
app/src/main/java/com/narutohuo/xindazhou/MainFragment.kt

@@ -0,0 +1,63 @@
+package com.narutohuo.xindazhou
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.narutohuo.xindazhou.community.ui.CommunityActivity
+import com.narutohuo.xindazhou.service.ui.ServiceActivity
+import com.narutohuo.xindazhou.shop.ui.ShopActivity
+import com.narutohuo.xindazhou.user.ui.UserActivity
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+import com.google.android.material.bottomnavigation.BottomNavigationView
+
+/**
+ * 主界面 Fragment
+ * 
+ * 包含底部导航栏,点击跳转到对应的 Activity
+ */
+class MainFragment : Fragment() {
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val view = inflater.inflate(R.layout.fragment_main, container, false)
+        
+        val bottomNavView = view.findViewById<BottomNavigationView>(R.id.bottomNavView)
+        bottomNavView.setOnItemSelectedListener { item ->
+            when (item.itemId) {
+                R.id.vehicleFragment -> {
+                    startActivity(Intent(requireContext(), VehicleActivity::class.java))
+                    true
+                }
+                R.id.communityFragment -> {
+                    startActivity(Intent(requireContext(), CommunityActivity::class.java))
+                    true
+                }
+                R.id.serviceFragment -> {
+                    startActivity(Intent(requireContext(), ServiceActivity::class.java))
+                    true
+                }
+                R.id.shopFragment -> {
+                    startActivity(Intent(requireContext(), ShopActivity::class.java))
+                    true
+                }
+                R.id.userFragment -> {
+                    startActivity(Intent(requireContext(), UserActivity::class.java))
+                    true
+                }
+                else -> false
+            }
+        }
+        
+        // 设置默认选中项
+        bottomNavView.selectedItemId = R.id.vehicleFragment
+        
+        return view
+    }
+}
+

+ 134 - 0
app/src/main/java/com/narutohuo/xindazhou/community/ui/CommunityActivity.kt

@@ -0,0 +1,134 @@
+package com.narutohuo.xindazhou.community.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.widget.Toast
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityCommunityBinding
+import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
+import com.narutohuo.xindazhou.service.ui.ServiceActivity
+import com.narutohuo.xindazhou.shop.ui.ShopActivity
+import com.narutohuo.xindazhou.user.ui.UserActivity
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+
+/**
+ * 社区 Activity
+ * 
+ * 架构A:每个页面一个Activity
+ * 显示社区相关功能
+ */
+class CommunityActivity : BaseActivity<ActivityCommunityBinding>() {
+    
+    private val qrCodeManager = QRCodeManagerFactory.getInstance()
+    
+    override fun getViewBinding(): ActivityCommunityBinding {
+        return ActivityCommunityBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        // 创建布局
+        val layout = LinearLayout(this).apply {
+            orientation = LinearLayout.VERTICAL
+            gravity = android.view.Gravity.CENTER
+            setPadding(32, 32, 32, 32)
+        }
+
+        // 标题
+        val textView = TextView(this).apply {
+            text = "社区页面"
+            textSize = 18f
+            gravity = android.view.Gravity.CENTER
+            layoutParams = LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+            ).apply {
+                setMargins(0, 0, 0, 32)
+            }
+        }
+        layout.addView(textView)
+
+        // 扫码按钮
+        val scanButton = Button(this).apply {
+            text = "扫码"
+            layoutParams = LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+            )
+            setOnClickListener {
+                startScanQRCode()
+            }
+        }
+        layout.addView(scanButton)
+        
+        // 将内容添加到布局中
+        binding.contentContainer.removeAllViews()
+        binding.contentContainer.addView(layout)
+        
+        // 设置底部导航栏
+        setupBottomNavigation()
+    }
+    
+    /**
+     * 开始扫码
+     */
+    private fun startScanQRCode() {
+        qrCodeManager.scanQRCode(this) { response ->
+            if (response.success) {
+                val qrCodeContent = response.data
+                Toast.makeText(
+                    this,
+                    "扫码成功:$qrCodeContent",
+                    Toast.LENGTH_SHORT
+                ).show()
+                // TODO: 处理扫码结果
+            } else {
+                val error = response.errorMessage
+                if (error != null) {
+                    Toast.makeText(
+                        this,
+                        "扫码失败:$error",
+                        Toast.LENGTH_SHORT
+                    ).show()
+                }
+            }
+        }
+    }
+    
+    private fun setupBottomNavigation() {
+        binding.bottomNavView.selectedItemId = R.id.communityFragment
+        binding.bottomNavView.setOnItemSelectedListener { item ->
+            when (item.itemId) {
+                R.id.vehicleFragment -> {
+                    startActivity(Intent(this, VehicleActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.communityFragment -> {
+                    // 当前已在社区模块,不需要跳转
+                    true
+                }
+                R.id.serviceFragment -> {
+                    startActivity(Intent(this, ServiceActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.shopFragment -> {
+                    startActivity(Intent(this, ShopActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.userFragment -> {
+                    startActivity(Intent(this, UserActivity::class.java))
+                    finish()
+                    true
+                }
+                else -> false
+            }
+        }
+    }
+}
+

+ 0 - 92
app/src/main/java/com/narutohuo/xindazhou/community/ui/CommunityFragment.kt

@@ -1,92 +0,0 @@
-package com.narutohuo.xindazhou.community.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
-
-/**
- * 社区Fragment
- * 
- * 显示社区相关功能
- */
-class CommunityFragment : Fragment() {
-
-    private val qrCodeManager = QRCodeManagerFactory.getInstance()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // 创建布局
-        val layout = LinearLayout(requireContext()).apply {
-            orientation = LinearLayout.VERTICAL
-            gravity = android.view.Gravity.CENTER
-            setPadding(32, 32, 32, 32)
-        }
-
-        // 标题
-        val textView = TextView(requireContext()).apply {
-            text = "社区页面"
-            textSize = 18f
-            gravity = android.view.Gravity.CENTER
-            layoutParams = LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.MATCH_PARENT,
-                LinearLayout.LayoutParams.WRAP_CONTENT
-            ).apply {
-                setMargins(0, 0, 0, 32)
-            }
-        }
-        layout.addView(textView)
-
-        // 扫码按钮
-        val scanButton = Button(requireContext()).apply {
-            text = "扫码"
-            layoutParams = LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT,
-                LinearLayout.LayoutParams.WRAP_CONTENT
-            )
-            setOnClickListener {
-                startScanQRCode()
-            }
-        }
-        layout.addView(scanButton)
-
-        return layout
-    }
-
-    /**
-     * 开始扫码
-     */
-    private fun startScanQRCode() {
-        val activity = requireActivity()
-        qrCodeManager.scanQRCode(activity) { response ->
-            if (response.success) {
-                val qrCodeContent = response.data
-                Toast.makeText(
-                    requireContext(),
-                    "扫码成功:$qrCodeContent",
-                    Toast.LENGTH_SHORT
-                ).show()
-                // TODO: 处理扫码结果
-            } else {
-                val error = response.errorMessage
-                if (error != null) {
-                    Toast.makeText(
-                        requireContext(),
-                        "扫码失败:$error",
-                        Toast.LENGTH_SHORT
-                    ).show()
-                }
-            }
-        }
-    }
-}
-

+ 48 - 23
app/src/main/java/com/narutohuo/xindazhou/launch/AppInitializer.kt

@@ -4,12 +4,13 @@ import android.app.Application
 import com.alibaba.android.arouter.launcher.ARouter
 import com.narutohuo.xindazhou.common.auth.AuthManager
 import com.narutohuo.xindazhou.common.config.ServerConfigManager
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.network.ApiManager
-import com.narutohuo.xindazhou.common.socketio.SocketIOManager
 import com.narutohuo.xindazhou.common.version.VersionUpdateManager
+import com.narutohuo.xindazhou.socketio.SocketIOManager
 import com.narutohuo.xindazhou.core.storage.StorageImpl
 import com.narutohuo.xindazhou.share.factory.ShareServiceFactory
+import com.narutohuo.xindazhou.push.factory.PushServiceFactory
 
 /**
  * 应用初始化管理器
@@ -39,54 +40,77 @@ object AppInitializer {
      */
     fun init(application: Application) {
         if (initialized) {
-            LogHelper.w(TAG, "AppInitializer 已初始化,跳过重复初始化")
+            ILog.w(TAG, "AppInitializer 已初始化,跳过重复初始化")
             return
         }
         
         try {
-            // 1. 初始化统一存储(必须在其他初始化之前,因为其他模块可能依赖存储)
+            // 1. 初始化日志系统(必须在其他初始化之前,因为其他模块可能依赖日志)
+            initLogging(application)
+            
+            // 2. 初始化统一存储(必须在其他初始化之前,因为其他模块可能依赖存储)
             StorageImpl.init(application.applicationContext)
-            LogHelper.d(TAG, "统一存储初始化完成")
+            ILog.d(TAG, "统一存储初始化完成")
             
-            // 2. 初始化 ARouter(必须在其他初始化之前)
+            // 3. 初始化 ARouter(必须在其他初始化之前)
             initARouter(application)
             
-            // 3. 初始化配置管理器(base-common)
+            // 4. 初始化配置管理器(base-common)
             ServerConfigManager.init(application.applicationContext)
             
-            // 4. 初始化网络管理器(base-common)
+            // 5. 初始化网络管理器(base-common)
             // ApiManager.initialize() 会自动从 ServerConfigManager 读取服务器地址并初始化 NetworkHelper
             // Token Provider 稍后由 AuthManager 设置
             ApiManager.initialize(application)
-            LogHelper.d(TAG, "✅ 网络管理器初始化完成")
+            ILog.d(TAG, "✅ 网络管理器初始化完成")
             
-            // 5. 初始化认证管理器(base-common,会自动初始化 TokenStore,TokenStore 使用 StorageImpl)
+            // 6. 初始化认证管理器(base-common,会自动初始化 TokenStore,TokenStore 使用 StorageImpl)
             // AuthManager.init() 会自动配置 NetworkHelper 的 Token Provider
             AuthManager.init(application.applicationContext)
-            LogHelper.d(TAG, "✅ 认证管理器初始化完成")
+            ILog.d(TAG, "✅ 认证管理器初始化完成")
             
-            // 6. 初始化版本更新管理器(base-common)
+            // 7. 初始化版本更新管理器(base-common)
             VersionUpdateManager.init(application.applicationContext)
-            LogHelper.d(TAG, "✅ 版本更新管理器初始化完成")
+            ILog.d(TAG, "✅ 版本更新管理器初始化完成")
             
-            // 7. SocketIO 管理器(base-common
+            // 8. SocketIO 管理器(capability-socketio
             // SocketIOManager 会自动处理连接、Token 刷新、重连等
             // 业务层可以直接订阅消息,无需关心连接细节
-            SocketIOManager.initialize(application)
-            LogHelper.d(TAG, "✅ SocketIO 管理器初始化完成(自动处理连接和Token刷新)")
+            SocketIOManager.initialize(
+                application,
+                isLoggedInProvider = { AuthManager.isLoggedIn() },
+                refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
+            )
+            ILog.d(TAG, "✅ SocketIO 管理器初始化完成(自动处理连接和Token刷新)")
             
-            // 8. 初始化分享服务(capability-share,可选)
+            // 9. 初始化分享服务(capability-share,可选)
             initShareService(application)
             
+            // 10. 推送服务已通过 Startup Library 自动初始化,无需手动调用
+            // 如需自定义监听器,可以调用 PushServiceFactory.getInstance().setMessageListener()
+            ILog.d(TAG, "✅ 推送服务已自动初始化(Startup Library)")
+            
             initialized = true
-            LogHelper.d(TAG, "所有模块初始化完成")
+            ILog.d(TAG, "所有模块初始化完成")
         } catch (e: Exception) {
-            LogHelper.e(TAG, "初始化失败", e)
+            ILog.e(TAG, "初始化失败", e)
             throw e
         }
     }
     
     /**
+     * 初始化日志系统
+     * 
+     * 自动启动 LogcatViewer:通过 ActivityLifecycleCallbacks 在第一个 Activity resume 时自动启动
+     */
+    private fun initLogging(application: Application) {
+        val isDebug = isDebugMode(application)
+        // 传入 application,内部会自动注册 ActivityLifecycleCallbacks 来启动 LogcatViewer
+        com.narutohuo.xindazhou.core.log.ILog.init(application, enableLogging = isDebug)
+        ILog.d(TAG, "日志系统初始化完成(使用 LogcatViewer),将在 Activity 启动时自动显示悬浮按钮")
+    }
+    
+    /**
      * 初始化 ARouter
      */
     private fun initARouter(application: Application) {
@@ -99,7 +123,7 @@ object AppInitializer {
         }
         // 初始化 ARouter
         ARouter.init(application)
-        LogHelper.d(TAG, "ARouter 初始化完成")
+        ILog.d(TAG, "ARouter 初始化完成")
     }
     
     /**
@@ -110,13 +134,14 @@ object AppInitializer {
     private fun initShareService(application: Application) {
         try {
             ShareServiceFactory.init(application)
-            LogHelper.d(TAG, "分享服务初始化成功")
+            ILog.d(TAG, "分享服务初始化成功")
         } catch (e: Exception) {
             // 分享服务初始化失败不影响应用运行,但分享功能将不可用
-            LogHelper.e(TAG, "分享服务初始化失败,分享功能将不可用", e)
+            ILog.e(TAG, "分享服务初始化失败,分享功能将不可用", e)
         }
     }
     
+    
     /**
      * 获取是否为调试模式
      * 
@@ -128,7 +153,7 @@ object AppInitializer {
             val debugField = buildConfigClass.getField("DEBUG")
             debugField.getBoolean(null)
         } catch (e: Exception) {
-            LogHelper.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
+            ILog.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
             false
         }
     }

+ 0 - 45
app/src/main/java/com/narutohuo/xindazhou/main/ui/MainFragment.kt

@@ -1,45 +0,0 @@
-package com.narutohuo.xindazhou.main.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.ui.setupWithNavController
-import com.google.android.material.bottomnavigation.BottomNavigationView
-import com.narutohuo.xindazhou.R
-
-/**
- * 主界面Fragment
- * 
- * 包含底部导航栏(TabBar)和各个业务模块的Fragment容器
- * 默认显示"车辆"标签页
- */
-class MainFragment : Fragment() {
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_main, container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-
-        val bottomNavView: BottomNavigationView = view.findViewById(R.id.bottomNavView)
-
-        // 获取嵌套的 NavController(main_nav_graph)
-        // FragmentContainerView 会自动创建 NavHostFragment
-        val childNavHostFragment = childFragmentManager.findFragmentById(R.id.navHostFragment) 
-            as? androidx.navigation.fragment.NavHostFragment
-        
-        // 将底部导航栏与嵌套的 NavController 关联
-        childNavHostFragment?.navController?.let { navController ->
-            bottomNavView.setupWithNavController(navController)
-        }
-    }
-}
-

+ 74 - 0
app/src/main/java/com/narutohuo/xindazhou/service/ui/ServiceActivity.kt

@@ -0,0 +1,74 @@
+package com.narutohuo.xindazhou.service.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.TextView
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityServiceBinding
+import com.narutohuo.xindazhou.community.ui.CommunityActivity
+import com.narutohuo.xindazhou.shop.ui.ShopActivity
+import com.narutohuo.xindazhou.user.ui.UserActivity
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+
+/**
+ * 服务 Activity
+ * 
+ * 架构A:每个页面一个Activity
+ * 显示服务相关功能
+ */
+class ServiceActivity : BaseActivity<ActivityServiceBinding>() {
+    
+    override fun getViewBinding(): ActivityServiceBinding {
+        return ActivityServiceBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        // 设置内容
+        val textView = TextView(this)
+        textView.text = "服务页面\n(待实现)"
+        textView.textSize = 18f
+        textView.gravity = android.view.Gravity.CENTER
+        
+        // 将内容添加到布局中
+        binding.contentContainer.removeAllViews()
+        binding.contentContainer.addView(textView)
+        
+        // 设置底部导航栏
+        setupBottomNavigation()
+    }
+    
+    private fun setupBottomNavigation() {
+        binding.bottomNavView.selectedItemId = R.id.serviceFragment
+        binding.bottomNavView.setOnItemSelectedListener { item ->
+            when (item.itemId) {
+                R.id.vehicleFragment -> {
+                    startActivity(Intent(this, VehicleActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.communityFragment -> {
+                    startActivity(Intent(this, CommunityActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.serviceFragment -> {
+                    // 当前已在服务模块,不需要跳转
+                    true
+                }
+                R.id.shopFragment -> {
+                    startActivity(Intent(this, ShopActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.userFragment -> {
+                    startActivity(Intent(this, UserActivity::class.java))
+                    finish()
+                    true
+                }
+                else -> false
+            }
+        }
+    }
+}
+

+ 0 - 30
app/src/main/java/com/narutohuo/xindazhou/service/ui/ServiceFragment.kt

@@ -1,30 +0,0 @@
-package com.narutohuo.xindazhou.service.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-
-/**
- * 服务Fragment
- * 
- * 显示服务相关功能
- */
-class ServiceFragment : Fragment() {
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // 临时使用简单的 TextView,后续可以替换为实际布局
-        val textView = TextView(requireContext())
-        textView.text = "服务页面\n(待实现)"
-        textView.textSize = 18f
-        textView.gravity = android.view.Gravity.CENTER
-        return textView
-    }
-}
-

+ 74 - 0
app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopActivity.kt

@@ -0,0 +1,74 @@
+package com.narutohuo.xindazhou.shop.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.TextView
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityShopBinding
+import com.narutohuo.xindazhou.community.ui.CommunityActivity
+import com.narutohuo.xindazhou.service.ui.ServiceActivity
+import com.narutohuo.xindazhou.user.ui.UserActivity
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+
+/**
+ * 商城 Activity
+ * 
+ * 架构A:每个页面一个Activity
+ * 显示商城相关功能
+ */
+class ShopActivity : BaseActivity<ActivityShopBinding>() {
+    
+    override fun getViewBinding(): ActivityShopBinding {
+        return ActivityShopBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        // 设置内容
+        val textView = TextView(this)
+        textView.text = "商城页面\n(待实现)"
+        textView.textSize = 18f
+        textView.gravity = android.view.Gravity.CENTER
+        
+        // 将内容添加到布局中
+        binding.contentContainer.removeAllViews()
+        binding.contentContainer.addView(textView)
+        
+        // 设置底部导航栏
+        setupBottomNavigation()
+    }
+    
+    private fun setupBottomNavigation() {
+        binding.bottomNavView.selectedItemId = R.id.shopFragment
+        binding.bottomNavView.setOnItemSelectedListener { item ->
+            when (item.itemId) {
+                R.id.vehicleFragment -> {
+                    startActivity(Intent(this, VehicleActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.communityFragment -> {
+                    startActivity(Intent(this, CommunityActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.serviceFragment -> {
+                    startActivity(Intent(this, ServiceActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.shopFragment -> {
+                    // 当前已在商城模块,不需要跳转
+                    true
+                }
+                R.id.userFragment -> {
+                    startActivity(Intent(this, UserActivity::class.java))
+                    finish()
+                    true
+                }
+                else -> false
+            }
+        }
+    }
+}
+

+ 0 - 30
app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopFragment.kt

@@ -1,30 +0,0 @@
-package com.narutohuo.xindazhou.shop.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-
-/**
- * 商城Fragment
- * 
- * 显示商城相关功能
- */
-class ShopFragment : Fragment() {
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        // 临时使用简单的 TextView,后续可以替换为实际布局
-        val textView = TextView(requireContext())
-        textView.text = "商城页面\n(待实现)"
-        textView.textSize = 18f
-        textView.gravity = android.view.Gravity.CENTER
-        return textView
-    }
-}
-

+ 167 - 0
app/src/main/java/com/narutohuo/xindazhou/user/ui/UserActivity.kt

@@ -0,0 +1,167 @@
+package com.narutohuo.xindazhou.user.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import android.widget.Toast
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.ViewModelProvider
+import com.google.android.material.button.MaterialButton
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityUserBinding
+import com.narutohuo.xindazhou.databinding.FragmentUserSocketTestBinding
+import com.narutohuo.xindazhou.community.ui.CommunityActivity
+import com.narutohuo.xindazhou.service.ui.ServiceActivity
+import com.narutohuo.xindazhou.shop.ui.ShopActivity
+import com.narutohuo.xindazhou.user.ui.viewmodel.UserViewModel
+import com.narutohuo.xindazhou.user.ui.viewmodel.UserViewModelFactory
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+import kotlinx.coroutines.launch
+
+/**
+ * 用户 Activity
+ * 
+ * 架构A:每个页面一个Activity
+ * 包含 SocketIO 测试功能
+ */
+class UserActivity : BaseActivity<ActivityUserBinding>() {
+    
+    private lateinit var viewModel: UserViewModel
+    private lateinit var contentBinding: FragmentUserSocketTestBinding
+    
+    private lateinit var tvConnectionStatus: TextView
+    private lateinit var btnConnect: MaterialButton
+    private lateinit var btnFindVehicle: MaterialButton
+    private lateinit var tvLog: TextView
+    
+    override fun getViewBinding(): ActivityUserBinding {
+        return ActivityUserBinding.inflate(layoutInflater)
+    }
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 初始化 ViewModel
+        viewModel = androidx.lifecycle.ViewModelProvider(
+            this,
+            UserViewModelFactory(application)
+        )[UserViewModel::class.java]
+        
+        // 初始化内容布局
+        contentBinding = FragmentUserSocketTestBinding.bind(binding.contentLayout.root)
+        
+        initViews()
+        setupClickListeners()
+        observeUiState()
+        observeAlarmMessage()
+        observeErrorMessage()
+    }
+    
+    override fun initView() {
+        // 设置底部导航栏
+        setupBottomNavigation()
+    }
+    
+    private fun initViews() {
+        tvConnectionStatus = contentBinding.tvConnectionStatus
+        btnConnect = contentBinding.btnConnect
+        btnFindVehicle = contentBinding.btnFindVehicle
+        tvLog = contentBinding.tvLog
+    }
+    
+    private fun setupClickListeners() {
+        btnConnect.setOnClickListener {
+            if (viewModel.uiState.value.isConnected) {
+                viewModel.disconnect()
+            } else {
+                viewModel.connect()
+            }
+        }
+
+        btnFindVehicle.setOnClickListener {
+            viewModel.sendFindVehicleCommand()
+        }
+    }
+    
+    /**
+     * 观察 UI 状态(连接状态、日志列表)
+     */
+    private fun observeUiState() {
+        lifecycleScope.launch {
+            viewModel.uiState.collect { state ->
+                tvConnectionStatus.text = "连接状态:${if (state.isConnected) "已连接" else "未连接"}"
+                btnConnect.text = if (state.isConnected) "断开连接" else "连接SocketIO"
+                btnFindVehicle.isEnabled = state.isConnected
+                tvLog.text = state.logs.joinToString("\n")
+                
+                tvLog.post {
+                    val scrollView = tvLog.parent as? android.widget.ScrollView
+                    scrollView?.fullScroll(View.FOCUS_DOWN)
+                }
+            }
+        }
+    }
+
+    /**
+     * 观察报警消息
+     */
+    private fun observeAlarmMessage() {
+        lifecycleScope.launch {
+            viewModel.alarmMessage.collect { alarm ->
+                alarm?.let {
+                    Toast.makeText(this@UserActivity, "收到车辆报警!", Toast.LENGTH_LONG).show()
+                }
+            }
+        }
+    }
+
+    /**
+     * 观察错误消息
+     */
+    private fun observeErrorMessage() {
+        lifecycleScope.launch {
+            viewModel.errorMessage.collect { error ->
+                error?.let {
+                    Toast.makeText(this@UserActivity, error, Toast.LENGTH_SHORT).show()
+                    viewModel.clearErrorMessage()
+                }
+            }
+        }
+    }
+    
+    private fun setupBottomNavigation() {
+        binding.bottomNavView.selectedItemId = R.id.userFragment
+        binding.bottomNavView.setOnItemSelectedListener { item ->
+            when (item.itemId) {
+                R.id.vehicleFragment -> {
+                    startActivity(Intent(this, VehicleActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.communityFragment -> {
+                    startActivity(Intent(this, CommunityActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.serviceFragment -> {
+                    startActivity(Intent(this, ServiceActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.shopFragment -> {
+                    startActivity(Intent(this, ShopActivity::class.java))
+                    finish()
+                    true
+                }
+                R.id.userFragment -> {
+                    // 当前已在用户模块,不需要跳转
+                    true
+                }
+                else -> false
+            }
+        }
+    }
+}
+

+ 113 - 0
app/src/main/java/com/narutohuo/xindazhou/user/ui/login/LoginActivity.kt

@@ -0,0 +1,113 @@
+package com.narutohuo.xindazhou.user.ui.login
+
+import android.content.Intent
+import android.text.TextUtils
+import android.view.View
+import android.widget.Toast
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.textfield.TextInputEditText
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.auth.NavigationCallback
+import com.narutohuo.xindazhou.common.dialog.ServerConfigDialog
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityLoginBinding
+import com.narutohuo.xindazhou.socketio.SocketIOManager
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+import kotlinx.coroutines.launch
+
+/**
+ * 登录 Activity
+ * 
+ * 多 Activity 架构:登录模块独立 Activity
+ * 实现 NavigationCallback 接口,支持导航请求
+ */
+class LoginActivity : BaseActivity<ActivityLoginBinding>(), NavigationCallback {
+    
+    private lateinit var etMobile: TextInputEditText
+    private lateinit var etPassword: TextInputEditText
+    private lateinit var btnLogin: MaterialButton
+    private lateinit var btnRegister: MaterialButton
+    private lateinit var progressBar: View
+    private var ivServerConfig: View? = null
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        etMobile = binding.etMobile
+        etPassword = binding.etPassword
+        btnLogin = binding.btnLogin
+        btnRegister = binding.btnRegister
+        progressBar = binding.progressBar
+        ivServerConfig = binding.ivServerConfig
+        
+        // 【测试功能】服务器配置按钮
+        ivServerConfig?.setOnClickListener {
+            val dialog = ServerConfigDialog()
+            dialog.show(supportFragmentManager, "ServerConfigDialog")
+        }
+        
+        // 登录按钮
+        btnLogin.setOnClickListener {
+            val mobile = etMobile.text?.toString()?.trim() ?: ""
+            val password = etPassword.text?.toString() ?: ""
+            
+            if (TextUtils.isEmpty(mobile)) {
+                Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (TextUtils.isEmpty(password)) {
+                Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            // 显示加载状态
+            progressBar.visibility = View.VISIBLE
+            btnLogin.isEnabled = false
+            
+            // 调用 AuthManager 登录
+            lifecycleScope.launch {
+                AuthManager.login(this@LoginActivity, mobile, password) { result ->
+                    progressBar.visibility = View.GONE
+                    btnLogin.isEnabled = true
+                    
+                    result.onSuccess {
+                        Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
+                        // 登录成功后,确保 SocketIO 已连接(从存储中读取 Token)
+                        SocketIOManager.ensureConnected()
+                        // 跳转到主界面
+                        navigateToMain()
+                    }.onFailure { error ->
+                        Toast.makeText(this@LoginActivity, error.message ?: "登录失败", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+        
+        // 注册按钮
+        btnRegister.setOnClickListener {
+            // 跳转到注册页
+            navigateToRegister()
+        }
+    }
+    
+    // ==================== NavigationCallback 接口实现 ====================
+    
+    override fun navigateToMain() {
+        startActivity(Intent(this, VehicleActivity::class.java))
+        finish()
+    }
+    
+    override fun navigateToLogin() {
+        // 当前已在登录页,不需要跳转
+    }
+    
+    override fun navigateToRegister() {
+        startActivity(Intent(this, com.narutohuo.xindazhou.user.ui.register.RegisterActivity::class.java))
+    }
+}
+

+ 0 - 141
app/src/main/java/com/narutohuo/xindazhou/user/ui/profile/UserFragment.kt

@@ -1,141 +0,0 @@
-package com.narutohuo.xindazhou.user.ui.profile
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.button.MaterialButton
-import com.narutohuo.xindazhou.R
-import com.narutohuo.xindazhou.user.ui.viewmodel.UserViewModel
-import com.narutohuo.xindazhou.user.ui.viewmodel.UserViewModelFactory
-import kotlinx.coroutines.launch
-
-/**
- * 用户Fragment - 包含 SocketIO 测试功能
- * 
- * 功能:
- * 1. SocketIO 连接测试
- * 2. 发送寻车指令测试(上行)
- * 3. 接收报警消息测试(下行)
- * 
- * 架构:MVVM
- * - View (Fragment): 只负责 UI 初始化和状态观察
- * - ViewModel: 管理业务逻辑和状态
- * - SocketIOManager: Socket.IO 管理器(base-common,已在 AppInitializer 中统一初始化)
- */
-class UserFragment : Fragment() {
-
-    private val viewModel: UserViewModel by viewModels {
-        UserViewModelFactory(requireContext().applicationContext as android.app.Application)
-    }
-
-    private lateinit var tvConnectionStatus: TextView
-    private lateinit var btnConnect: MaterialButton
-    private lateinit var btnFindVehicle: MaterialButton
-    private lateinit var tvLog: TextView
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_user_socket_test, container, false)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-
-        // 初始化视图
-        tvConnectionStatus = view.findViewById(R.id.tvConnectionStatus)
-        btnConnect = view.findViewById(R.id.btnConnect)
-        btnFindVehicle = view.findViewById(R.id.btnFindVehicle)
-        tvLog = view.findViewById(R.id.tvLog)
-
-        // 设置按钮点击事件
-        btnConnect.setOnClickListener {
-            if (viewModel.uiState.value.isConnected) {
-                viewModel.disconnect()
-            } else {
-                viewModel.connect()
-            }
-        }
-
-        btnFindVehicle.setOnClickListener {
-            viewModel.sendFindVehicleCommand()
-        }
-
-        // 观察 UI 状态
-        observeUiState()
-
-        // 观察报警消息
-        observeAlarmMessage()
-
-        // 观察错误消息
-        observeErrorMessage()
-    }
-
-    /**
-     * 观察 UI 状态(连接状态、日志列表)
-     */
-    private fun observeUiState() {
-        lifecycleScope.launch {
-            viewModel.uiState.collect { state ->
-                // 更新连接状态显示
-                tvConnectionStatus.text = "连接状态:${if (state.isConnected) "已连接" else "未连接"}"
-                
-                // 更新连接按钮文本
-                btnConnect.text = if (state.isConnected) "断开连接" else "连接SocketIO"
-                
-                // 更新寻车按钮状态
-                btnFindVehicle.isEnabled = state.isConnected
-                
-                // 更新日志显示
-                tvLog.text = state.logs.joinToString("\n")
-                
-                // 自动滚动到底部
-                tvLog.post {
-                    val scrollView = tvLog.parent as? android.widget.ScrollView
-                    scrollView?.fullScroll(View.FOCUS_DOWN)
-                }
-            }
-        }
-    }
-
-    /**
-     * 观察报警消息
-     */
-    private fun observeAlarmMessage() {
-        lifecycleScope.launch {
-            viewModel.alarmMessage.collect { alarm ->
-                alarm?.let {
-                    Toast.makeText(requireContext(), "收到车辆报警!", Toast.LENGTH_LONG).show()
-                }
-            }
-        }
-    }
-
-    /**
-     * 观察错误消息
-     */
-    private fun observeErrorMessage() {
-        lifecycleScope.launch {
-            viewModel.errorMessage.collect { error ->
-                error?.let {
-                    Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
-                    viewModel.clearErrorMessage()
-                }
-            }
-        }
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        // 注意:不在这里断开连接,因为是单例,其他页面可能还需要使用
-        // 如果需要断开,可以在应用退出时统一处理
-    }
-}

+ 118 - 0
app/src/main/java/com/narutohuo/xindazhou/user/ui/register/RegisterActivity.kt

@@ -0,0 +1,118 @@
+package com.narutohuo.xindazhou.user.ui.register
+
+import android.content.Intent
+import android.text.TextUtils
+import android.view.View
+import android.widget.Toast
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.textfield.TextInputEditText
+import com.narutohuo.xindazhou.R
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.auth.NavigationCallback
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.databinding.ActivityRegisterBinding
+import com.narutohuo.xindazhou.socketio.SocketIOManager
+import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
+import kotlinx.coroutines.launch
+
+/**
+ * 注册 Activity
+ * 
+ * 多 Activity 架构:注册模块独立 Activity
+ * 实现 NavigationCallback 接口,支持导航请求
+ */
+class RegisterActivity : BaseActivity<ActivityRegisterBinding>(), NavigationCallback {
+    
+    private lateinit var etMobile: TextInputEditText
+    private lateinit var etPassword: TextInputEditText
+    private lateinit var etConfirmPassword: TextInputEditText
+    private lateinit var btnRegister: MaterialButton
+    private lateinit var btnBackToLogin: MaterialButton
+    private lateinit var progressBar: View
+    
+    override fun getViewBinding(): ActivityRegisterBinding {
+        return ActivityRegisterBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        etMobile = binding.etMobile
+        etPassword = binding.etPassword
+        etConfirmPassword = binding.etConfirmPassword
+        btnRegister = binding.btnRegister
+        btnBackToLogin = binding.btnBackToLogin
+        progressBar = binding.progressBar
+        
+        // 注册按钮
+        btnRegister.setOnClickListener {
+            val mobile = etMobile.text?.toString()?.trim() ?: ""
+            val password = etPassword.text?.toString() ?: ""
+            val confirmPassword = etConfirmPassword.text?.toString() ?: ""
+            
+            if (TextUtils.isEmpty(mobile)) {
+                Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (TextUtils.isEmpty(password)) {
+                Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (password.length < 6 || password.length > 16) {
+                Toast.makeText(this, "密码长度为6-16位", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (password != confirmPassword) {
+                Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            // 显示加载状态
+            progressBar.visibility = View.VISIBLE
+            btnRegister.isEnabled = false
+            
+            // 调用 AuthManager 注册
+            lifecycleScope.launch {
+                AuthManager.register(this@RegisterActivity, mobile, password) { result ->
+                    progressBar.visibility = View.GONE
+                    btnRegister.isEnabled = true
+                    
+                    result.onSuccess {
+                        Toast.makeText(this@RegisterActivity, "注册成功,请登录", Toast.LENGTH_SHORT).show()
+                        // 注册成功后,确保 SocketIO 已连接(从存储中读取 Token)
+                        SocketIOManager.ensureConnected()
+                        // 跳转到登录页
+                        navigateToLogin()
+                    }.onFailure { error ->
+                        Toast.makeText(this@RegisterActivity, error.message ?: "注册失败", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+        
+        // 返回登录按钮
+        btnBackToLogin.setOnClickListener {
+            // 跳转到登录页
+            navigateToLogin()
+        }
+    }
+    
+    // ==================== NavigationCallback 接口实现 ====================
+    
+    override fun navigateToMain() {
+        startActivity(Intent(this, VehicleActivity::class.java))
+        finish()
+    }
+    
+    override fun navigateToLogin() {
+        startActivity(Intent(this, com.narutohuo.xindazhou.user.ui.login.LoginActivity::class.java))
+        finish()
+    }
+    
+    override fun navigateToRegister() {
+        // 当前已在注册页,不需要跳转
+    }
+}
+

+ 3 - 3
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt

@@ -3,9 +3,9 @@ package com.narutohuo.xindazhou.user.ui.viewmodel
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.narutohuo.xindazhou.common.socketio.SocketIOManager
-import com.narutohuo.xindazhou.common.socketio.model.SocketIOResponse
-import com.narutohuo.xindazhou.common.socketio.model.SocketIOEvent
+import com.narutohuo.xindazhou.socketio.SocketIOManager
+import com.narutohuo.xindazhou.socketio.model.SocketIOResponse
+import com.narutohuo.xindazhou.socketio.model.SocketIOEvent
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 269 - 234
app/src/main/java/com/narutohuo/xindazhou/vehicle/ui/VehicleFragment.kt


+ 31 - 0
app/src/main/res/layout/activity_community.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- 内容区域 -->
+    <FrameLayout
+        android:id="@+id/contentContainer"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 底部导航栏 -->
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottomNavView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/windowBackground"
+        app:itemIconTint="@android:color/black"
+        app:itemTextColor="@android:color/black"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_navigation_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 1 - 1
app/src/main/res/layout/fragment_login.xml

@@ -6,7 +6,7 @@
     android:layout_height="match_parent"
     android:padding="24dp"
     android:background="@android:color/white"
-    tools:context=".user.ui.login.LoginFragment">
+    tools:context=".user.ui.login.LoginActivity">
 
     <!-- Logo 或应用名称 -->
     <TextView

+ 5 - 12
app/src/main/res/layout/activity_main.xml

@@ -6,18 +6,11 @@
     android:layout_height="match_parent"
     tools:context=".MainActivity">
 
-    <!-- 主界面 Navigation Fragment容器 -->
-    <!-- 包含 MainFragment,MainFragment 内部使用底部导航栏管理各个业务模块 -->
+    <!-- MainFragment 容器(主页内容,包含底部导航栏) -->
     <androidx.fragment.app.FragmentContainerView
-        android:id="@+id/navHostFragment"
-        android:name="androidx.navigation.fragment.NavHostFragment"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        app:defaultNavHost="true"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:navGraph="@navigation/nav_graph" />
+        android:id="@+id/fragmentContainer"
+        android:name="com.narutohuo.xindazhou.MainFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 1
app/src/main/res/layout/fragment_register.xml

@@ -6,7 +6,7 @@
     android:layout_height="match_parent"
     android:padding="24dp"
     android:background="@android:color/white"
-    tools:context=".user.ui.register.RegisterFragment">
+    tools:context=".user.ui.register.RegisterActivity">
 
     <!-- 标题 -->
     <TextView

+ 31 - 0
app/src/main/res/layout/activity_service.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- 内容区域 -->
+    <FrameLayout
+        android:id="@+id/contentContainer"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 底部导航栏 -->
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottomNavView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/windowBackground"
+        app:itemIconTint="@android:color/black"
+        app:itemTextColor="@android:color/black"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_navigation_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 32 - 0
app/src/main/res/layout/activity_shop.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- 内容区域 -->
+    <FrameLayout
+        android:id="@+id/contentContainer"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 底部导航栏 -->
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottomNavView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/windowBackground"
+        app:itemIconTint="@android:color/black"
+        app:itemTextColor="@android:color/black"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_navigation_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 32 - 0
app/src/main/res/layout/activity_user.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- 内容区域 -->
+    <include
+        android:id="@+id/contentLayout"
+        layout="@layout/fragment_user_socket_test"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 底部导航栏 -->
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottomNavView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/windowBackground"
+        app:itemIconTint="@android:color/black"
+        app:itemTextColor="@android:color/black"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_navigation_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 744 - 0
app/src/main/res/layout/activity_vehicle.xml

@@ -0,0 +1,744 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- 车辆控制内容(使用 fragment_vehicle.xml 的内容) -->
+    <androidx.core.widget.NestedScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:fillViewport="true"
+        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:padding="16dp">
+
+            <!-- 蓝牙连接状态 -->
+            <androidx.cardview.widget.CardView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                app:cardCornerRadius="8dp"
+                app:cardElevation="4dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:padding="16dp"
+                    android:gravity="center_vertical">
+
+                    <TextView
+                        android:id="@+id/tvBluetoothStatus"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:text="蓝牙未连接"
+                        android:textSize="16sp"
+                        android:textStyle="bold" />
+
+                    <Button
+                        android:id="@+id/btnConnectBluetooth"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="连接" />
+                </LinearLayout>
+            </androidx.cardview.widget.CardView>
+
+            <!-- 实时状态信息 -->
+            <androidx.cardview.widget.CardView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                app:cardCornerRadius="8dp"
+                app:cardElevation="4dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:padding="16dp">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="实时状态"
+                        android:textSize="18sp"
+                        android:textStyle="bold"
+                        android:layout_marginBottom="12dp" />
+
+                    <GridLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:columnCount="2"
+                        android:rowCount="4">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="设防状态:"
+                            android:layout_marginEnd="8dp" />
+                        <TextView
+                            android:id="@+id/tvDefenseStatus"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="未知" />
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="上电状态:"
+                            android:layout_marginEnd="8dp" />
+                        <TextView
+                            android:id="@+id/tvPowerStatus"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="未知" />
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="门锁状态:"
+                            android:layout_marginEnd="8dp" />
+                        <TextView
+                            android:id="@+id/tvDoorLockStatus"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="未知" />
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="电池电量:"
+                            android:layout_marginEnd="8dp" />
+                        <TextView
+                            android:id="@+id/tvBatteryLevel"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_columnWeight="1"
+                            android:text="未知" />
+                    </GridLayout>
+
+                    <Button
+                        android:id="@+id/btnQueryVehicleInfo"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="12dp"
+                        android:text="查询车辆信息" />
+                </LinearLayout>
+            </androidx.cardview.widget.CardView>
+
+            <!-- 快捷控制 -->
+            <androidx.cardview.widget.CardView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                app:cardCornerRadius="8dp"
+                app:cardElevation="4dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:padding="16dp">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="快捷控制"
+                        android:textSize="18sp"
+                        android:textStyle="bold"
+                        android:layout_marginBottom="12dp" />
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Button
+                            android:id="@+id/btnDefense"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:layout_marginEnd="8dp"
+                            android:text="设防" />
+
+                        <Button
+                            android:id="@+id/btnPower"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:layout_marginEnd="8dp"
+                            android:text="上电" />
+
+                        <Button
+                            android:id="@+id/btnFindCar"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="寻车" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal">
+
+                        <Button
+                            android:id="@+id/btnSeatLock"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:layout_marginEnd="8dp"
+                            android:text="座桶锁" />
+
+                        <Button
+                            android:id="@+id/btnHandlebarLock"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:layout_marginEnd="8dp"
+                            android:text="龙头锁" />
+
+                        <Button
+                            android:id="@+id/btnTrunkLock"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="尾箱锁" />
+                    </LinearLayout>
+                </LinearLayout>
+            </androidx.cardview.widget.CardView>
+
+            <!-- 系统控制 -->
+            <androidx.cardview.widget.CardView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                app:cardCornerRadius="8dp"
+                app:cardElevation="4dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:padding="16dp">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="系统控制"
+                        android:textSize="18sp"
+                        android:textStyle="bold"
+                        android:layout_marginBottom="12dp" />
+
+                    <!-- 开关类控制 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Switch
+                            android:id="@+id/switchInductionUnlock"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="感应解锁" />
+
+                        <Switch
+                            android:id="@+id/switchTheftLock"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="被盗锁定" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Switch
+                            android:id="@+id/switchSeatSensor"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="座椅感应" />
+
+                        <Switch
+                            android:id="@+id/switchVehicleSound"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="车辆音效" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Switch
+                            android:id="@+id/switchAutoHeadlight"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="自动大灯" />
+
+                        <Switch
+                            android:id="@+id/switchAtmosphereLight"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="氛围灯" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Switch
+                            android:id="@+id/switchVoiceBroadcast"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="语音播报" />
+
+                        <Switch
+                            android:id="@+id/switchFollowMeHome"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="伴我回家" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp">
+
+                        <Switch
+                            android:id="@+id/switchABS"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="ABS" />
+
+                        <Switch
+                            android:id="@+id/switchTCS"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="TCS" />
+                    </LinearLayout>
+
+                    <!-- 时间设置 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="自动下电时间(秒):" />
+
+                        <EditText
+                            android:id="@+id/etAutoPowerOffTime"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="300" />
+
+                        <Button
+                            android:id="@+id/btnSetAutoPowerOffTime"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="自动设防时间(秒):" />
+
+                        <EditText
+                            android:id="@+id/etAutoDefenseTime"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="5" />
+
+                        <Button
+                            android:id="@+id/btnSetAutoDefenseTime"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="进入P档时间(秒):" />
+
+                        <EditText
+                            android:id="@+id/etEnterPTime"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="300" />
+
+                        <Button
+                            android:id="@+id/btnSetEnterPTime"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <!-- 其他设置 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="蓝牙音箱音量(%):" />
+
+                        <EditText
+                            android:id="@+id/etBluetoothSpeakerVolume"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="50" />
+
+                        <Button
+                            android:id="@+id/btnSetBluetoothSpeakerVolume"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="低电量报警阈值(%):" />
+
+                        <EditText
+                            android:id="@+id/etLowBatteryThreshold"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="20" />
+
+                        <Button
+                            android:id="@+id/btnSetLowBatteryThreshold"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <!-- 氛围灯颜色设置 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="氛围灯颜色:" />
+
+                        <EditText
+                            android:id="@+id/etAtmosphereLightR"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="R(0-255)"
+                            android:layout_marginEnd="4dp" />
+
+                        <EditText
+                            android:id="@+id/etAtmosphereLightG"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="G(0-255)"
+                            android:layout_marginEnd="4dp" />
+
+                        <EditText
+                            android:id="@+id/etAtmosphereLightB"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:hint="B(0-255)" />
+
+                        <Button
+                            android:id="@+id/btnSetAtmosphereLightColor"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <!-- 感应解锁RSSI信号强度采集 -->
+                    <Button
+                        android:id="@+id/btnCollectRSSI"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginBottom="8dp"
+                        android:text="感应解锁RSSI信号强度采集" />
+
+                    <!-- 小计里程清零 -->
+                    <Button
+                        android:id="@+id/btnResetTripMileage"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="小计里程清零" />
+                </LinearLayout>
+            </androidx.cardview.widget.CardView>
+
+            <!-- 系统设置 -->
+            <androidx.cardview.widget.CardView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                app:cardCornerRadius="8dp"
+                app:cardElevation="4dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:padding="16dp">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="系统设置"
+                        android:textSize="18sp"
+                        android:textStyle="bold"
+                        android:layout_marginBottom="12dp" />
+
+                    <!-- 钥匙学码 -->
+                    <Button
+                        android:id="@+id/btnKeyLearning"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginBottom="8dp"
+                        android:text="钥匙学码" />
+
+                    <!-- 震动灵敏度 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="震动灵敏度:" />
+
+                        <RadioGroup
+                            android:id="@+id/rgVibrationSensitivity"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="2"
+                            android:orientation="horizontal">
+
+                            <RadioButton
+                                android:id="@+id/rbSensitivity1"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="1" />
+
+                            <RadioButton
+                                android:id="@+id/rbSensitivity2"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="2" />
+
+                            <RadioButton
+                                android:id="@+id/rbSensitivity3"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="3" />
+
+                            <RadioButton
+                                android:id="@+id/rbSensitivity4"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="4" />
+
+                            <RadioButton
+                                android:id="@+id/rbSensitivity5"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="5" />
+                        </RadioGroup>
+
+                        <Button
+                            android:id="@+id/btnSetVibrationSensitivity"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+
+                    <!-- 报警开关 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="8dp"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="报警是否上报:" />
+
+                        <Switch
+                            android:id="@+id/switchAlarmReport"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1" />
+                    </LinearLayout>
+
+                    <!-- 倾倒报警阈值 -->
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal"
+                        android:gravity="center_vertical">
+
+                        <TextView
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:text="倾倒报警阈值:" />
+
+                        <RadioGroup
+                            android:id="@+id/rgTiltAlarmThreshold"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="2"
+                            android:orientation="horizontal">
+
+                            <RadioButton
+                                android:id="@+id/rbTilt50"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="50°" />
+
+                            <RadioButton
+                                android:id="@+id/rbTilt60"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="60°" />
+
+                            <RadioButton
+                                android:id="@+id/rbTilt70"
+                                android:layout_width="0dp"
+                                android:layout_height="wrap_content"
+                                android:layout_weight="1"
+                                android:text="70°" />
+                        </RadioGroup>
+
+                        <Button
+                            android:id="@+id/btnSetTiltAlarmThreshold"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="设置" />
+                    </LinearLayout>
+                </LinearLayout>
+            </androidx.cardview.widget.CardView>
+
+        </LinearLayout>
+    </androidx.core.widget.NestedScrollView>
+
+    <!-- 底部导航栏 -->
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/bottomNavView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/windowBackground"
+        app:itemIconTint="@android:color/black"
+        app:itemTextColor="@android:color/black"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_navigation_menu" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 1 - 14
app/src/main/res/layout/fragment_main.xml

@@ -4,23 +4,10 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <!-- Fragment容器,用于显示各个业务模块的Fragment -->
-    <androidx.fragment.app.FragmentContainerView
-        android:id="@+id/navHostFragment"
-        android:name="androidx.navigation.fragment.NavHostFragment"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        app:layout_constraintBottom_toTopOf="@+id/bottomNavView"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:defaultNavHost="true"
-        app:navGraph="@navigation/main_nav_graph" />
-
     <!-- 底部导航栏 -->
     <com.google.android.material.bottomnavigation.BottomNavigationView
         android:id="@+id/bottomNavView"
-        android:layout_width="0dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="?android:attr/windowBackground"
         app:itemIconTint="@android:color/black"

+ 6 - 2
app/src/main/res/values/themes.xml

@@ -1,13 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <style name="Theme.XinDaZhou" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <!-- 全屏主题,无 ActionBar -->
+    <style name="Theme.XinDaZhou" parent="Theme.MaterialComponents.DayNight.NoActionBar">
         <item name="colorPrimary">@android:color/black</item>
         <item name="colorPrimaryVariant">@android:color/black</item>
         <item name="colorOnPrimary">@android:color/white</item>
         <item name="colorSecondary">@android:color/black</item>
         <item name="colorSecondaryVariant">@android:color/black</item>
         <item name="colorOnSecondary">@android:color/white</item>
-        <item name="android:statusBarColor">@android:color/black</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
     </style>
 </resources>
 

+ 7 - 0
app/src/main/res/xml/logcat_filepaths.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- LogcatViewer 文件路径配置 -->
+    <external-path name="external_files" path="." />
+    <cache-path name="cache_files" path="." />
+</paths>
+

+ 3 - 5
base-common/build.gradle

@@ -48,11 +48,9 @@ dependencies {
     implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
     implementation("androidx.lifecycle:lifecycle-process:2.7.0")
     
-    // SocketIO 客户端库
-    // 使用 2.1.2 版本,支持 Socket.IO 2.x/3.x(服务器端使用 socketio4j 3.0.1,支持 Engine.IO v1-v3)
-    implementation("io.socket:socket.io-client:2.1.2")
+    // 注意:SocketIO 已移动到 capability-socketio 模块,不再在此处依赖
     
-    // 注意:Gson、Retrofit、OkHttp、Glide、Coroutines、Timber 已通过 base-core 传递,无需重复依赖
-    // 日志通过 base-core 的 ILog 接口统一管理,无需单独依赖 Timber
+    // 注意:Gson、Retrofit、OkHttp、Glide、Coroutines 已通过 base-core 传递,无需重复依赖
+    // 日志通过 base-core 的 ILog 接口统一管理
 }
 

+ 33 - 34
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt

@@ -10,9 +10,10 @@ import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourc
 import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
 import com.narutohuo.xindazhou.common.auth.storage.TokenStore
 import com.narutohuo.xindazhou.common.auth.utils.JWTUtil
-import com.narutohuo.xindazhou.common.log.LogHelper
-import com.narutohuo.xindazhou.common.network.NetworkHelper
-import com.narutohuo.xindazhou.common.socketio.SocketIOManager
+import com.narutohuo.xindazhou.core.log.ILog
+import com.narutohuo.xindazhou.common.network.ApiManager
+// SocketIOManager 已移动到 capability-socketio,不再直接依赖
+// 业务层可以通过回调来处理 SocketIO 连接
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -69,12 +70,12 @@ object AuthManager {
         this.localDataSource = defaultLocalDataSource
         this.repository = AuthRepository(remoteDataSource, defaultLocalDataSource)
         
-        // 配置 NetworkHelper 的 Token Provider(使用 TokenStore 的同步方法)
-        NetworkHelper.setTokenProvider {
+        // 配置 ApiManager 的 Token Provider(使用 TokenStore 的同步方法)
+        ApiManager.setTokenProvider {
             TokenStore.getAccessToken()
         }
         
-        LogHelper.d("AuthManager", "认证管理器初始化完成,Token Provider 已配置")
+        ILog.d("AuthManager", "认证管理器初始化完成,Token Provider 已配置")
     }
     
     /**
@@ -89,12 +90,12 @@ object AuthManager {
         this.localDataSource = localDataSource
         this.repository = AuthRepository(remoteDataSource, localDataSource)
         
-        // 配置 NetworkHelper 的 Token Provider(使用 TokenStore 的同步方法)
-        NetworkHelper.setTokenProvider {
+        // 配置 ApiManager 的 Token Provider(使用 TokenStore 的同步方法)
+        ApiManager.setTokenProvider {
             TokenStore.getAccessToken()
         }
         
-        LogHelper.d("AuthManager", "认证管理器初始化完成(自定义数据源),Token Provider 已配置")
+        ILog.d("AuthManager", "认证管理器初始化完成(自定义数据源),Token Provider 已配置")
     }
     
     /**
@@ -114,22 +115,21 @@ object AuthManager {
         activity.lifecycleScope.launch {
             try {
                 val repo = repository ?: run {
-                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
                     onResult(Result.failure(Exception("AuthManager 未初始化")))
                     return@launch
                 }
                 
                 val result = repo.login(mobile, password)
                 
-                // 登录成功后,自动触发 Socket.IO 连接
+                // 登录成功后,自动触发 Socket.IO 连接(由业务层通过回调处理)
                 result.onSuccess {
-                    LogHelper.d("AuthManager", "登录成功,自动触发 Socket.IO 连接")
-                    SocketIOManager.ensureConnected()
+                    ILog.d("AuthManager", "登录成功,业务层应调用 SocketIOManager.ensureConnected()")
                 }
                 
                 onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
             } catch (e: Exception) {
-                LogHelper.e("AuthManager", "登录异常", e)
+                ILog.e("AuthManager", "登录异常", e)
                 onResult(Result.failure(e))
             }
         }
@@ -152,22 +152,21 @@ object AuthManager {
         activity.lifecycleScope.launch {
             try {
                 val repo = repository ?: run {
-                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
                     onResult(Result.failure(Exception("AuthManager 未初始化")))
                     return@launch
                 }
                 
                 val result = repo.register(mobile, password)
                 
-                // 注册成功后,自动触发 Socket.IO 连接
+                // 注册成功后,自动触发 Socket.IO 连接(由业务层通过回调处理)
                 result.onSuccess {
-                    LogHelper.d("AuthManager", "注册成功,自动触发 Socket.IO 连接")
-                    SocketIOManager.ensureConnected()
+                    ILog.d("AuthManager", "注册成功,业务层应调用 SocketIOManager.ensureConnected()")
                 }
                 
                 onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
             } catch (e: Exception) {
-                LogHelper.e("AuthManager", "注册异常", e)
+                ILog.e("AuthManager", "注册异常", e)
                 onResult(Result.failure(e))
             }
         }
@@ -188,7 +187,7 @@ object AuthManager {
         activity.lifecycleScope.launch {
             try {
                 val repo = repository ?: run {
-                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
                     onResult(Result.failure(Exception("AuthManager 未初始化")))
                     return@launch
                 }
@@ -196,7 +195,7 @@ object AuthManager {
                 val result = repo.refreshToken(refreshToken)
                 onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
             } catch (e: Exception) {
-                LogHelper.e("AuthManager", "Token 刷新异常", e)
+                ILog.e("AuthManager", "Token 刷新异常", e)
                 onResult(Result.failure(e))
             }
         }
@@ -234,7 +233,7 @@ object AuthManager {
             val refreshToken = TokenStore.getRefreshToken()
             if (!refreshToken.isNullOrEmpty()) {
                 // 有 Refresh Token,尝试刷新 Access Token
-                LogHelper.d("AuthManager", "Access Token 不存在,尝试使用 Refresh Token 刷新...")
+                ILog.d("AuthManager", "Access Token 不存在,尝试使用 Refresh Token 刷新...")
                 val newToken = refreshTokenIfNeeded()
                 return !newToken.isNullOrEmpty()
             }
@@ -248,20 +247,20 @@ object AuthManager {
             val refreshToken = TokenStore.getRefreshToken()
             if (!refreshToken.isNullOrEmpty()) {
                 // 有 Refresh Token,尝试刷新 Access Token
-                LogHelper.d("AuthManager", "Access Token 已过期,尝试使用 Refresh Token 刷新...")
+                ILog.d("AuthManager", "Access Token 已过期,尝试使用 Refresh Token 刷新...")
                 val newToken = refreshTokenIfNeeded()
                 
                 // 如果刷新成功,验证新 Token 是否有效
                 if (!newToken.isNullOrEmpty() && JWTUtil.isTokenValid(newToken)) {
-                    LogHelper.d("AuthManager", "Access Token 刷新成功,用户仍然处于登录状态")
+                    ILog.d("AuthManager", "Access Token 刷新成功,用户仍然处于登录状态")
                     return true
                 } else {
-                    LogHelper.w("AuthManager", "Access Token 刷新失败或新 Token 无效,用户需要重新登录")
+                    ILog.w("AuthManager", "Access Token 刷新失败或新 Token 无效,用户需要重新登录")
                     return false
                 }
             } else {
                 // 没有 Refresh Token,返回 false
-                LogHelper.w("AuthManager", "Access Token 已过期且没有 Refresh Token,用户需要重新登录")
+                ILog.w("AuthManager", "Access Token 已过期且没有 Refresh Token,用户需要重新登录")
                 return false
             }
         }
@@ -303,7 +302,7 @@ object AuthManager {
     ): String? {
         val refreshToken = TokenStore.getRefreshToken()
         if (refreshToken.isNullOrEmpty()) {
-            LogHelper.w("AuthManager", "RefreshToken 为空,无法刷新")
+            ILog.w("AuthManager", "RefreshToken 为空,无法刷新")
             onResult?.invoke(Result.failure(Exception("RefreshToken 为空")))
             return null
         }
@@ -311,7 +310,7 @@ object AuthManager {
         return try {
             val repo = repository ?: run {
                 val error = Exception("AuthManager 未初始化")
-                LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
                 onResult?.invoke(Result.failure(error))
                 return null
             }
@@ -320,21 +319,21 @@ object AuthManager {
             
             result.getOrNull()?.let { loginResponse ->
                 // Token 已由 AuthRepository 自动保存
-                LogHelper.d("AuthManager", "Token 刷新成功")
+                ILog.d("AuthManager", "Token 刷新成功")
                 
-                // Token 刷新成功后,确保 Socket.IO 使用新 Token 重连
-                SocketIOManager.ensureConnected()
+                // Token 刷新成功后,业务层应调用 SocketIOManager.ensureConnected() 确保 Socket.IO 使用新 Token 重连
+                ILog.d("AuthManager", "Token 刷新成功,业务层应调用 SocketIOManager.ensureConnected()")
                 
                 onResult?.invoke(Result.success(loginResponse.accessToken))
                 loginResponse.accessToken
             } ?: run {
                 val error = result.exceptionOrNull() ?: Exception("Token 刷新失败")
-                LogHelper.w("AuthManager", "Token 刷新失败: ${error.message}")
+                ILog.w("AuthManager", "Token 刷新失败: ${error.message}")
                 onResult?.invoke(Result.failure(error))
                 null
             }
         } catch (e: Exception) {
-            LogHelper.e("AuthManager", "Token 刷新异常", e)
+            ILog.e("AuthManager", "Token 刷新异常", e)
             onResult?.invoke(Result.failure(e))
             null
         }
@@ -362,7 +361,7 @@ object AuthManager {
      */
     fun logout() {
         TokenStore.clearToken()
-        LogHelper.d("AuthManager", "用户已登出")
+        ILog.d("AuthManager", "用户已登出")
     }
 }
 

+ 4 - 4
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt

@@ -5,7 +5,7 @@ import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourc
 import com.narutohuo.xindazhou.common.auth.model.LoginRequest
 import com.narutohuo.xindazhou.common.auth.model.LoginResponse
 import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.network.ApiBaseRepository
 
 /**
@@ -39,7 +39,7 @@ class AuthRepository(
         // 如果成功,保存到本地
         result.onSuccess { response ->
             localDataSource.saveToken(response)
-            LogHelper.d("AuthRepository", "login: Token已保存")
+            ILog.d("AuthRepository", "login: Token已保存")
         }
         
         // 如果失败,可以在这里添加额外的错误处理逻辑
@@ -67,7 +67,7 @@ class AuthRepository(
         // 如果成功,保存到本地
         result.onSuccess { response ->
             localDataSource.saveToken(response)
-            LogHelper.d("AuthRepository", "register: Token已保存")
+            ILog.d("AuthRepository", "register: Token已保存")
         }
         
         // 如果失败,可以在这里添加额外的错误处理逻辑
@@ -91,7 +91,7 @@ class AuthRepository(
         // 如果成功,保存到本地
         result.onSuccess { response ->
             localDataSource.saveToken(response)
-            LogHelper.d("AuthRepository", "refreshToken: Token已刷新并保存")
+            ILog.d("AuthRepository", "refreshToken: Token已刷新并保存")
         }
         
         // 如果失败,可以在这里添加额外的错误处理逻辑

+ 0 - 104
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/LoginFragment.kt

@@ -1,104 +0,0 @@
-package com.narutohuo.xindazhou.common.auth.ui
-
-import android.os.Bundle
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import com.google.android.material.button.MaterialButton
-import com.google.android.material.textfield.TextInputEditText
-import com.narutohuo.xindazhou.common.R
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
-import com.narutohuo.xindazhou.common.dialog.ServerConfigDialog
-import kotlinx.coroutines.launch
-import androidx.lifecycle.lifecycleScope
-
-/**
- * 登录 Fragment(完整封装)
- * 
- * 使用单 Activity 架构,通过 NavigationCallback 接口触发导航
- */
-class LoginFragment : Fragment() {
-    
-    private lateinit var etMobile: TextInputEditText
-    private lateinit var etPassword: TextInputEditText
-    private lateinit var btnLogin: MaterialButton
-    private lateinit var btnRegister: MaterialButton
-    private lateinit var progressBar: View
-    // 【测试功能】服务器配置按钮
-    private var ivServerConfig: View? = null
-    
-    private val navigationCallback: NavigationCallback?
-        get() = activity as? NavigationCallback
-    
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_login, container, false)
-    }
-    
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        
-        etMobile = view.findViewById(R.id.etMobile)
-        etPassword = view.findViewById(R.id.etPassword)
-        btnLogin = view.findViewById(R.id.btnLogin)
-        btnRegister = view.findViewById(R.id.btnRegister)
-        progressBar = view.findViewById(R.id.progressBar)
-        ivServerConfig = view.findViewById(R.id.ivServerConfig)
-        
-        // 【测试功能】服务器配置按钮
-        ivServerConfig?.setOnClickListener {
-            val dialog = ServerConfigDialog()
-            dialog.show(parentFragmentManager, "ServerConfigDialog")
-        }
-        
-        // 登录按钮
-        btnLogin.setOnClickListener {
-            val mobile = etMobile.text?.toString()?.trim() ?: ""
-            val password = etPassword.text?.toString() ?: ""
-            
-            if (TextUtils.isEmpty(mobile)) {
-                Toast.makeText(requireContext(), "请输入手机号", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (TextUtils.isEmpty(password)) {
-                Toast.makeText(requireContext(), "请输入密码", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            // 显示加载状态
-            progressBar.visibility = View.VISIBLE
-            btnLogin.isEnabled = false
-            
-            // 调用 AuthManager 登录
-            lifecycleScope.launch {
-                AuthManager.login(requireActivity(), mobile, password) { result ->
-                    progressBar.visibility = View.GONE
-                    btnLogin.isEnabled = true
-                    
-                    result.onSuccess {
-                        Toast.makeText(requireContext(), "登录成功", Toast.LENGTH_SHORT).show()
-                        // 登录成功后,通过接口回调触发导航到主界面
-                        navigationCallback?.navigateToMain()
-                    }.onFailure { error ->
-                        Toast.makeText(requireContext(), error.message ?: "登录失败", Toast.LENGTH_SHORT).show()
-                    }
-                }
-            }
-        }
-        
-        // 注册按钮
-        btnRegister.setOnClickListener {
-            // 通过接口回调触发导航到注册页
-            navigationCallback?.navigateToRegister()
-        }
-    }
-}
-

+ 0 - 107
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/RegisterFragment.kt

@@ -1,107 +0,0 @@
-package com.narutohuo.xindazhou.common.auth.ui
-
-import android.os.Bundle
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.button.MaterialButton
-import com.google.android.material.textfield.TextInputEditText
-import com.narutohuo.xindazhou.common.R
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
-import kotlinx.coroutines.launch
-
-/**
- * 注册 Fragment(完整封装)
- * 
- * 使用单 Activity 架构,通过 NavigationCallback 接口触发导航
- */
-class RegisterFragment : Fragment() {
-    
-    private lateinit var etMobile: TextInputEditText
-    private lateinit var etPassword: TextInputEditText
-    private lateinit var etConfirmPassword: TextInputEditText
-    private lateinit var btnRegister: MaterialButton
-    private lateinit var btnBackToLogin: MaterialButton
-    private lateinit var progressBar: View
-    
-    private val navigationCallback: NavigationCallback?
-        get() = activity as? NavigationCallback
-    
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.fragment_register, container, false)
-    }
-    
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        
-        etMobile = view.findViewById(R.id.etMobile)
-        etPassword = view.findViewById(R.id.etPassword)
-        etConfirmPassword = view.findViewById(R.id.etConfirmPassword)
-        btnRegister = view.findViewById(R.id.btnRegister)
-        btnBackToLogin = view.findViewById(R.id.btnBackToLogin)
-        progressBar = view.findViewById(R.id.progressBar)
-        
-        // 注册按钮
-        btnRegister.setOnClickListener {
-            val mobile = etMobile.text?.toString()?.trim() ?: ""
-            val password = etPassword.text?.toString() ?: ""
-            val confirmPassword = etConfirmPassword.text?.toString() ?: ""
-            
-            if (TextUtils.isEmpty(mobile)) {
-                Toast.makeText(requireContext(), "请输入手机号", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (TextUtils.isEmpty(password)) {
-                Toast.makeText(requireContext(), "请输入密码", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (password.length < 6 || password.length > 16) {
-                Toast.makeText(requireContext(), "密码长度为6-16位", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (password != confirmPassword) {
-                Toast.makeText(requireContext(), "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            // 显示加载状态
-            progressBar.visibility = View.VISIBLE
-            btnRegister.isEnabled = false
-            
-            // 调用 AuthManager 注册
-            lifecycleScope.launch {
-                AuthManager.register(requireActivity(), mobile, password) { result ->
-                    progressBar.visibility = View.GONE
-                    btnRegister.isEnabled = true
-                    
-                    result.onSuccess {
-                        Toast.makeText(requireContext(), "注册成功,请登录", Toast.LENGTH_SHORT).show()
-                        // 注册成功后,通过接口回调触发导航到登录页
-                        navigationCallback?.navigateToLogin()
-                    }.onFailure { error ->
-                        Toast.makeText(requireContext(), error.message ?: "注册失败", Toast.LENGTH_SHORT).show()
-                    }
-                }
-            }
-        }
-        
-        // 返回登录按钮
-        btnBackToLogin.setOnClickListener {
-            // 通过接口回调触发导航到登录页
-            navigationCallback?.navigateToLogin()
-        }
-    }
-}
-

+ 7 - 7
base-common/src/main/java/com/narutohuo/xindazhou/common/image/ImageLoader.kt

@@ -12,29 +12,29 @@ import com.bumptech.glide.request.target.CustomTarget
 import com.bumptech.glide.request.transition.Transition
 
 /**
- * 图片加载管理器
+ * 图片加载工具类
  * 
  * 统一封装 Glide 图片加载,提供便捷的图片加载方式
  * 
  * 使用方式:
  * ```kotlin
  * // 基础加载
- * ImageLoader.load("https://example.com/image.jpg", imageView)
+ * ImageLoadHelper.load("https://example.com/image.jpg", imageView)
  * 
  * // 带占位图
- * ImageLoader.load("https://example.com/image.jpg", imageView, R.drawable.placeholder)
+ * ImageLoadHelper.load("https://example.com/image.jpg", imageView, R.drawable.placeholder)
  * 
  * // 圆形图片
- * ImageLoader.loadCircle("https://example.com/avatar.jpg", imageView)
+ * ImageLoadHelper.loadCircle("https://example.com/avatar.jpg", imageView)
  * 
  * // 圆角图片
- * ImageLoader.loadRound("https://example.com/image.jpg", imageView, 10)
+ * ImageLoadHelper.loadRound("https://example.com/image.jpg", imageView, 10)
  * 
  * // 清除缓存
- * ImageLoader.clearCache(context)
+ * ImageLoadHelper.clearCache(context)
  * ```
  */
-object ImageLoader {
+object ImageLoadHelper {
     
     /**
      * 默认占位图资源ID(可在 Application 中设置)

+ 5 - 5
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt

@@ -3,7 +3,7 @@ package com.narutohuo.xindazhou.common.launch
 import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.lifecycleScope
 import com.narutohuo.xindazhou.common.auth.NavigationCallback
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
@@ -52,20 +52,20 @@ object AppLaunchManager {
                 // 根据登录状态导航(支持异步刷新 Token)
                 if (isLoggedIn()) {
                     // 已登录,导航到主界面
-                    LogHelper.d("AppLaunchManager", "用户已登录,导航到主界面")
+                    ILog.d("AppLaunchManager", "用户已登录,导航到主界面")
                     navigationCallback.navigateToMain()
                 } else {
                     // 未登录,导航到登录页
-                    LogHelper.d("AppLaunchManager", "用户未登录,导航到登录页")
+                    ILog.d("AppLaunchManager", "用户未登录,导航到登录页")
                     navigationCallback.navigateToLogin()
                 }
             } catch (e: Exception) {
-                LogHelper.e("AppLaunchManager", "启动处理失败", e)
+                ILog.e("AppLaunchManager", "启动处理失败", e)
                 // 出错时默认导航到登录页
                 try {
                     navigationCallback.navigateToLogin()
                 } catch (ex: Exception) {
-                    LogHelper.e("AppLaunchManager", "导航到登录页失败", ex)
+                    ILog.e("AppLaunchManager", "导航到登录页失败", ex)
                 }
             }
         }

+ 0 - 118
base-common/src/main/java/com/narutohuo/xindazhou/common/log/LogHelper.kt

@@ -1,118 +0,0 @@
-package com.narutohuo.xindazhou.common.log
-
-import com.narutohuo.xindazhou.core.log.ILog as CoreILog
-import com.narutohuo.xindazhou.core.log.LogLevel
-
-/**
- * 日志工具类封装
- * 
- * 业务层统一使用此类进行日志记录,不直接使用 base-core 的 ILog
- * 
- * **使用示例**:
- * ```kotlin
- * // 调试日志
- * LogHelper.d("Tag", "调试信息")
- * 
- * // 信息日志
- * LogHelper.i("Tag", "一般信息")
- * 
- * // 警告日志
- * LogHelper.w("Tag", "警告信息")
- * 
- * // 错误日志
- * LogHelper.e("Tag", "错误信息", exception)
- * 
- * // 上传日志文件
- * LogHelper.uploadLogs()
- * 
- * // 收集崩溃信息
- * LogHelper.collectCrash(throwable)
- * ```
- */
-object LogHelper {
-    /**
-     * 初始化日志实现(在 Application 中调用,可选)
-     * 
-     * @param enableLogging 是否启用日志(默认 true)
-     */
-    @JvmStatic
-    fun init(enableLogging: Boolean = true) {
-        CoreILog.init(enableLogging = enableLogging)
-    }
-    
-    /**
-     * 调试日志
-     * 
-     * @param tag 标签
-     * @param message 消息
-     */
-    @JvmStatic
-    fun d(tag: String, message: String) {
-        CoreILog.d(tag, message)
-    }
-    
-    /**
-     * 信息日志
-     * 
-     * @param tag 标签
-     * @param message 消息
-     */
-    @JvmStatic
-    fun i(tag: String, message: String) {
-        CoreILog.i(tag, message)
-    }
-    
-    /**
-     * 警告日志
-     * 
-     * @param tag 标签
-     * @param message 消息
-     */
-    @JvmStatic
-    fun w(tag: String, message: String) {
-        CoreILog.w(tag, message)
-    }
-    
-    /**
-     * 错误日志
-     * 
-     * @param tag 标签
-     * @param message 消息
-     * @param throwable 异常(可选)
-     */
-    @JvmStatic
-    fun e(tag: String, message: String, throwable: Throwable? = null) {
-        CoreILog.e(tag, message, throwable)
-    }
-    
-    /**
-     * 按级别记录日志
-     * 
-     * @param level 日志级别
-     * @param tag 标签
-     * @param message 消息
-     * @param throwable 异常(可选)
-     */
-    @JvmStatic
-    fun log(level: LogLevel, tag: String, message: String, throwable: Throwable? = null) {
-        CoreILog.log(level, tag, message, throwable)
-    }
-    
-    /**
-     * 上传日志文件
-     */
-    @JvmStatic
-    fun uploadLogs() {
-        CoreILog.uploadLogs()
-    }
-    
-    /**
-     * 收集崩溃信息
-     * 
-     * @param throwable 异常
-     */
-    @JvmStatic
-    fun collectCrash(throwable: Throwable) {
-        CoreILog.collectCrash(throwable)
-    }
-}

+ 46 - 12
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiManager.kt

@@ -1,7 +1,7 @@
 package com.narutohuo.xindazhou.common.network
 
 import com.narutohuo.xindazhou.common.config.ServerConfigManager
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.core.network.NetworkManager
 import retrofit2.Retrofit
 
@@ -9,20 +9,27 @@ import retrofit2.Retrofit
  * API 管理器
  * 
  * 统一创建和管理 API 接口实例,避免重复代码
+ * 整合了网络配置和 API 接口创建功能
  * 
  * 与 AuthManager、SocketIOManager 类似,提供统一的 API 封装
- * 自动处理配置初始化、URL 刷新等,外部只需要调用 create() 即可
+ * 自动处理配置初始化、Token 管理、URL 刷新等,外部只需要调用 create() 即可
  * 
  * 使用方式:
  * ```kotlin
  * // 在 AppInitializer 中初始化(自动从 ServerConfigManager 读取服务器地址)
  * ApiManager.initialize(application)
  * 
+ * // 设置 Token 提供器(可选,通常由 AuthManager 自动设置)
+ * ApiManager.setTokenProvider { TokenManager.getAccessToken() }
+ * 
  * // 然后就可以直接使用,无需再设置 baseUrl
  * val authApi = ApiManager.create<AuthApi>()
  * 
  * // 或者使用自定义 baseUrl
  * val authApi = ApiManager.create<AuthApi>("https://api.example.com")
+ * 
+ * // 重置网络管理器(切换服务器地址时)
+ * ApiManager.reset()
  * ```
  */
 object ApiManager {
@@ -50,14 +57,14 @@ object ApiManager {
     /**
      * 初始化 API 管理器(从存储读取服务器地址)
      * 
-     * 自动从 ServerConfigManager 读取服务器地址并缓存,同时初始化 NetworkHelper
+     * 自动从 ServerConfigManager 读取服务器地址并缓存,同时初始化底层网络管理器
      * 自动检测调试模式,所有参数使用默认值,业务层只需一行代码即可完成初始化
      * 
      * @param application Application 实例(用于检测调试模式)
      */
     fun initialize(application: android.app.Application) {
         if (isInitialized) {
-            LogHelper.d(TAG, "已初始化,跳过")
+            ILog.d(TAG, "已初始化,跳过")
             return
         }
         
@@ -76,19 +83,21 @@ object ApiManager {
             val debugField = buildConfigClass.getField("DEBUG")
             debugField.getBoolean(null)
         } catch (e: Exception) {
-            LogHelper.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
+            ILog.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
             false
         }
         
-        // 同时初始化 NetworkHelper(使用相同的 baseURL)
-        NetworkHelper.init(
-            baseUrl = serverURL,
-            isDebug = isDebug,
-            enableLogging = isDebug
+        // 初始化底层网络管理器(使用 base-core 的 NetworkManager)
+        NetworkManager.init(
+            com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
+                .baseUrl(serverURL)
+                .isDebug(isDebug)
+                .enableLogging(isDebug)
+                .build()
         )
         
         isInitialized = true
-        LogHelper.d(TAG, "初始化完成,baseURL: $serverURL, isDebug: $isDebug")
+        ILog.d(TAG, "初始化完成,baseURL: $serverURL, isDebug: $isDebug")
     }
     
     /**
@@ -99,7 +108,7 @@ object ApiManager {
     fun refreshBaseURL() {
         val serverURL = ServerConfigManager.getHttpServerUrl()
         cachedBaseURL = serverURL
-        LogHelper.d(TAG, "BaseURL 已刷新: $serverURL")
+        ILog.d(TAG, "BaseURL 已刷新: $serverURL")
     }
     
     /**
@@ -137,5 +146,30 @@ object ApiManager {
     inline fun <reified T> create(retrofit: Retrofit): T {
         return retrofit.create(T::class.java)
     }
+    
+    /**
+     * 设置 Token 提供器
+     * 
+     * 用于自动在请求头中添加 Authorization Token
+     * 通常由 AuthManager 自动调用,无需手动设置
+     * 
+     * @param tokenProvider Token 提供器,返回当前的 accessToken
+     */
+    fun setTokenProvider(tokenProvider: () -> String?) {
+        NetworkManager.tokenProvider = tokenProvider
+        ILog.d(TAG, "Token 提供器已设置")
+    }
+    
+    /**
+     * 重置网络管理器(用于切换服务器地址时)
+     * 
+     * 清空已缓存的 Retrofit 和 OkHttpClient 实例,下次使用时会重新创建
+     */
+    fun reset() {
+        NetworkManager.reset()
+        isInitialized = false
+        cachedBaseURL = null
+        ILog.d(TAG, "网络管理器已重置")
+    }
 }
 

+ 0 - 104
base-common/src/main/java/com/narutohuo/xindazhou/common/network/NetworkHelper.kt

@@ -1,104 +0,0 @@
-package com.narutohuo.xindazhou.common.network
-
-import com.narutohuo.xindazhou.core.network.NetworkManager as CoreNetworkManager
-import com.narutohuo.xindazhou.core.network.NetworkConfig
-
-/**
- * 网络管理器封装
- * 
- * 业务层统一使用此类进行网络初始化,不直接使用 base-core 的 NetworkManager
- * 
- * **使用示例**:
- * ```kotlin
- * // 在 Application 中初始化网络管理器
- * NetworkHelper.init(
- *     baseUrl = "https://api.example.com",
- *     isDebug = BuildConfig.DEBUG,
- *     enableLogging = BuildConfig.DEBUG
- * )
- * 
- * // 设置 Token 提供器
- * NetworkHelper.setTokenProvider {
- *     TokenManager.getAccessToken()
- * }
- * 
- * // 重置网络管理器(切换服务器地址时)
- * NetworkHelper.reset()
- * ```
- */
-object NetworkHelper {
-    /**
-     * 初始化网络管理器
-     * 
-     * @param baseUrl 基础URL
-     * @param isDebug 是否为调试模式
-     * @param enableLogging 是否启用日志
-     */
-    @JvmStatic
-    fun init(
-        baseUrl: String,
-        isDebug: Boolean = false,
-        enableLogging: Boolean = isDebug
-    ) {
-        CoreNetworkManager.init(
-            NetworkConfig.Builder()
-                .baseUrl(baseUrl)
-                .isDebug(isDebug)
-                .enableLogging(enableLogging)
-                .build()
-        )
-    }
-    
-    /**
-     * 初始化网络管理器(使用完整配置)
-     * 
-     * @param baseUrl 基础URL
-     * @param connectTimeoutSeconds 连接超时时间(秒)
-     * @param readTimeoutSeconds 读取超时时间(秒)
-     * @param writeTimeoutSeconds 写入超时时间(秒)
-     * @param isDebug 是否为调试模式
-     * @param enableLogging 是否启用日志
-     */
-    @JvmStatic
-    fun init(
-        baseUrl: String,
-        connectTimeoutSeconds: Long,
-        readTimeoutSeconds: Long,
-        writeTimeoutSeconds: Long,
-        isDebug: Boolean = false,
-        enableLogging: Boolean = isDebug
-    ) {
-        CoreNetworkManager.init(
-            NetworkConfig.Builder()
-                .baseUrl(baseUrl)
-                .connectTimeout(connectTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
-                .readTimeout(readTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
-                .writeTimeout(writeTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
-                .isDebug(isDebug)
-                .enableLogging(enableLogging)
-                .build()
-        )
-    }
-    
-    /**
-     * 设置 Token 提供器
-     * 
-     * 用于自动在请求头中添加 Authorization Token
-     * 
-     * @param tokenProvider Token 提供器,返回当前的 accessToken
-     */
-    @JvmStatic
-    fun setTokenProvider(tokenProvider: () -> String?) {
-        CoreNetworkManager.tokenProvider = tokenProvider
-    }
-    
-    /**
-     * 重置网络管理器(用于切换服务器地址时)
-     * 
-     * 清空已缓存的 Retrofit 和 OkHttpClient 实例,下次使用时会重新创建
-     */
-    @JvmStatic
-    fun reset() {
-        CoreNetworkManager.reset()
-    }
-}

+ 0 - 934
base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求MVVM封装方案.md

@@ -1,934 +0,0 @@
-# 网络请求 MVVM 架构封装方案(优化版)
-
-## 一、设计目标
-
-将现有的 `ApiResponseParser` 和 `ApiServiceFactory` 封装成符合 MVVM 架构的完整 Data 层组件,统一处理公共逻辑,业务层只需简单调用。
-
-## 二、架构设计
-
-### 2.1 现有组件
-
-- ✅ `ApiResponseParser` - 响应解析器(已存在)
-- ✅ `ApiServiceFactory` - API 服务工厂(已存在)
-
-### 2.2 新增组件
-
-- **BaseRemoteDataSource** - 基础远程数据源抽象类(增强版)
-  - 封装通用网络请求逻辑
-  - 统一错误处理、异常捕获、日志记录
-  - 使用 reified 泛型简化调用,无需传入 dataClass
-  - 自动切换到 IO 线程
-
-- **BaseRepository** - 基础仓库类(可选)
-  - 封装通用仓库逻辑
-  - 统一数据转换和缓存处理
-
-## 三、实现方案
-
-### 3.1 BaseRemoteDataSource 抽象类(增强版)
-
-```kotlin
-package com.narutohuo.xindazhou.common.network
-
-import android.util.Log
-import retrofit2.Response
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import java.net.UnknownHostException
-import java.net.SocketTimeoutException
-
-/**
- * 基础远程数据源
- * 
- * 封装通用网络请求逻辑,统一处理:
- * - 错误处理和异常捕获
- * - 日志记录
- * - 线程切换(自动切换到 IO 线程)
- * - 网络异常友好提示
- * 
- * 使用方式:
- * ```kotlin
- * class AuthRemoteDataSource : BaseRemoteDataSource() {
- *     private val authApi: AuthApi = ApiServiceFactory.create()
- *     
- *     suspend fun login(request: LoginRequest): Result<LoginResponse> {
- *         return executeRequest(
- *             request = { authApi.login(request) },
- *             errorMessage = "登录失败"
- *         )
- *     }
- * }
- * ```
- */
-abstract class BaseRemoteDataSource {
-    
-    /**
-     * 获取日志标签(子类可重写)
-     */
-    protected open val tag: String
-        get() = this::class.simpleName ?: "BaseRemoteDataSource"
-    
-    /**
-     * 处理网络请求(通用方法)
-     * 
-     * 自动处理:
-     * - HTTP 错误码
-     * - 业务错误码
-     * - 网络异常
-     * - 超时异常
-     * - 日志记录
-     * 
-     * @param request 网络请求的 suspend 函数
-     * @param errorMessage 错误消息前缀
-     * @return Result<T>
-     */
-    protected suspend inline fun <reified T> executeRequest(
-        crossinline request: suspend () -> Response<CommonResult<T>>,
-        errorMessage: String = "请求失败"
-    ): Result<T> = withContext(Dispatchers.IO) {
-        try {
-            Log.d(tag, "开始请求: ${T::class.simpleName}")
-            val response = request()
-            
-            // 使用 reified 泛型,自动获取 Class
-            ApiResponseParser.handleResponse(
-                response = response,
-                dataClass = T::class.java,
-                tag = tag,
-                errorMessage = errorMessage
-            )
-        } catch (e: SocketTimeoutException) {
-            val errorMsg = "请求超时,请检查网络连接"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: UnknownHostException) {
-            val errorMsg = "网络连接失败,请检查网络设置"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: Exception) {
-            val errorMsg = e.message ?: errorMessage
-            Log.e(tag, "请求异常: $errorMsg", e)
-            Result.failure(Exception(errorMsg))
-        }
-    }
-    
-    /**
-     * 处理可空数据的网络请求
-     * 
-     * 用于处理服务器可能返回 null 的情况
-     * 
-     * @param request 网络请求的 suspend 函数
-     * @param errorMessage 错误消息前缀
-     * @return Result<T?>
-     */
-    protected suspend inline fun <reified T> executeNullableRequest(
-        crossinline request: suspend () -> Response<CommonResult<T>>,
-        errorMessage: String = "请求失败"
-    ): Result<T?> = withContext(Dispatchers.IO) {
-        try {
-            Log.d(tag, "开始请求(可空): ${T::class.simpleName}")
-            val response = request()
-            
-            ApiResponseParser.handleNullableResponse(
-                response = response,
-                tag = tag,
-                errorMessage = errorMessage
-            )
-        } catch (e: SocketTimeoutException) {
-            val errorMsg = "请求超时,请检查网络连接"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: UnknownHostException) {
-            val errorMsg = "网络连接失败,请检查网络设置"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: Exception) {
-            val errorMsg = e.message ?: errorMessage
-            Log.e(tag, "请求异常: $errorMsg", e)
-            Result.failure(Exception(errorMsg))
-        }
-    }
-}
-```
-
-### 3.2 BaseRepository 基础仓库类(可选)
-
-```kotlin
-package com.narutohuo.xindazhou.common.network
-
-import android.util.Log
-
-/**
- * 基础仓库类
- * 
- * 封装通用仓库逻辑:
- * - 统一错误处理
- * - 数据转换(Data Model → Domain Model)
- * - 缓存策略(可选)
- * 
- * 使用方式:
- * ```kotlin
- * class AuthRepository(
- *     private val remoteDataSource: AuthRemoteDataSource
- * ) : BaseRepository() {
- *     
- *     suspend fun login(mobile: String, password: String): Result<LoginResponse> {
- *         val request = LoginRequest(mobile, password)
- *         return remoteDataSource.login(request)
- *     }
- * }
- * ```
- */
-open class BaseRepository {
-    
-    /**
-     * 获取日志标签(子类可重写)
-     */
-    protected open val tag: String
-        get() = this::class.simpleName ?: "BaseRepository"
-    
-    /**
-     * 处理数据转换(Data Model → Domain Model)
-     * 
-     * 子类可重写此方法实现自定义转换逻辑
-     */
-    protected open fun <T> transform(data: T): T {
-        return data
-    }
-    
-    /**
-     * 统一错误处理
-     * 
-     * 子类可重写此方法实现自定义错误处理
-     */
-    protected open fun handleError(throwable: Throwable): Result<Nothing> {
-        val errorMsg = throwable.message ?: "操作失败"
-        Log.e(tag, "操作失败: $errorMsg", throwable)
-        return Result.failure(throwable)
-    }
-}
-```
-
-### 3.3 使用示例
-
-#### 示例 1:AuthRemoteDataSource(优化后)
-
-```kotlin
-package com.narutohuo.xindazhou.user.data.remote
-
-import com.narutohuo.xindazhou.common.network.BaseRemoteDataSource
-import com.narutohuo.xindazhou.common.network.ApiServiceFactory
-import com.narutohuo.xindazhou.user.data.model.LoginRequest
-import com.narutohuo.xindazhou.user.data.model.LoginResponse
-import com.narutohuo.xindazhou.user.data.api.AuthApi
-
-/**
- * 认证远程数据源
- * 
- * 继承 BaseRemoteDataSource,自动获得:
- * - 统一的错误处理
- * - 自动日志记录
- * - 网络异常友好提示
- * - 线程自动切换
- */
-class AuthRemoteDataSource : BaseRemoteDataSource() {
-    
-    private val authApi: AuthApi = ApiServiceFactory.create()
-    
-    /**
-     * 登录
-     * 
-     * 无需传入 dataClass,使用 reified 泛型自动推断
-     */
-    suspend fun login(request: LoginRequest): Result<LoginResponse> {
-        return executeRequest(
-            request = { authApi.login(request) },
-            errorMessage = "登录失败"
-        )
-    }
-    
-    /**
-     * 注册
-     */
-    suspend fun register(request: RegisterRequest): Result<RegisterResponse> {
-        return executeRequest(
-            request = { authApi.register(request) },
-            errorMessage = "注册失败"
-        )
-    }
-    
-    /**
-     * 刷新Token(可空数据)
-     */
-    suspend fun refreshToken(refreshToken: String): Result<TokenResponse?> {
-        return executeNullableRequest(
-            request = { authApi.refreshToken(refreshToken) },
-            errorMessage = "刷新Token失败"
-        )
-    }
-}
-```
-
-#### 示例 2:UserRemoteDataSource(优化后)
-
-```kotlin
-package com.narutohuo.xindazhou.user.data.remote
-
-import com.narutohuo.xindazhou.common.network.BaseRemoteDataSource
-import com.narutohuo.xindazhou.common.network.ApiServiceFactory
-import com.narutohuo.xindazhou.user.data.model.UserInfo
-import com.narutohuo.xindazhou.user.data.api.UserApi
-
-/**
- * 用户远程数据源
- */
-class UserRemoteDataSource : BaseRemoteDataSource() {
-    
-    // 可以自定义日志标签
-    override val tag: String = "UserRemoteDataSource"
-    
-    private val userApi: UserApi = ApiServiceFactory.create()
-    
-    /**
-     * 获取用户信息
-     */
-    suspend fun getUserInfo(userId: String): Result<UserInfo> {
-        return executeRequest(
-            request = { userApi.getUserInfo(userId) },
-            errorMessage = "获取用户信息失败"
-        )
-    }
-    
-    /**
-     * 更新用户信息(可空数据)
-     */
-    suspend fun updateUserInfo(request: UpdateUserRequest): Result<UserInfo?> {
-        return executeNullableRequest(
-            request = { userApi.updateUserInfo(request) },
-            errorMessage = "更新用户信息失败"
-        )
-    }
-}
-```
-
-#### 示例 3:AuthRepository(使用 BaseRepository)
-
-```kotlin
-package com.narutohuo.xindazhou.user.data.repository
-
-import com.narutohuo.xindazhou.common.network.BaseRepository
-import com.narutohuo.xindazhou.user.data.remote.AuthRemoteDataSource
-import com.narutohuo.xindazhou.user.data.model.LoginRequest
-import com.narutohuo.xindazhou.user.data.model.LoginResponse
-
-/**
- * 认证仓库
- * 
- * 继承 BaseRepository,自动获得:
- * - 统一的错误处理
- * - 日志记录
- * - 数据转换扩展点
- */
-class AuthRepository(
-    private val remoteDataSource: AuthRemoteDataSource
-) : BaseRepository() {
-    
-    /**
-     * 登录
-     */
-    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
-        val request = LoginRequest(mobile, password)
-        return remoteDataSource.login(request)
-            .onFailure { throwable ->
-                // 可以在这里添加额外的错误处理逻辑
-                // 例如:记录日志、发送错误上报等
-                handleError(throwable)
-            }
-    }
-    
-    /**
-     * 注册
-     */
-    suspend fun register(mobile: String, password: String, verifyCode: String): Result<RegisterResponse> {
-        val request = RegisterRequest(mobile, password, verifyCode)
-        return remoteDataSource.register(request)
-    }
-}
-```
-
-### 3.4 ViewModel 层使用(保持不变)
-
-```kotlin
-package com.narutohuo.xindazhou.user.ui.viewmodel
-
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.launch
-import com.narutohuo.xindazhou.user.data.repository.AuthRepository
-
-class LoginViewModel(
-    application: Application,
-    private val authRepository: AuthRepository
-) : AndroidViewModel(application) {
-    
-    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
-    val loginState: StateFlow<LoginState> = _loginState
-    
-    fun login(mobile: String, password: String) {
-        viewModelScope.launch {
-            _loginState.value = LoginState.Loading
-            
-            authRepository.login(mobile, password)
-                .onSuccess {
-                    _loginState.value = LoginState.Success("登录成功")
-                }
-                .onFailure { e ->
-                    _loginState.value = LoginState.Error(e.message ?: "登录失败")
-                }
-        }
-    }
-}
-```
-
-## 四、目录结构
-
-```
-base-common/src/main/java/com/narutohuo/xindazhou/common/
-├── network/
-│   ├── ApiResponseParser.kt          # 响应解析器(已存在)
-│   ├── ApiServiceFactory.kt          # API服务工厂(已存在)
-│   ├── CommonResult.kt               # 通用响应模型(已存在)
-│   ├── BaseRemoteDataSource.kt       # 基础远程数据源(新增,增强版)
-│   └── BaseRepository.kt             # 基础仓库类(新增,可选)
-│
-└── ui/
-    ├── BaseActivity.kt               # 基础Activity(新增)
-    └── BaseFragment.kt               # 基础Fragment(新增)
-
-示例模块:
-user/
-├── data/
-│   ├── remote/
-│   │   └── AuthRemoteDataSource.kt   # 认证远程数据源(继承BaseRemoteDataSource)
-│   └── repository/
-│       └── AuthRepository.kt         # 认证仓库(继承BaseRepository,可选)
-│
-└── ui/
-    ├── login/
-    │   ├── LoginActivity.kt          # 登录Activity(继承BaseActivity)
-    │   └── LoginFragment.kt          # 登录Fragment(继承BaseFragment)
-    └── viewmodel/
-        └── LoginViewModel.kt         # 登录ViewModel
-```
-
-## 五、优化亮点
-
-### 5.1 使用 reified 泛型,简化调用
-- ❌ **之前**:需要手动传入 `dataClass = LoginResponse::class.java`
-- ✅ **现在**:自动推断类型,无需传入 `dataClass`
-
-### 5.2 统一公共处理
-- ✅ **自动线程切换**:所有请求自动切换到 IO 线程
-- ✅ **统一错误处理**:网络异常、超时等自动处理并给出友好提示
-- ✅ **自动日志记录**:请求开始、成功、失败自动记录日志
-- ✅ **自定义日志标签**:子类可重写 `tag` 属性自定义日志标签
-
-### 5.3 网络异常友好提示
-- `SocketTimeoutException` → "请求超时,请检查网络连接"
-- `UnknownHostException` → "网络连接失败,请检查网络设置"
-- 其他异常 → 显示具体错误信息
-
-### 5.4 代码对比
-
-**优化前:**
-```kotlin
-suspend fun login(request: LoginRequest): Result<LoginResponse> {
-    return try {
-        val response = authApi.login(request)
-        ApiResponseParser.handleResponse(
-            response,
-            LoginResponse::class.java,
-            "登录失败"
-        )
-    } catch (e: Exception) {
-        Result.failure(e)
-    }
-}
-```
-
-**优化后:**
-```kotlin
-suspend fun login(request: LoginRequest): Result<LoginResponse> {
-    return executeRequest(
-        request = { authApi.login(request) },
-        errorMessage = "登录失败"
-    )
-}
-```
-
-## 六、优势
-
-✅ **代码简化**:统一的网络请求处理,减少重复代码 70%+  
-✅ **错误统一**:统一的错误处理逻辑,友好的错误提示  
-✅ **易于测试**:DataSource 和 Repository 可以轻松 Mock  
-✅ **符合架构**:完全符合 MVVM 四层架构设计  
-✅ **自动处理**:线程切换、日志记录、异常处理全自动  
-✅ **类型安全**:使用 reified 泛型,编译时类型检查  
-
-## 七、业务层基类设计
-
-### 7.1 BaseActivity 基类
-
-```kotlin
-package com.narutohuo.xindazhou.common.ui
-
-import android.os.Bundle
-import android.view.View
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import androidx.viewbinding.ViewBinding
-import kotlinx.coroutines.launch
-import android.widget.Toast
-
-/**
- * 基础 Activity
- * 
- * 封装通用功能:
- * - ViewBinding 支持
- * - 统一的加载状态管理
- * - 统一的错误提示
- * - 生命周期管理
- * 
- * 使用方式:
- * ```kotlin
- * class LoginActivity : BaseActivity<ActivityLoginBinding>() {
- *     override fun onCreate(savedInstanceState: Bundle?) {
- *         super.onCreate(savedInstanceState)
- *         // 你的代码
- *     }
- * }
- * ```
- */
-abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
-    
-    protected lateinit var binding: VB
-    protected abstract fun getViewBinding(): VB
-    
-    // 加载状态(子类可重写自定义实现)
-    protected open fun showLoading() {
-        // 默认实现:显示加载提示
-        // 子类可重写自定义加载UI
-    }
-    
-    protected open fun hideLoading() {
-        // 默认实现:隐藏加载提示
-        // 子类可重写自定义加载UI
-    }
-    
-    // 错误提示(子类可重写自定义实现)
-    protected open fun showError(message: String) {
-        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
-    }
-    
-    protected open fun showSuccess(message: String) {
-        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
-    }
-    
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        binding = getViewBinding()
-        setContentView(binding.root)
-        initView()
-        initObserver()
-    }
-    
-    /**
-     * 初始化视图(子类实现)
-     */
-    protected open fun initView() {}
-    
-    /**
-     * 初始化观察者(子类实现)
-     */
-    protected open fun initObserver() {}
-    
-    /**
-     * 统一执行网络请求
-     * 
-     * 自动处理加载状态和错误提示
-     */
-    protected fun <T> executeRequest(
-        request: suspend () -> Result<T>,
-        onSuccess: (T) -> Unit,
-        onError: ((String) -> Unit)? = null,
-        showLoading: Boolean = true
-    ) {
-        lifecycleScope.launch {
-            try {
-                if (showLoading) showLoading()
-                
-                request()
-                    .onSuccess { data ->
-                        hideLoading()
-                        onSuccess(data)
-                    }
-                    .onFailure { throwable ->
-                        hideLoading()
-                        val errorMsg = throwable.message ?: "操作失败"
-                        if (onError != null) {
-                            onError(errorMsg)
-                        } else {
-                            showError(errorMsg)
-                        }
-                    }
-            } catch (e: Exception) {
-                hideLoading()
-                val errorMsg = e.message ?: "操作失败"
-                if (onError != null) {
-                    onError(errorMsg)
-                } else {
-                    showError(errorMsg)
-                }
-            }
-        }
-    }
-}
-```
-
-### 7.2 BaseFragment 基类
-
-```kotlin
-package com.narutohuo.xindazhou.common.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import androidx.viewbinding.ViewBinding
-import kotlinx.coroutines.launch
-
-/**
- * 基础 Fragment
- * 
- * 封装通用功能:
- * - ViewBinding 支持
- * - 统一的加载状态管理
- * - 统一的错误提示
- * - 生命周期管理
- * 
- * 使用方式:
- * ```kotlin
- * class LoginFragment : BaseFragment<FragmentLoginBinding>() {
- *     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- *         super.onViewCreated(view, savedInstanceState)
- *         // 你的代码
- *     }
- * }
- * ```
- */
-abstract class BaseFragment<VB : ViewBinding> : Fragment() {
-    
-    protected lateinit var binding: VB
-    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
-    
-    // 加载状态(子类可重写自定义实现)
-    protected open fun showLoading() {
-        // 默认实现:显示加载提示
-        // 子类可重写自定义加载UI
-    }
-    
-    protected open fun hideLoading() {
-        // 默认实现:隐藏加载提示
-        // 子类可重写自定义加载UI
-    }
-    
-    // 错误提示(子类可重写自定义实现)
-    protected open fun showError(message: String) {
-        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
-    }
-    
-    protected open fun showSuccess(message: String) {
-        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
-    }
-    
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        binding = getViewBinding(inflater, container)
-        return binding.root
-    }
-    
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        initView()
-        initObserver()
-    }
-    
-    /**
-     * 初始化视图(子类实现)
-     */
-    protected open fun initView() {}
-    
-    /**
-     * 初始化观察者(子类实现)
-     */
-    protected open fun initObserver() {}
-    
-    /**
-     * 统一执行网络请求
-     * 
-     * 自动处理加载状态和错误提示
-     */
-    protected fun <T> executeRequest(
-        request: suspend () -> Result<T>,
-        onSuccess: (T) -> Unit,
-        onError: ((String) -> Unit)? = null,
-        showLoading: Boolean = true
-    ) {
-        lifecycleScope.launch {
-            try {
-                if (showLoading) showLoading()
-                
-                request()
-                    .onSuccess { data ->
-                        hideLoading()
-                        onSuccess(data)
-                    }
-                    .onFailure { throwable ->
-                        hideLoading()
-                        val errorMsg = throwable.message ?: "操作失败"
-                        if (onError != null) {
-                            onError(errorMsg)
-                        } else {
-                            showError(errorMsg)
-                        }
-                    }
-            } catch (e: Exception) {
-                hideLoading()
-                val errorMsg = e.message ?: "操作失败"
-                if (onError != null) {
-                    onError(errorMsg)
-                } else {
-                    showError(errorMsg)
-                }
-            }
-        }
-    }
-}
-```
-
-## 八、业务层使用示例
-
-### 8.1 Activity 中使用(推荐使用 ViewModel)
-
-```kotlin
-package com.narutohuo.xindazhou.user.ui.login
-
-import androidx.lifecycle.ViewModelProvider
-import com.narutohuo.xindazhou.common.ui.BaseActivity
-import com.narutohuo.xindazhou.user.databinding.ActivityLoginBinding
-import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModel
-import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModelFactory
-
-/**
- * 登录 Activity
- */
-class LoginActivity : BaseActivity<ActivityLoginBinding>() {
-    
-    private lateinit var viewModel: LoginViewModel
-    
-    override fun getViewBinding(): ActivityLoginBinding {
-        return ActivityLoginBinding.inflate(layoutInflater)
-    }
-    
-    override fun initView() {
-        // 初始化 ViewModel
-        viewModel = ViewModelProvider(
-            this,
-            LoginViewModelFactory(application)
-        )[LoginViewModel::class.java]
-        
-        // 设置点击事件
-        binding.btnLogin.setOnClickListener {
-            val mobile = binding.etMobile.text.toString()
-            val password = binding.etPassword.text.toString()
-            viewModel.login(mobile, password)
-        }
-    }
-    
-    override fun initObserver() {
-        // 观察登录状态
-        lifecycleScope.launch {
-            viewModel.loginState.collect { state ->
-                when (state) {
-                    is LoginState.Idle -> {}
-                    is LoginState.Loading -> showLoading()
-                    is LoginState.Success -> {
-                        hideLoading()
-                        showSuccess(state.message)
-                        // 跳转到主页
-                    }
-                    is LoginState.Error -> {
-                        hideLoading()
-                        showError(state.message)
-                    }
-                }
-            }
-        }
-    }
-}
-```
-
-### 8.2 Fragment 中使用(推荐使用 ViewModel)
-
-```kotlin
-package com.narutohuo.xindazhou.user.ui.login
-
-import androidx.fragment.app.viewModels
-import com.narutohuo.xindazhou.common.ui.BaseFragment
-import com.narutohuo.xindazhou.user.databinding.FragmentLoginBinding
-import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModel
-
-/**
- * 登录 Fragment
- */
-class LoginFragment : BaseFragment<FragmentLoginBinding>() {
-    
-    private val viewModel: LoginViewModel by viewModels { 
-        LoginViewModelFactory(requireActivity().application) 
-    }
-    
-    override fun getViewBinding(
-        inflater: LayoutInflater,
-        container: ViewGroup?
-    ): FragmentLoginBinding {
-        return FragmentLoginBinding.inflate(inflater, container, false)
-    }
-    
-    override fun initView() {
-        // 设置点击事件
-        binding.btnLogin.setOnClickListener {
-            val mobile = binding.etMobile.text.toString()
-            val password = binding.etPassword.text.toString()
-            viewModel.login(mobile, password)
-        }
-    }
-    
-    override fun initObserver() {
-        // 观察登录状态
-        lifecycleScope.launch {
-            viewModel.loginState.collect { state ->
-                when (state) {
-                    is LoginState.Idle -> {}
-                    is LoginState.Loading -> showLoading()
-                    is LoginState.Success -> {
-                        hideLoading()
-                        showSuccess(state.message)
-                    }
-                    is LoginState.Error -> {
-                        hideLoading()
-                        showError(state.message)
-                    }
-                }
-            }
-        }
-    }
-}
-```
-
-### 8.3 Activity 中直接使用 Repository(简单场景)
-
-```kotlin
-package com.narutohuo.xindazhou.user.ui.login
-
-import com.narutohuo.xindazhou.common.ui.BaseActivity
-import com.narutohuo.xindazhou.user.databinding.ActivityLoginBinding
-import com.narutohuo.xindazhou.user.data.repository.AuthRepository
-
-/**
- * 登录 Activity(直接使用 Repository)
- * 
- * 适用于简单的业务场景,不推荐用于复杂业务
- */
-class LoginActivity : BaseActivity<ActivityLoginBinding>() {
-    
-    private val authRepository = AuthRepository(...) // 通过依赖注入获取
-    
-    override fun getViewBinding(): ActivityLoginBinding {
-        return ActivityLoginBinding.inflate(layoutInflater)
-    }
-    
-    override fun initView() {
-        binding.btnLogin.setOnClickListener {
-            val mobile = binding.etMobile.text.toString()
-            val password = binding.etPassword.text.toString()
-            
-            // 使用基类的 executeRequest 方法
-            executeRequest(
-                request = { authRepository.login(mobile, password) },
-                onSuccess = { response ->
-                    showSuccess("登录成功")
-                    // 处理登录成功
-                },
-                onError = { errorMsg ->
-                    // 默认会调用 showError,也可以自定义处理
-                }
-            )
-        }
-    }
-}
-```
-
-## 九、完整架构层次
-
-```
-业务层(UI层)
-├── BaseActivity / BaseFragment          # 基础UI组件(新增)
-│   ├── ViewBinding 支持
-│   ├── 加载状态管理
-│   ├── 错误提示
-│   └── executeRequest() 便捷方法
-│
-├── Activity / Fragment                  # 业务UI组件
-│   └── 继承 BaseActivity / BaseFragment
-│
-└── ViewModel                            # 视图模型
-    └── 使用 Repository
-
-数据层
-├── Repository                           # 数据仓库
-│   └── 继承 BaseRepository(可选)
-│
-├── RemoteDataSource                     # 远程数据源
-│   └── 继承 BaseRemoteDataSource
-│
-└── ApiServiceFactory                    # API服务工厂
-```
-
-## 十、使用注意事项
-
-1. **必须继承基类**:
-   - 所有 RemoteDataSource 必须继承 `BaseRemoteDataSource`
-   - 所有 Activity 建议继承 `BaseActivity`
-   - 所有 Fragment 建议继承 `BaseFragment`
-
-2. **使用 executeRequest**:
-   - DataSource 层:使用 `executeRequest()` 处理非空数据
-   - DataSource 层:使用 `executeNullableRequest()` 处理可空数据
-   - Activity/Fragment 层:使用 `executeRequest()` 统一处理请求(如果直接使用 Repository)
-
-3. **推荐使用 ViewModel**:
-   - 复杂业务推荐使用 ViewModel + StateFlow
-   - 简单业务可以直接使用基类的 `executeRequest()` 方法
-
-4. **自定义实现**:
-   - 可以重写 `showLoading()`, `hideLoading()`, `showError()` 等方法自定义UI
-   - 可以重写 `tag` 属性自定义日志标签
-
-5. **CommonResult**:已在 `ApiResponseParser.kt` 中定义,无需重复定义
-

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 926
base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求封装方案.html


+ 0 - 277
base-common/src/main/java/com/narutohuo/xindazhou/common/permission/PermissionHelper.kt

@@ -1,277 +0,0 @@
-package com.narutohuo.xindazhou.common.permission
-
-import android.app.Activity
-import android.content.Context
-import android.content.pm.PackageManager
-import androidx.activity.ComponentActivity
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
-import com.narutohuo.xindazhou.common.dialog.DialogHelper
-
-/**
- * 权限管理封装
- * 
- * 统一封装 Android 权限请求功能,提供便捷的权限管理方式
- * 
- * 使用方式:
- * ```kotlin
- * // 在 Activity 中注册权限请求
- * private val requestPermissionLauncher = PermissionHelper.registerRequestPermission(this) { permissions ->
- *     // 处理权限请求结果
- *     permissions.forEach { (permission, granted) ->
- *         if (granted) {
- *             // 权限已授予
- *         } else {
- *             // 权限被拒绝
- *         }
- *     }
- * }
- * 
- * // 请求单个权限
- * PermissionHelper.requestPermission(activity, Manifest.permission.CAMERA) { granted ->
- *     if (granted) {
- *         // 权限已授予
- *     }
- * }
- * 
- * // 请求多个权限
- * PermissionHelper.requestPermissions(activity, arrayOf(
- *     Manifest.permission.CAMERA,
- *     Manifest.permission.READ_EXTERNAL_STORAGE
- * )) { permissions ->
- *     // 处理权限结果
- * }
- * ```
- */
-object PermissionHelper {
-    
-    /**
-     * 注册权限请求器(Activity)
-     * 
-     * @param activity ComponentActivity 实例
-     * @param onResult 权限请求结果回调(Map<权限名, 是否授予>)
-     * @return ActivityResultLauncher,调用 launch() 启动权限请求
-     */
-    fun registerRequestPermission(
-        activity: ComponentActivity,
-        onResult: (Map<String, Boolean>) -> Unit
-    ): ActivityResultLauncher<Array<String>> {
-        return activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
-            onResult(permissions)
-        }
-    }
-    
-    /**
-     * 注册权限请求器(Fragment)
-     * 
-     * @param fragment Fragment 实例
-     * @param onResult 权限请求结果回调
-     * @return ActivityResultLauncher
-     */
-    fun registerRequestPermission(
-        fragment: Fragment,
-        onResult: (Map<String, Boolean>) -> Unit
-    ): ActivityResultLauncher<Array<String>> {
-        return fragment.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
-            onResult(permissions)
-        }
-    }
-    
-    /**
-     * 注册单个权限请求器(Activity)
-     * 
-     * @param activity ComponentActivity 实例
-     * @param onResult 权限请求结果回调(是否授予)
-     * @return ActivityResultLauncher,调用 launch() 启动权限请求
-     */
-    fun registerRequestSinglePermission(
-        activity: ComponentActivity,
-        onResult: (Boolean) -> Unit
-    ): ActivityResultLauncher<String> {
-        return activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
-            onResult(granted)
-        }
-    }
-    
-    /**
-     * 注册单个权限请求器(Fragment)
-     * 
-     * @param fragment Fragment 实例
-     * @param onResult 权限请求结果回调
-     * @return ActivityResultLauncher
-     */
-    fun registerRequestSinglePermission(
-        fragment: Fragment,
-        onResult: (Boolean) -> Unit
-    ): ActivityResultLauncher<String> {
-        return fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
-            onResult(granted)
-        }
-    }
-    
-    /**
-     * 检查权限是否已授予
-     * 
-     * @param context 上下文
-     * @param permission 权限名称
-     * @return true 表示已授予,false 表示未授予
-     */
-    fun checkPermission(context: Context, permission: String): Boolean {
-        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
-    }
-    
-    /**
-     * 检查多个权限是否已授予
-     * 
-     * @param context 上下文
-     * @param permissions 权限名称数组
-     * @return Map<权限名, 是否授予>
-     */
-    fun checkPermissions(context: Context, permissions: Array<String>): Map<String, Boolean> {
-        return permissions.associateWith { permission ->
-            checkPermission(context, permission)
-        }
-    }
-    
-    /**
-     * 检查所有权限是否都已授予
-     * 
-     * @param context 上下文
-     * @param permissions 权限名称数组
-     * @return true 表示所有权限都已授予
-     */
-    fun checkAllPermissionsGranted(context: Context, permissions: Array<String>): Boolean {
-        return permissions.all { checkPermission(context, it) }
-    }
-    
-    /**
-     * 请求单个权限(简化版,需要先注册)
-     * 
-     * @param launcher ActivityResultLauncher(通过 registerRequestSinglePermission 获取)
-     * @param permission 权限名称
-     */
-    fun requestPermission(launcher: ActivityResultLauncher<String>, permission: String) {
-        launcher.launch(permission)
-    }
-    
-    /**
-     * 请求多个权限(简化版,需要先注册)
-     * 
-     * @param launcher ActivityResultLauncher(通过 registerRequestPermission 获取)
-     * @param permissions 权限名称数组
-     */
-    fun requestPermissions(launcher: ActivityResultLauncher<Array<String>>, permissions: Array<String>) {
-        launcher.launch(permissions)
-    }
-    
-    /**
-     * 检查权限是否应该显示说明(用户之前拒绝过)
-     * 
-     * @param activity Activity 实例
-     * @param permission 权限名称
-     * @return true 表示应该显示说明
-     */
-    fun shouldShowRequestPermissionRationale(activity: Activity, permission: String): Boolean {
-        return activity.shouldShowRequestPermissionRationale(permission)
-    }
-    
-    /**
-     * 检查权限是否应该显示说明(Fragment)
-     * 
-     * @param fragment Fragment 实例
-     * @param permission 权限名称
-     * @return true 表示应该显示说明
-     */
-    fun shouldShowRequestPermissionRationale(fragment: Fragment, permission: String): Boolean {
-        return fragment.shouldShowRequestPermissionRationale(permission)
-    }
-    
-    /**
-     * 请求权限(带说明对话框,如果用户之前拒绝过)
-     * 
-     * @param activity ComponentActivity 实例
-     * @param permission 权限名称
-     * @param rationale 权限说明文本
-     * @param onRationaleConfirmed 用户确认说明后的回调(在这里执行实际权限请求)
-     * @param onResult 权限请求结果回调
-     */
-    fun requestPermissionWithRationale(
-        activity: ComponentActivity,
-        permission: String,
-        rationale: String,
-        onRationaleConfirmed: () -> Unit,
-        onResult: (Boolean) -> Unit
-    ) {
-        if (checkPermission(activity, permission)) {
-            // 权限已授予
-            onResult(true)
-            return
-        }
-        
-        if (shouldShowRequestPermissionRationale(activity, permission)) {
-            // 用户之前拒绝过,显示说明对话框
-            com.narutohuo.xindazhou.common.dialog.DialogHelper.showConfirm(
-                activity,
-                "权限说明",
-                rationale,
-                confirmText = "确定",
-                cancelText = "取消",
-                onConfirm = {
-                    onRationaleConfirmed()
-                },
-                onCancel = {
-                    onResult(false)
-                }
-            )
-        } else {
-            // 直接请求权限
-            onRationaleConfirmed()
-        }
-    }
-    
-    /**
-     * 请求权限(带说明对话框,Fragment 版本)
-     * 
-     * @param fragment Fragment 实例
-     * @param permission 权限名称
-     * @param rationale 权限说明文本
-     * @param onRationaleConfirmed 用户确认说明后的回调
-     * @param onResult 权限请求结果回调
-     */
-    fun requestPermissionWithRationale(
-        fragment: Fragment,
-        permission: String,
-        rationale: String,
-        onRationaleConfirmed: () -> Unit,
-        onResult: (Boolean) -> Unit
-    ) {
-        if (checkPermission(fragment.requireContext(), permission)) {
-            // 权限已授予
-            onResult(true)
-            return
-        }
-        
-        if (shouldShowRequestPermissionRationale(fragment, permission)) {
-            // 用户之前拒绝过,显示说明对话框
-            DialogHelper.showConfirm(
-                fragment.requireContext(),
-                "权限说明",
-                rationale,
-                confirmText = "确定",
-                cancelText = "取消",
-                onConfirm = {
-                    onRationaleConfirmed()
-                },
-                onCancel = {
-                    onResult(false)
-                }
-            )
-        } else {
-            // 直接请求权限
-            onRationaleConfirmed()
-        }
-    }
-}
-

+ 0 - 472
base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/SocketIOManager.kt

@@ -1,472 +0,0 @@
-package com.narutohuo.xindazhou.common.socketio
-
-import android.app.Application
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.ProcessLifecycleOwner
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.utils.JWTUtil
-import com.narutohuo.xindazhou.common.config.ServerConfigManager
-import com.narutohuo.xindazhou.common.log.LogHelper
-import com.narutohuo.xindazhou.common.socketio.impl.SocketIOClient
-import com.narutohuo.xindazhou.common.socketio.model.SocketIOResponse
-import com.narutohuo.xindazhou.core.storage.StorageImpl
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.launch
-
-/**
- * SocketIO 管理器(业务封装层)
- * 
- * 与 AuthManager、VersionUpdateManager 类似,提供统一的 Socket.IO 封装
- * 自动处理连接、重连、Token 刷新等,外部只需要订阅即可
- * 
- * 职责:
- * ✅ 自动处理连接(包括 Token 刷新)
- * ✅ 自动处理重连
- * ✅ 提供消息订阅接口
- * ✅ 外部只需要订阅即可
- * 
- * 使用方式:
- * ```kotlin
- * // 在 AppInitializer 中初始化(可选,会自动连接)
- * SocketIOManager.initialize(application)
- * 
- * // 在 ViewModel 中订阅消息
- * viewModelScope.launch {
- *     SocketIOManager.shared.subscribe("community_message").collect { response ->
- *         // 处理社区消息
- *     }
- * }
- * ```
- */
-object SocketIOManager : DefaultLifecycleObserver {
-    
-    private const val TAG = "SocketIOManager"
-    
-    // 单例实例
-    val shared = SocketIOManager
-    
-    private val socketClient = SocketIOClient.getInstance()
-    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
-    
-    private var application: Application? = null
-    private var serverURL: String? = null
-    private var isInitialized = false
-    
-    // 连接状态(使用 replay = 1 确保新订阅者能立即获取最新状态)
-    private val _connectionState = MutableSharedFlow<Boolean>(extraBufferCapacity = 1, replay = 1)
-    val connectionState: SharedFlow<Boolean> = _connectionState.asSharedFlow()
-    
-    init {
-        // 初始化连接状态(同步获取当前状态)
-        _connectionState.tryEmit(isConnected())
-        
-        // 订阅连接事件
-        subscribeConnectionEvents()
-    }
-    
-    // 消息订阅(使用 SharedFlow)
-    private val eventFlows = mutableMapOf<String, MutableSharedFlow<SocketIOResponse>>()
-    
-    // Token 刷新防抖
-    @Volatile
-    private var isRefreshingToken = false
-    
-    @Volatile
-    private var lastTokenRefreshTime = 0L
-    
-    // 重连防抖
-    @Volatile
-    private var isReconnecting = false
-    
-    @Volatile
-    private var lastReconnectTime = 0L
-    
-    /**
-     * 初始化并自动连接(如果用户已登录)
-     * 
-     * 在 AppInitializer 中调用一次即可
-     * 后续会自动处理连接、重连、Token 刷新等
-     * 
-     * 注意:
-     * - 如果用户未登录,不会立即连接,等待登录成功后调用 ensureConnected()
-     * - 如果用户已登录,会自动连接
-     * 
-     * @param application Application 实例(用于生命周期监听)
-     */
-    fun initialize(application: Application) {
-        if (isInitialized) {
-            LogHelper.d(TAG, "已初始化,跳过")
-            return
-        }
-        
-        this.application = application
-        isInitialized = true
-        
-        // 注册 App 生命周期监听
-        try {
-            ProcessLifecycleOwner.get().lifecycle.addObserver(this)
-            LogHelper.d(TAG, "生命周期监听已注册")
-        } catch (e: Exception) {
-            LogHelper.e(TAG, "注册生命周期监听失败", e)
-        }
-        
-        // 如果用户已登录,自动连接
-        appScope.launch {
-            if (AuthManager.isLoggedIn()) {
-                LogHelper.d(TAG, "用户已登录,自动连接 Socket.IO...")
-                connect()
-            } else {
-                LogHelper.d(TAG, "用户未登录,等待登录成功后连接")
-                // 更新连接状态为 false(用户未登录)
-                _connectionState.emit(false)
-            }
-        }
-        
-        LogHelper.d(TAG, "SocketIOManager 初始化完成(自动处理连接和Token刷新)")
-    }
-    
-    /**
-     * 确保已连接(如果未连接则自动连接)
-     * 
-     * 在用户登录成功后调用,确保 Socket.IO 已连接
-     * 如果已连接,则不做任何操作
-     * 如果未连接,会自动处理连接(包括 Token 刷新)
-     * 
-     * 使用场景:
-     * - 用户登录成功后调用
-     * - 应用启动后检查连接状态时调用
-     * - 前台重连时调用
-     */
-    fun ensureConnected() {
-        appScope.launch {
-            // 检查是否已登录
-            if (!AuthManager.isLoggedIn()) {
-                LogHelper.d(TAG, "用户未登录,无法连接")
-                _connectionState.emit(false)
-                return@launch
-            }
-            
-            // 如果已连接,更新状态并返回
-            if (isConnected()) {
-                LogHelper.d(TAG, "已连接,状态正常")
-                _connectionState.emit(true)
-                return@launch
-            }
-            
-            // 未连接,尝试重连
-            LogHelper.d(TAG, "检测到未连接,开始重连...")
-            checkAndReconnect()
-        }
-    }
-    
-    /**
-     * 连接 Socket.IO(自动处理 Token 刷新)
-     */
-    private suspend fun connect() {
-        LogHelper.d(TAG, "=== Socket.IO 连接开始 ===")
-        
-        // 获取服务器地址
-        val url = serverURL ?: ServerConfigManager.getSocketIOUrl()
-        if (url.isEmpty()) {
-            LogHelper.e(TAG, "服务器地址未配置,无法连接")
-            _connectionState.emit(false)
-            return
-        }
-        
-        this.serverURL = url
-        
-        // 获取 Token,如果为空或过期则刷新
-        var token = StorageImpl.getString("access_token")
-        
-        // 如果 Token 为空,尝试刷新
-        if (token.isEmpty()) {
-            LogHelper.d(TAG, "Token 为空,尝试刷新...")
-            token = refreshTokenIfNeeded() ?: ""
-            if (token.isEmpty()) {
-                LogHelper.e(TAG, "Token 为空且刷新失败,无法连接")
-                _connectionState.emit(false)
-                return
-            }
-        }
-        
-        // 如果 Token 已过期,尝试刷新
-        if (!JWTUtil.isTokenValid(token)) {
-            LogHelper.w(TAG, "Token 已过期,尝试刷新...")
-            val expiresAt = JWTUtil.getExpiresAt(token)
-            if (expiresAt != null) {
-                val expiresDate = java.util.Date(expiresAt * 1000) // 转换为毫秒
-                LogHelper.d(TAG, "Token 过期时间: $expiresDate")
-            }
-            token = refreshTokenIfNeeded() ?: token // 如果刷新失败,仍然尝试使用旧 Token(由服务端判断)
-            
-            // 如果刷新后仍然无效,返回
-            if (token.isEmpty() || !JWTUtil.isTokenValid(token)) {
-                LogHelper.e(TAG, "Token 刷新失败或仍然无效,无法连接")
-                _connectionState.emit(false)
-                return
-            }
-            LogHelper.d(TAG, "Token 刷新成功,使用新 Token 连接...")
-        }
-        
-        LogHelper.d(TAG, "正在连接到服务器: $url")
-        socketClient.connect(url, token)
-    }
-    
-    /**
-     * 断开连接
-     */
-    fun disconnect() {
-        LogHelper.d(TAG, "断开 Socket.IO 连接")
-        socketClient.disconnect()
-        appScope.launch {
-            _connectionState.emit(false)
-        }
-    }
-    
-    /**
-     * 检查连接状态
-     */
-    fun isConnected(): Boolean {
-        return socketClient.isConnected()
-    }
-    
-    /**
-     * 自动重连(如果断开)
-     */
-    private suspend fun checkAndReconnect() {
-        if (isConnected()) {
-            return
-        }
-        
-        // 防重复重连(5秒内最多重连一次)
-        val currentTime = System.currentTimeMillis()
-        if (isReconnecting || (currentTime - lastReconnectTime < 5000)) {
-            LogHelper.d(TAG, "正在重连或刚重连过,跳过")
-            return
-        }
-        
-        isReconnecting = true
-        lastReconnectTime = currentTime
-        
-        try {
-            LogHelper.d(TAG, "检测到断开连接,开始重连...")
-            
-            // 如果未初始化,先初始化
-            if (!isInitialized) {
-                LogHelper.d(TAG, "检测到未初始化,先执行初始化...")
-                val app = application ?: run {
-                    LogHelper.e(TAG, "Application 未设置,无法初始化")
-                    return
-                }
-                initialize(app)
-                return
-            }
-            
-            // 刷新 Token 后重连
-            val newToken = refreshTokenIfNeeded()
-            if (!newToken.isNullOrEmpty()) {
-                LogHelper.d(TAG, "Token 已刷新,使用新 Token 重连...")
-                delay(1000) // 延迟1秒,避免过于频繁的重连尝试
-                connect()
-            } else {
-                LogHelper.e(TAG, "Token 刷新失败,无法重连")
-            }
-        } finally {
-            isReconnecting = false
-        }
-    }
-    
-    /**
-     * 订阅消息(返回 SharedFlow)
-     * 
-     * 各个业务模块使用此方法订阅需要的消息类型
-     * 
-     * 注意:
-     * - 如果未连接,会自动尝试连接
-     * - 如果用户未登录,会等待登录成功后连接
-     * 
-     * 示例:
-     * ```kotlin
-     * // CommunityViewModel.kt
-     * viewModelScope.launch {
-     *     SocketIOManager.shared.subscribe("community_message").collect { response ->
-     *         // 处理社区消息
-     *     }
-     * }
-     * ```
-     */
-    fun subscribe(eventName: String): SharedFlow<SocketIOResponse> {
-        return eventFlows.getOrPut(eventName) {
-            MutableSharedFlow<SocketIOResponse>(extraBufferCapacity = 1).also { flow ->
-                // 订阅 Socket.IO 事件
-                socketClient.on(eventName) { response ->
-                    appScope.launch {
-                        flow.emit(response)
-                    }
-                }
-                
-                // 如果未连接,尝试连接(使用 ensureConnected 统一处理)
-                if (!isConnected()) {
-                    ensureConnected()
-                }
-            }
-        }.asSharedFlow()
-    }
-    
-    /**
-     * 发送消息
-     */
-    fun emit(eventName: String, data: Any) {
-        if (!isConnected()) {
-            LogHelper.w(TAG, "Socket.IO 未连接,无法发送事件: $eventName,尝试重新连接...")
-            appScope.launch {
-                checkAndReconnect()
-            }
-            return
-        }
-        
-        socketClient.emit(eventName, data)
-    }
-    
-    // MARK: - 生命周期监听
-    
-    /**
-     * App 进入前台时调用
-     * 
-     * 检查 SocketIO 连接状态,如果断开则自动重连
-     * 确保应用回到前台时,Socket.IO 连接状态正常
-     */
-    override fun onStart(owner: LifecycleOwner) {
-        LogHelper.d(TAG, "App 进入前台,检查 SocketIO 连接状态")
-        
-        // 使用 ensureConnected() 统一处理连接逻辑
-        ensureConnected()
-    }
-    
-    /**
-     * App 进入后台时调用
-     * 
-     * 注意:通常不断开 SocketIO 连接,因为:
-     * 1. SocketIO 连接是轻量级的
-     * 2. 用户可能需要在后台接收消息
-     * 3. 系统会在内存不足时自动清理
-     */
-    override fun onStop(owner: LifecycleOwner) {
-        LogHelper.d(TAG, "App 进入后台")
-        // 不断开连接,保持连接以便接收消息
-    }
-    
-    // MARK: - Private Methods
-    
-    /**
-     * 订阅连接事件
-     */
-    private fun subscribeConnectionEvents() {
-        socketClient.on("connect") { response ->
-            appScope.launch {
-                _connectionState.emit(true)
-                LogHelper.d(TAG, "✅ Socket.IO 连接成功")
-            }
-        }
-        
-        socketClient.on("disconnect") { response ->
-            appScope.launch {
-                _connectionState.emit(false)
-                LogHelper.d(TAG, "❌ Socket.IO 断开连接: ${response.data}")
-                
-                // 自动重连(延迟2秒)
-                delay(2000)
-                checkAndReconnect()
-            }
-        }
-        
-        socketClient.on("connect_error") { response ->
-            appScope.launch {
-                _connectionState.emit(false)
-                val errorMsg = response.data
-                LogHelper.e(TAG, "❌ Socket.IO 连接错误: $errorMsg")
-                
-                // 如果是 Token 错误,刷新后重连
-                if (errorMsg.contains("token", ignoreCase = true) || 
-                    errorMsg.contains("unauthorized", ignoreCase = true)) {
-                    refreshTokenAndReconnect()
-                } else {
-                    // 其他错误也尝试重连
-                    delay(2000)
-                    checkAndReconnect()
-                }
-            }
-        }
-    }
-    
-    /**
-     * 刷新 Token 并重连
-     */
-    private suspend fun refreshTokenAndReconnect() {
-        val newToken = refreshTokenIfNeeded()
-        if (!newToken.isNullOrEmpty()) {
-            LogHelper.d(TAG, "Token 已刷新,使用新 Token 重连...")
-            delay(1000) // 延迟1秒
-            connect()
-        } else {
-            LogHelper.e(TAG, "Token 刷新失败,无法重连")
-        }
-    }
-    
-    /**
-     * 刷新 Token(如果需要)
-     * 
-     * 使用 AuthManager 处理 Token 刷新
-     */
-    private suspend fun refreshTokenIfNeeded(): String? {
-        // 防止短时间内多次刷新(5秒内最多刷新一次)
-        val currentTime = System.currentTimeMillis()
-        if (isRefreshingToken) {
-            LogHelper.d(TAG, "正在刷新 Token,等待...")
-            // 等待刷新完成,最多等待3秒
-            var waitCount = 0
-            while (isRefreshingToken && waitCount < 30) {
-                delay(100) // 0.1秒
-                waitCount++
-            }
-            // 刷新完成后,从存储中获取最新的 Token
-            return StorageImpl.getString("access_token").takeIf { it.isNotEmpty() }
-        }
-        
-        if (currentTime - lastTokenRefreshTime < 5000) {
-            LogHelper.d(TAG, "刚刷新过 Token(${(currentTime - lastTokenRefreshTime) / 1000}秒前),使用存储中的 Token")
-            return StorageImpl.getString("access_token").takeIf { it.isNotEmpty() }
-        }
-        
-        return try {
-            isRefreshingToken = true
-            lastTokenRefreshTime = currentTime
-            
-            LogHelper.d(TAG, "开始刷新 Token...")
-            
-            // 使用 base-common 的 AuthManager
-            val newToken = AuthManager.refreshTokenIfNeeded()
-            
-            if (!newToken.isNullOrEmpty()) {
-                LogHelper.d(TAG, "Token 刷新成功")
-                newToken
-            } else {
-                LogHelper.w(TAG, "Token 刷新失败(返回 null)")
-                null
-            }
-        } catch (e: Exception) {
-            LogHelper.e(TAG, "Token 刷新异常", e)
-            null
-        } finally {
-            isRefreshingToken = false
-        }
-    }
-}
-

+ 0 - 243
base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/impl/SocketIOClient.kt

@@ -1,243 +0,0 @@
-package com.narutohuo.xindazhou.common.socketio.impl
-
-import com.google.gson.Gson
-import com.narutohuo.xindazhou.common.log.LogHelper
-import com.narutohuo.xindazhou.common.socketio.model.SocketIOResponse
-import io.socket.client.IO
-import io.socket.client.Socket
-import org.json.JSONObject
-
-/**
- * SocketIO 客户端(单例模式)
- * 
- * Socket.IO 连接是全局唯一的,多个界面需要共享同一个连接
- * 这是底层 Socket.IO 客户端的封装,由 SocketIOManager 使用
- */
-internal class SocketIOClient private constructor() {
-    
-    companion object {
-        @Volatile
-        private var INSTANCE: SocketIOClient? = null
-        
-        /**
-         * 获取 SocketIO 客户端单例
-         */
-        @JvmStatic
-        internal fun getInstance(): SocketIOClient {
-            return INSTANCE ?: synchronized(this) {
-                INSTANCE ?: SocketIOClient().also { INSTANCE = it }
-            }
-        }
-        
-        /**
-         * 销毁单例(用于测试或需要重新初始化时)
-         */
-        @JvmStatic
-        internal fun destroyInstance() {
-            INSTANCE?.disconnect()
-            INSTANCE = null
-        }
-    }
-    
-    private var socket: Socket? = null
-    // 存储事件监听器,以便在 socket 创建后注册
-    private val eventListeners = mutableMapOf<String, MutableList<(SocketIOResponse) -> Unit>>()
-    
-    fun connect(serverUrl: String, token: String) {
-        try {
-            LogHelper.d("SocketIOClient", "=== SocketIO 连接开始 ===")
-            LogHelper.d("SocketIOClient", "服务器地址: $serverUrl")
-            LogHelper.d("SocketIOClient", "Token 长度: ${token.length}")
-            
-            // 如果已连接,先断开
-            if (socket?.connected() == true) {
-                LogHelper.d("SocketIOClient", "检测到已有连接,先断开")
-                disconnect()
-            }
-            
-            // 初始化 Socket 连接
-            val options = IO.Options().apply {
-                // 添加认证 token(通过 HTTP Headers)
-                extraHeaders = mapOf("Authorization" to listOf("Bearer $token"))
-                // 添加 token 作为查询参数(备用方案)
-                query = "token=$token&EIO=1" // 使用 Engine.IO v1 以匹配服务器
-                // 禁用自动重连,改为手动重连(原因:自动重连无法更新 Token)
-                reconnection = false
-            }
-            
-            LogHelper.d("SocketIOClient", "连接配置: Authorization=Bearer $token, query=token=$token&EIO=1")
-            
-            LogHelper.d("SocketIOClient", "创建 Socket 实例...")
-            socket = IO.socket(serverUrl, options)
-            
-            // 预先注册所有已保存的事件监听器(连接前注册)
-            LogHelper.d("SocketIOClient", "connect: 预先注册 ${eventListeners.size} 个事件类型的监听器...")
-            if (eventListeners.isEmpty()) {
-                LogHelper.w("SocketIOClient", "⚠️ 警告:eventListeners 为空,没有监听器需要预注册!")
-            } else {
-                registerAllEventListeners()
-            }
-            
-            // 添加连接事件监听
-            socket?.on(Socket.EVENT_CONNECT) {
-                LogHelper.d("SocketIOClient", "✅ SocketIO 连接成功")
-                
-                // 关键:连接成功后,再次注册所有已保存的事件监听器
-                // 这样可以确保即使监听器是在连接之后才注册的,也能正确工作
-                LogHelper.d("SocketIOClient", "连接成功:再次注册所有事件监听器(${eventListeners.size} 个事件类型)...")
-                registerAllEventListeners()
-                
-                triggerEvent("connect", "{}")
-            }
-            
-            socket?.on(Socket.EVENT_DISCONNECT) {
-                LogHelper.d("SocketIOClient", "❌ SocketIO 断开连接")
-                triggerEvent("disconnect", "{}")
-            }
-            
-            socket?.on(Socket.EVENT_CONNECT_ERROR) { args ->
-                val errorMsg = args?.getOrNull(0)?.toString() ?: "连接错误"
-                LogHelper.e("SocketIOClient", "❌ SocketIO 连接错误: $errorMsg")
-                triggerEvent("connect_error", errorMsg)
-            }
-            
-            LogHelper.d("SocketIOClient", "开始连接 SocketIO 服务器...")
-            socket?.connect()
-            LogHelper.d("SocketIOClient", "connect() 方法调用完成")
-        } catch (e: Exception) {
-            LogHelper.e("SocketIOClient", "❌ SocketIO 连接异常", e)
-        }
-    }
-    
-    fun disconnect() {
-        socket?.disconnect()
-        socket = null
-        // 注意:不清除 eventListeners,这样重新连接时可以自动注册
-        LogHelper.d("SocketIOClient", "已断开 SocketIO 连接")
-    }
-    
-    fun isConnected(): Boolean {
-        return socket?.connected() == true
-    }
-    
-    fun on(event: String, callback: (SocketIOResponse) -> Unit) {
-        // 保存事件监听器
-        eventListeners.getOrPut(event) { mutableListOf() }.add(callback)
-        LogHelper.d("SocketIOClient", "on: 保存事件监听器: $event (当前该事件有 ${eventListeners[event]?.size} 个监听器)")
-        
-        // 如果 socket 已连接,立即注册该事件的所有监听器(重新注册所有,确保不丢失)
-        if (socket != null && socket!!.connected()) {
-            LogHelper.d("SocketIOClient", "on: Socket 已连接,重新注册该事件的所有监听器: $event")
-            // 先移除该事件的所有旧监听器,然后重新注册所有 callbacks
-            socket?.off(event)
-            val callbacks = eventListeners[event] ?: return
-            callbacks.forEach { cb ->
-                registerEventListener(event, cb)
-            }
-        } else {
-            LogHelper.d("SocketIOClient", "on: Socket 未连接,监听器已保存,将在连接成功后自动注册")
-        }
-    }
-    
-    fun off(event: String) {
-        socket?.off(event)
-        eventListeners.remove(event)
-    }
-    
-    fun emit(event: String, data: Any) {
-        try {
-            val jsonData = when (data) {
-                is String -> data
-                is JSONObject -> data
-                is Map<*, *> -> {
-                    // 将 Kotlin Map 转换为 JSONObject
-                    JSONObject().apply {
-                        @Suppress("UNCHECKED_CAST")
-                        (data as Map<String, Any>).forEach { (key, value) ->
-                            put(key, value)
-                        }
-                    }
-                }
-                else -> {
-                    // 其他类型先转为 JSON 字符串,再转为 JSONObject
-                    JSONObject(Gson().toJson(data))
-                }
-            }
-            
-            socket?.emit(event, jsonData)
-            LogHelper.d("SocketIOClient", "发送事件: $event")
-        } catch (e: Exception) {
-            LogHelper.e("SocketIOClient", "发送事件失败: $event", e)
-        }
-    }
-    
-    // ========== 内部方法 ==========
-    
-    /**
-     * 注册所有已保存的事件监听器
-     * 
-     * 参考 iOS 实现:不先 off,直接为每个 callback 注册监听器
-     */
-    private fun registerAllEventListeners() {
-        if (socket == null) {
-            LogHelper.w("SocketIOClient", "registerAllEventListeners: Socket 为 null,无法注册监听器")
-            return
-        }
-        LogHelper.d("SocketIOClient", "registerAllEventListeners: 开始注册 ${eventListeners.size} 个事件类型的监听器...")
-        eventListeners.forEach { (event, callbacks) ->
-            LogHelper.d("SocketIOClient", "registerAllEventListeners: 注册事件监听器: $event (${callbacks.size} 个回调)")
-            // 参考 iOS:不先 off,直接为每个 callback 注册监听器
-            // 这样每个 callback 都会收到事件,不会互相覆盖
-            callbacks.forEach { callback ->
-                registerEventListener(event, callback)
-            }
-        }
-        LogHelper.d("SocketIOClient", "registerAllEventListeners: 所有事件监听器注册完成")
-    }
-    
-    /**
-     * 注册单个事件监听器
-     */
-    private fun registerEventListener(event: String, callback: (SocketIOResponse) -> Unit) {
-        socket?.on(event) { args ->
-            try {
-                // Socket.IO Java 客户端库接收数据时,服务端发送的 Map 会被自动序列化为 JSONObject
-                // 统一转换为 JSON 字符串格式
-                val data = if (args.isNotEmpty()) {
-                    when (val arg = args[0]) {
-                        is String -> arg
-                        is JSONObject -> arg.toString()
-                        else -> Gson().toJson(arg)
-                    }
-                } else {
-                    "{}"
-                }
-                
-                val response = SocketIOResponse(
-                    event = event,
-                    data = data,
-                    timestamp = System.currentTimeMillis()
-                )
-                
-                LogHelper.d("SocketIOClient", "收到事件: $event, data=${response.data}")
-                callback(response)
-            } catch (e: Exception) {
-                LogHelper.e("SocketIOClient", "处理事件失败: $event", e)
-            }
-        }
-    }
-    
-    /**
-     * 触发事件(用于连接/断开等内置事件)
-     */
-    private fun triggerEvent(event: String, data: String) {
-        val callbacks = eventListeners[event] ?: return
-        val response = SocketIOResponse(
-            event = event,
-            data = data,
-            timestamp = System.currentTimeMillis()
-        )
-        callbacks.forEach { it(response) }
-    }
-}
-

+ 0 - 40
base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/model/SocketIOEvent.kt

@@ -1,40 +0,0 @@
-package com.narutohuo.xindazhou.common.socketio.model
-
-/**
- * SocketIO 事件类型常量
- * 
- * 定义 SocketIO 通信中使用的事件名称
- * 建议使用这些常量而不是硬编码字符串
- */
-object SocketIOEvent {
-    // ========== 连接事件(SocketIO 内置事件) ==========
-    /** 连接成功 */
-    const val CONNECT = "connect"
-    /** 断开连接 */
-    const val DISCONNECT = "disconnect"
-    /** 连接错误 */
-    const val CONNECT_ERROR = "connect_error"
-    
-    // ========== 车辆相关事件(业务事件) ==========
-    /** 车辆状态更新 */
-    const val VEHICLE_STATUS = "vehicle_status"
-    /** 车辆控制指令 */
-    const val VEHICLE_CONTROL = "vehicle_control"
-    /** 车辆控制响应 */
-    const val VEHICLE_CONTROL_RESPONSE = "vehicle_control_response"
-    /** 车辆报警 */
-    const val VEHICLE_ALARM = "vehicle_alarm"
-    /** 车辆位置更新 */
-    const val VEHICLE_LOCATION = "vehicle_location"
-    
-    // ========== 消息相关事件(业务事件) ==========
-    /** 普通消息 */
-    const val MESSAGE = "message"
-    /** 站内信 */
-    const val INBOX_MESSAGE = "inbox_message"
-    /** 系统消息 */
-    const val SYSTEM_MESSAGE = "system_message"
-    /** 社区消息 */
-    const val COMMUNITY_MESSAGE = "community_message"
-}
-

+ 0 - 17
base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/model/SocketIOResponse.kt

@@ -1,17 +0,0 @@
-package com.narutohuo.xindazhou.common.socketio.model
-
-/**
- * SocketIO 响应模型
- * 
- * 用于封装 SocketIO 服务器推送的实时数据
- * 
- * @param event 事件名称(如 "vehicle_status", "vehicle_control" 等)
- * @param data 数据内容(JSON 字符串格式)
- * @param timestamp 时间戳(接收时间)
- */
-data class SocketIOResponse(
-    val event: String,
-    val data: String,
-    val timestamp: Long = System.currentTimeMillis()
-)
-

+ 16 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityManager.kt

@@ -222,6 +222,10 @@ object ActivityManager {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         context.startActivity(intent)
+        // 去掉页面切换动画(不要左右滑动效果)
+        if (context is Activity) {
+            context.overridePendingTransition(0, 0)
+        }
     }
     
     /**
@@ -237,6 +241,10 @@ object ActivityManager {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         context.startActivity(intent)
+        // 去掉页面切换动画(不要左右滑动效果)
+        if (context is Activity) {
+            context.overridePendingTransition(0, 0)
+        }
     }
     
     /**
@@ -277,6 +285,10 @@ object ActivityManager {
             addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
         }
         context.startActivity(intent)
+        // 去掉页面切换动画(不要左右滑动效果)
+        if (context is Activity) {
+            context.overridePendingTransition(0, 0)
+        }
         if (context is Activity) {
             context.finish()
         }
@@ -295,6 +307,10 @@ object ActivityManager {
             block()
         }
         context.startActivity(intent)
+        // 去掉页面切换动画(不要左右滑动效果)
+        if (context is Activity) {
+            context.overridePendingTransition(0, 0)
+        }
         if (context is Activity) {
             context.finish()
         }

+ 62 - 73
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseActivity.kt

@@ -13,7 +13,7 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.lifecycle.lifecycleScope
 import androidx.viewbinding.ViewBinding
 import com.narutohuo.xindazhou.common.dialog.DialogHelper
-import com.narutohuo.xindazhou.common.permission.PermissionHelper
+import com.narutohuo.xindazhou.core.permission.PermissionHelper
 import kotlinx.coroutines.launch
 
 /**
@@ -84,9 +84,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
     //    android:configChanges="orientation|keyboardHidden|screenSize"
     protected open val screenOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
     
-    // 权限请求 Launcher
-    private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null
-    
     // Activity 结果 Launcher
     private val activityResultLauncher = registerForActivityResult(
         ActivityResultContracts.StartActivityForResult()
@@ -132,9 +129,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
             StatusBarHelper.setImmersiveStatusBar(this)
         }
         
-        // 初始化权限请求
-        initPermissionLauncher()
-        
         // 初始化视图和观察者
         initView()
         initObserver()
@@ -175,24 +169,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
     }
     
     /**
-     * 初始化权限请求 Launcher
-     */
-    private fun initPermissionLauncher() {
-        permissionLauncher = registerForActivityResult(
-            ActivityResultContracts.RequestMultiplePermissions()
-        ) { permissions ->
-            onPermissionResult(permissions)
-        }
-    }
-    
-    /**
-     * 权限请求结果回调(子类可重写)
-     */
-    protected open fun onPermissionResult(permissions: Map<String, Boolean>) {
-        // 子类可重写处理权限结果
-    }
-    
-    /**
      * Activity 结果回调(子类可重写)
      */
     protected open fun onActivityResult(resultCode: Int, data: Intent?) {
@@ -212,6 +188,19 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
     // ========== Activity 跳转封装 ==========
     
     /**
+     * 重写 startActivity 方法,统一去掉切换动画
+     */
+    override fun startActivity(intent: Intent?) {
+        super.startActivity(intent)
+        overridePendingTransition(0, 0) // 去掉页面切换动画
+    }
+    
+    override fun startActivity(intent: Intent?, options: Bundle?) {
+        super.startActivity(intent, options)
+        overridePendingTransition(0, 0) // 去掉页面切换动画
+    }
+    
+    /**
      * 启动 Activity(泛型方式)
      */
     fun <T : AppCompatActivity> startActivity(clazz: Class<T>) {
@@ -271,64 +260,56 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
     ) {
         val intent = Intent(this, clazz).apply(block)
         activityResultLauncher.launch(intent)
+        overridePendingTransition(0, 0) // 去掉页面切换动画
     }
     
     // ========== 权限请求封装 ==========
     
     /**
-     * 请求权限
-     */
-    protected fun requestPermissions(vararg permissions: String) {
-        permissionLauncher?.launch(arrayOf(*permissions))
-    }
-    
-    /**
-     * 请求权限(带说明)
-     * 注意:此方法需要先注册权限请求器
+     * 请求权限(推荐使用)
+     * 
+     * 基于 XXPermissions 封装,无需注册 Launcher
+     * 
+     * @param permissions 权限数组
+     * @param onGranted 所有权限都已授予的回调
+     * @param onDenied 有权限被拒绝的回调
+     * 
+     * 示例:
+     * ```kotlin
+     * requestPermissions(
+     *     Manifest.permission.CAMERA,
+     *     Manifest.permission.WRITE_EXTERNAL_STORAGE,
+     *     onGranted = { openCamera() },
+     *     onDenied = { showToast("需要相关权限") }
+     * )
+     * ```
      */
-    protected fun requestPermissionsWithRationale(
-        launcher: ActivityResultLauncher<Array<String>>,
+    protected fun requestPermissions(
         vararg permissions: String,
-        rationale: String = "需要相关权限才能使用此功能",
-        onResult: (Map<String, Boolean>) -> Unit = {}
+        onGranted: () -> Unit,
+        onDenied: (() -> Unit)? = null
     ) {
-        // 检查权限是否已授予
-        val allGranted = permissions.all { permission ->
-            PermissionHelper.checkPermission(this, permission)
-        }
-        
-        if (allGranted) {
-            val result = permissions.associateWith { true }
-            onPermissionResult(result)
-            onResult(result)
-            return
-        }
-        
-        // 检查是否需要显示说明
-        val needRationale = permissions.any { permission ->
-            PermissionHelper.shouldShowRequestPermissionRationale(this, permission)
+        PermissionHelper.request(this, *permissions, onGranted = onGranted, onDenied = onDenied)
         }
         
-        val permissionsArray = permissions.toList().toTypedArray()
-        
-        if (needRationale) {
-            // 显示说明对话框
-            showConfirm(
-                title = "权限说明",
-                message = rationale,
-                onConfirm = {
-                    PermissionHelper.requestPermissions(launcher, permissionsArray)
-                },
-                onCancel = {
-                    val result = permissions.associateWith { false }
-                    onPermissionResult(result)
-                    onResult(result)
-                }
-            )
-        } else {
-            // 直接请求权限
-            PermissionHelper.requestPermissions(launcher, permissionsArray)
-        }
+    /**
+     * 检查权限是否已授予
+     * 
+     * @param permission 权限名称
+     * @return true 表示已授予
+     */
+    protected fun isPermissionGranted(permission: String): Boolean {
+        return PermissionHelper.isGranted(this, permission)
+    }
+    
+    /**
+     * 检查多个权限是否都已授予
+     * 
+     * @param permissions 权限数组
+     * @return true 表示所有权限都已授予
+     */
+    protected fun arePermissionsGranted(vararg permissions: String): Boolean {
+        return PermissionHelper.isGranted(this, *permissions)
     }
     
     // ========== 对话框封装 ==========
@@ -459,6 +440,14 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
     // ========== 其他便捷方法 ==========
     
     /**
+     * 重写 finish 方法,统一去掉关闭动画
+     */
+    override fun finish() {
+        super.finish()
+        overridePendingTransition(0, 0) // 去掉页面关闭动画
+    }
+    
+    /**
      * 关闭当前 Activity
      */
     protected fun finishActivity() {

+ 0 - 156
base-common/src/main/java/com/narutohuo/xindazhou/common/util/UtilManager.kt

@@ -1,156 +0,0 @@
-package com.narutohuo.xindazhou.common.util
-
-import com.alibaba.android.arouter.launcher.ARouter
-import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.core.util.IUtil
-
-/**
- * 工具管理器
- * 
- * 业务层封装,提供便捷的工具方法调用方式
- * 内部使用 IUtil 接口(由 app 模块实现)
- * 
- * 使用方式:
- * ```kotlin
- * // 时间处理
- * val timeStr = UtilManager.formatTime(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss")
- * val timestamp = UtilManager.parseTime("2024-01-01 12:00:00", "yyyy-MM-dd HH:mm:ss")
- * 
- * // 字符串处理
- * val encrypted = UtilManager.encryptString("原始数据")
- * val decrypted = UtilManager.decryptString(encrypted)
- * val md5Hash = UtilManager.md5("原始数据")
- * 
- * // 数据转换
- * val json = UtilManager.toJson(user)
- * val user = UtilManager.fromJson<User>(json, User::class.java)
- * ```
- */
-object UtilManager {
-    
-    internal const val TAG = "UtilManager"
-    
-    /**
-     * 获取 IUtil 实现实例
-     * 通过 ARouter 获取,如果未注册则返回 null
-     */
-    fun getUtil(): IUtil? {
-        return try {
-            ARouter.getInstance().navigation(IUtil::class.java)
-        } catch (e: Exception) {
-            ILog.w(TAG, "IUtil 未注册,请确保 app 模块已实现并注册 IUtil")
-            null
-        }
-    }
-    
-    // ========== 时间处理 ==========
-    
-    /**
-     * 格式化时间
-     * 
-     * @param timestamp 时间戳(毫秒)
-     * @param pattern 格式模式(如 "yyyy-MM-dd HH:mm:ss")
-     * @return 格式化后的时间字符串
-     */
-    fun formatTime(timestamp: Long, pattern: String): String {
-        return getUtil()?.formatTime(timestamp, pattern) ?: run {
-            ILog.w(TAG, "formatTime 失败:IUtil 未注册")
-            ""
-        }
-    }
-    
-    /**
-     * 解析时间字符串
-     * 
-     * @param timeString 时间字符串
-     * @param pattern 格式模式(如 "yyyy-MM-dd HH:mm:ss")
-     * @return 时间戳(毫秒),如果解析失败返回 null
-     */
-    fun parseTime(timeString: String, pattern: String): Long? {
-        return getUtil()?.parseTime(timeString, pattern) ?: run {
-            ILog.w(TAG, "parseTime 失败:IUtil 未注册")
-            null
-        }
-    }
-    
-    // ========== 字符串处理 ==========
-    
-    /**
-     * 加密字符串
-     * 
-     * @param data 原始数据
-     * @return 加密后的字符串
-     */
-    fun encryptString(data: String): String {
-        return getUtil()?.encryptString(data) ?: run {
-            ILog.w(TAG, "encryptString 失败:IUtil 未注册")
-            data
-        }
-    }
-    
-    /**
-     * 解密字符串
-     * 
-     * @param data 加密后的数据
-     * @return 解密后的字符串
-     */
-    fun decryptString(data: String): String {
-        return getUtil()?.decryptString(data) ?: run {
-            ILog.w(TAG, "decryptString 失败:IUtil 未注册")
-            data
-        }
-    }
-    
-    /**
-     * MD5 哈希
-     * 
-     * @param data 原始数据
-     * @return MD5 哈希值
-     */
-    fun md5(data: String): String {
-        return getUtil()?.md5(data) ?: run {
-            ILog.w(TAG, "md5 失败:IUtil 未注册")
-            ""
-        }
-    }
-    
-    // ========== 数据转换 ==========
-    
-    /**
-     * 对象转 JSON
-     * 
-     * @param obj 对象
-     * @return JSON 字符串
-     */
-    inline fun <reified T> toJson(obj: T): String {
-        return getUtil()?.toJson(obj) ?: run {
-            ILog.w("UtilManager", "toJson 失败:IUtil 未注册")
-            ""
-        }
-    }
-    
-    /**
-     * JSON 转对象
-     * 
-     * @param json JSON 字符串
-     * @param clazz 目标类型
-     * @return 对象,如果解析失败返回 null
-     */
-    inline fun <reified T> fromJson(json: String, clazz: Class<T>): T? {
-        return getUtil()?.fromJson(json, clazz) ?: run {
-            ILog.w("UtilManager", "fromJson 失败:IUtil 未注册")
-            null
-        }
-    }
-    
-    /**
-     * JSON 转对象(简化版,使用 reified 泛型)
-     * 
-     * @param json JSON 字符串
-     * @return 对象,如果解析失败返回 null
-     */
-    inline fun <reified T> fromJson(json: String): T? {
-        return fromJson(json, T::class.java)
-    }
-}
-

+ 5 - 5
base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt

@@ -7,7 +7,7 @@ import androidx.core.content.pm.PackageInfoCompat
 import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.network.ApiManager
 import com.narutohuo.xindazhou.common.version.datasource.remote.VersionRemoteDataSourceImpl
 import com.narutohuo.xindazhou.common.version.repository.VersionRepository
@@ -52,7 +52,7 @@ object VersionUpdateManager {
         if (ApiManager.baseUrlProvider == null) {
             // 这里需要从 ServerConfigManager 获取,但为了避免循环依赖,
             // 建议在 Application 中先设置 ApiManager.baseUrlProvider
-            LogHelper.w("VersionUpdateManager", "ApiManager.baseUrlProvider 未设置,版本检查可能失败")
+            ILog.w("VersionUpdateManager", "ApiManager.baseUrlProvider 未设置,版本检查可能失败")
         }
     }
     
@@ -117,7 +117,7 @@ object VersionUpdateManager {
             } catch (e: Exception) {
                 // 版本检查失败不影响应用启动
                 // 可以记录日志,但不显示错误提示
-                LogHelper.e("VersionUpdateManager", "版本检查失败", e)
+                ILog.e("VersionUpdateManager", "版本检查失败", e)
             }
         }
     }
@@ -167,7 +167,7 @@ object VersionUpdateManager {
             } catch (e: Exception) {
                 // 版本检查失败不影响应用启动
                 // 可以记录日志,但不显示错误提示
-                LogHelper.e("VersionUpdateManager", "版本检查失败", e)
+                ILog.e("VersionUpdateManager", "版本检查失败", e)
             }
         }
     }
@@ -186,7 +186,7 @@ object VersionUpdateManager {
             PackageInfoCompat.getLongVersionCode(packageInfo).toInt()
         } catch (e: PackageManager.NameNotFoundException) {
             // 记录异常
-            LogHelper.e("VersionUpdateManager", "无法获取版本号", e)
+            ILog.e("VersionUpdateManager", "无法获取版本号", e)
             1 // 默认版本号
         }
     }

+ 2 - 2
base-common/src/main/java/com/narutohuo/xindazhou/common/version/repository/VersionRepository.kt

@@ -1,6 +1,6 @@
 package com.narutohuo.xindazhou.common.version.repository
 
-import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.network.ApiBaseRepository
 import com.narutohuo.xindazhou.common.version.datasource.remote.VersionRemoteDataSource
 import com.narutohuo.xindazhou.common.version.model.VersionResponse
@@ -41,7 +41,7 @@ class VersionRepository(
             // 如果请求失败,记录日志但不抛出异常
             // 这样客户端可以正常启动,不会因为版本检查失败而阻塞
             result.onFailure { throwable ->
-                LogHelper.e("VersionRepository", "版本检查失败", throwable)
+                ILog.e("VersionRepository", "版本检查失败", throwable)
                 handleError(throwable)
             }
             null

+ 0 - 128
base-common/src/main/res/layout/fragment_login.xml

@@ -1,128 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="24dp"
-    android:background="@android:color/white">
-
-    <!-- Logo 或应用名称 -->
-    <TextView
-        android:id="@+id/tvAppName"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="新大洲本田"
-        android:textSize="32sp"
-        android:textStyle="bold"
-        android:textColor="@android:color/black"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="80dp" />
-
-    <TextView
-        android:id="@+id/tvSubtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="欢迎登录"
-        android:textSize="16sp"
-        android:textColor="@android:color/darker_gray"
-        app:layout_constraintTop_toBottomOf="@id/tvAppName"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="8dp" />
-
-    <!-- 手机号输入框 -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/tilMobile"
-        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:hint="手机号"
-        app:layout_constraintTop_toBottomOf="@id/tvSubtitle"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="48dp">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/etMobile"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="phone"
-            android:maxLines="1" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <!-- 密码输入框 -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/tilPassword"
-        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:hint="密码"
-        app:passwordToggleEnabled="true"
-        app:layout_constraintTop_toBottomOf="@id/tilMobile"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/etPassword"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="textPassword"
-            android:maxLines="1" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <!-- 登录按钮 -->
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btnLogin"
-        android:layout_width="0dp"
-        android:layout_height="56dp"
-        android:text="登录"
-        android:textSize="16sp"
-        app:layout_constraintTop_toBottomOf="@id/tilPassword"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="32dp" />
-
-    <!-- 注册按钮 -->
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btnRegister"
-        style="@style/Widget.MaterialComponents.Button.TextButton"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="还没有账号?立即注册"
-        app:layout_constraintTop_toBottomOf="@id/btnLogin"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp" />
-
-    <!-- 加载指示器 -->
-    <ProgressBar
-        android:id="@+id/progressBar"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/btnLogin"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp" />
-
-    <!-- 【测试功能】服务器配置按钮(左下角) -->
-    <ImageView
-        android:id="@+id/ivServerConfig"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:src="@android:drawable/ic_menu_preferences"
-        android:background="?attr/selectableItemBackgroundBorderless"
-        android:padding="8dp"
-        android:contentDescription="服务器配置(测试)"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        android:layout_margin="16dp"
-        android:alpha="0.6"
-        tools:ignore="ContentDescription" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
-

+ 0 - 134
base-common/src/main/res/layout/fragment_register.xml

@@ -1,134 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="24dp"
-    android:background="@android:color/white">
-
-    <!-- Logo 或应用名称 -->
-    <TextView
-        android:id="@+id/tvAppName"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="新大洲本田"
-        android:textSize="32sp"
-        android:textStyle="bold"
-        android:textColor="@android:color/black"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="80dp" />
-
-    <TextView
-        android:id="@+id/tvSubtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="欢迎注册"
-        android:textSize="16sp"
-        android:textColor="@android:color/darker_gray"
-        app:layout_constraintTop_toBottomOf="@id/tvAppName"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="8dp" />
-
-    <!-- 手机号输入框 -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/tilMobile"
-        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:hint="手机号"
-        app:layout_constraintTop_toBottomOf="@id/tvSubtitle"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="48dp">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/etMobile"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="phone"
-            android:maxLines="1" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <!-- 密码输入框 -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/tilPassword"
-        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:hint="密码"
-        app:passwordToggleEnabled="true"
-        app:layout_constraintTop_toBottomOf="@id/tilMobile"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/etPassword"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="textPassword"
-            android:maxLines="1" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <!-- 确认密码输入框 -->
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/tilConfirmPassword"
-        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:hint="确认密码"
-        app:passwordToggleEnabled="true"
-        app:layout_constraintTop_toBottomOf="@id/tilPassword"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/etConfirmPassword"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="textPassword"
-            android:maxLines="1" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <!-- 注册按钮 -->
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btnRegister"
-        android:layout_width="0dp"
-        android:layout_height="56dp"
-        android:text="注册"
-        android:textSize="16sp"
-        app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="32dp" />
-
-    <!-- 返回登录按钮 -->
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btnBackToLogin"
-        style="@style/Widget.MaterialComponents.Button.TextButton"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="已有账号?返回登录"
-        app:layout_constraintTop_toBottomOf="@id/btnRegister"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp" />
-
-    <!-- 加载指示器 -->
-    <ProgressBar
-        android:id="@+id/progressBar"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/btnRegister"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginTop="16dp" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
-

+ 8 - 4
base-core/build.gradle

@@ -29,10 +29,6 @@ dependencies {
     // AndroidX Core
     api(libs.androidx.core.ktx)
     
-    // Timber for logging (默认日志实现)
-    // 如果以后不用 Timber,只需修改 TimberLog 实现类
-    api("com.jakewharton.timber:timber:5.0.1")
-    
     // Retrofit & OkHttp(基础网络库,所有模块都需要)
     // 使用 api 传递依赖,让 base-common 和其他模块可以使用
     api("com.squareup.retrofit2:retrofit:2.9.0")
@@ -53,9 +49,17 @@ dependencies {
     // ARouter(路由和依赖注入)
     api("com.alibaba:arouter-api:1.5.2")
     
+    // XXPermissions(权限请求库,所有模块都需要)
+    // 使用 api 传递依赖,让 base-common 和其他模块可以使用
+    api("com.github.getActivity:XXPermissions:18.6")
+    
     // 微信 SDK(仅用于 WXEntryActivity 回调 Activity)
     // 注意:base-core 只依赖微信官方 SDK,不依赖友盟
     // 友盟相关依赖应该在 capability-share 模块中
     api("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
+    
+    // LogcatViewer - 浮动窗口日志输出(开发调试用)
+    // 注意:只在开发环境使用,生产环境使用 NoOpLog
+    implementation("com.github.weijiaxing:LogcatViewer:1.0.3")
 }
 

+ 162 - 0
base-core/src/main/java/com/narutohuo/xindazhou/core/log/FloatingLogButton.kt

@@ -0,0 +1,162 @@
+package com.narutohuo.xindazhou.core.log
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.Build
+import android.util.Log
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.widget.ImageView
+import com.weijiaxing.logviewer.LogcatActivity
+
+/**
+ * 悬浮日志按钮管理器
+ * 
+ * 提供一个小的悬浮按钮,点击后打开 LogcatViewer 查看日志
+ */
+@SuppressLint("ClickableViewAccessibility")
+object FloatingLogButton {
+    
+    private const val TAG = "FloatingLogButton"
+    
+    @Volatile
+    private var floatingButton: View? = null
+    
+    @Volatile
+    private var windowManager: WindowManager? = null
+    
+    @Volatile
+    private var isShowing = false
+    
+    /**
+     * 显示悬浮按钮
+     */
+    fun show(context: Context) {
+        if (isShowing || floatingButton != null) {
+            Log.d(TAG, "悬浮按钮已显示,跳过")
+            return
+        }
+        
+        try {
+            val activity = context as? Activity ?: run {
+                Log.w(TAG, "context 不是 Activity,无法显示悬浮按钮")
+                return
+            }
+            
+            windowManager = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+            
+            // 创建一个简单的圆形按钮
+            floatingButton = ImageView(activity).apply {
+                // 使用 Android 系统图标
+                setImageResource(android.R.drawable.ic_menu_info_details)
+                setBackgroundResource(android.R.drawable.btn_default)
+                setPadding(20, 20, 20, 20)
+            }
+            
+            // 设置窗口参数
+            val params = WindowManager.LayoutParams().apply {
+                width = WindowManager.LayoutParams.WRAP_CONTENT
+                height = WindowManager.LayoutParams.WRAP_CONTENT
+                type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+                } else {
+                    @Suppress("DEPRECATION")
+                    WindowManager.LayoutParams.TYPE_PHONE
+                }
+                flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                format = PixelFormat.TRANSLUCENT
+                gravity = Gravity.TOP or Gravity.END
+                x = 20
+                y = 200
+            }
+            
+            // 添加到窗口
+            windowManager?.addView(floatingButton, params)
+            isShowing = true
+            
+            // 点击事件:打开 LogcatViewer
+            // 注意:LogcatActivity 已在 AndroidManifest 中设置为 singleTask,确保只有一个实例
+            floatingButton?.setOnClickListener {
+                Log.d(TAG, "悬浮按钮被点击,打开 LogcatViewer")
+                try {
+                    LogcatActivity.launch(activity)
+                } catch (e: Exception) {
+                    Log.e(TAG, "打开 LogcatViewer 失败", e)
+                }
+            }
+            
+            // 长按事件:隐藏按钮
+            floatingButton?.setOnLongClickListener {
+                Log.d(TAG, "悬浮按钮被长按,隐藏按钮")
+                hide()
+                true
+            }
+            
+            // 支持拖动
+            var initialX = 0
+            var initialY = 0
+            var initialTouchX = 0f
+            var initialTouchY = 0f
+            
+            floatingButton?.setOnTouchListener { v, event ->
+                when (event.action) {
+                    MotionEvent.ACTION_DOWN -> {
+                        initialX = params.x
+                        initialY = params.y
+                        initialTouchX = event.rawX
+                        initialTouchY = event.rawY
+                        false
+                    }
+                    MotionEvent.ACTION_MOVE -> {
+                        params.x = initialX + (initialTouchX - event.rawX).toInt()
+                        params.y = initialY + (event.rawY - initialTouchY).toInt()
+                        windowManager?.updateViewLayout(floatingButton, params)
+                        true
+                    }
+                    else -> false
+                }
+            }
+            
+            Log.d(TAG, "悬浮按钮显示成功")
+            
+        } catch (e: Exception) {
+            Log.e(TAG, "显示悬浮按钮失败", e)
+            isShowing = false
+            floatingButton = null
+        }
+    }
+    
+    /**
+     * 隐藏悬浮按钮
+     */
+    fun hide() {
+        try {
+            floatingButton?.let {
+                windowManager?.removeView(it)
+                floatingButton = null
+                isShowing = false
+                Log.d(TAG, "悬浮按钮已隐藏")
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "隐藏悬浮按钮失败", e)
+        }
+    }
+    
+    /**
+     * 切换显示/隐藏
+     */
+    fun toggle(context: Context) {
+        if (isShowing) {
+            hide()
+        } else {
+            show(context)
+        }
+    }
+}
+

+ 69 - 23
base-core/src/main/java/com/narutohuo/xindazhou/core/log/ILog.kt

@@ -14,7 +14,7 @@ enum class LogLevel {
  * 日志服务接口
  * 统一封装日志记录、日志文件管理、日志上传、崩溃收集
  * 
- * 实现类:TimberLog(开发环境)、NoOpLog(生产环境)
+ * 实现类:LogcatViewerLog(开发环境)、NoOpLog(生产环境)
  */
 interface ILogger {
     /**
@@ -56,6 +56,17 @@ interface ILogger {
      * 收集崩溃信息
      */
     fun collectCrash(throwable: Throwable)
+    
+    /**
+     * 启动日志查看器(如果支持的话)
+     * 例如 LogcatViewer 的浮动窗口
+     * 
+     * @param context 上下文
+     */
+    fun launchLogcatViewer(context: android.content.Context) {
+        // 默认实现:什么都不做
+        // LogcatViewerLog 会重写这个方法
+    }
 }
 
 /**
@@ -64,7 +75,7 @@ interface ILogger {
  * 所有SDK模块通过静态方法进行日志记录
  * 
  * **实现选择**:
- * - **开发环境**(Debug):使用 TimberLog(输出详细日志
+ * - **开发环境**(Debug):使用 LogcatViewerLog(输出详细日志,支持浮动窗口查看
  * - **生产环境**(Release):使用 NoOpLog(不输出日志,提升性能)
  * 
  * **使用示例**:
@@ -73,11 +84,14 @@ interface ILogger {
  * // Debug 版本自动启用日志,Release 版本自动禁用
  * ILog.init(enableLogging = BuildConfig.DEBUG)
  * 
- * // 2. 静态方法调用(推荐,类似 Android Log.d())
+ * // 2. 启动 LogcatViewer 浮动窗口(可选,在需要的地方调用)
+ * LogcatActivity.launch(context)
+ * 
+ * // 3. 静态方法调用(推荐,类似 Android Log.d())
  * ILog.d("Tag", "调试信息")
  * ILog.e("Tag", "错误信息", exception)
  * 
- * // 3. 手动控制(可选)
+ * // 4. 手动控制(可选)
  * ILog.init(enableLogging = false) // 生产环境禁用日志
  * ILog.init(enableLogging = true)   // 开发环境启用日志
  * ```
@@ -89,31 +103,52 @@ object ILog {
     /**
      * 初始化日志实现(在 Application 中调用,可选)
      * 
-     * **自动选择实现**:
-     * - 如果 `enableLogging = true`(默认):使用 TimberLog(开发环境
-     * - 如果 `enableLogging = false`:使用 NoOpLog(生产环境,不输出日志)
-     * 
-     * **使用示例**:
-     * ```kotlin
-     * // 方式1:自动选择(推荐)
-     * // Debug 版本自动启用日志,Release 版本自动禁用
-     * ILog.init(enableLogging = BuildConfig.DEBUG)
-     * 
-     * // 方式2:手动指定实现
-     * ILog.init(com.narutohuo.xindazhou.core.log.impl.NoOpLog.getInstance()) // 生产环境
-     * ILog.init(com.narutohuo.xindazhou.core.log.impl.TimberLog.getInstance()) // 开发环境
-     * ```
+ * **自动选择实现**:
+ * - 如果 `enableLogging = true`(默认):使用 LogcatViewerLog(开发环境,支持浮动窗口查看
+ * - 如果 `enableLogging = false`:使用 NoOpLog(生产环境,不输出日志)
+ * 
+ * **使用示例**:
+ * ```kotlin
+ * // 方式1:自动选择(推荐)
+ * // Debug 版本自动启用日志,Release 版本自动禁用
+ * ILog.init(enableLogging = BuildConfig.DEBUG)
+ * 
+ * // 方式2:手动指定实现
+ * ILog.init(com.narutohuo.xindazhou.core.log.impl.NoOpLog.getInstance()) // 生产环境
+ * ILog.init(com.narutohuo.xindazhou.core.log.impl.LogcatViewerLog.getInstance()) // 开发环境
+ * ```
      * 
+     * @param application Application 实例(用于自动启动 LogcatViewer,如果传入会自动注册 ActivityLifecycleCallbacks)
      * @param enableLogging 是否启用日志(默认 true)
      * @param logImpl 自定义日志实现(可选,如果指定则忽略 enableLogging 参数)
      */
     @JvmStatic
-    fun init(enableLogging: Boolean = true, logImpl: ILogger? = null) {
+    fun init(application: android.app.Application, enableLogging: Boolean = true, logImpl: ILogger? = null) {
         instance = when {
             logImpl != null -> logImpl // 如果指定了自定义实现,优先使用
-            enableLogging -> com.narutohuo.xindazhou.core.log.impl.TimberLog.getInstance() // 启用日志
+            enableLogging -> com.narutohuo.xindazhou.core.log.impl.LogcatViewerLog.getInstance() // 启用日志(使用 LogcatViewer)
             else -> com.narutohuo.xindazhou.core.log.impl.NoOpLog.getInstance() // 禁用日志(生产环境)
         }
+        
+        // 如果使用的是 LogcatViewerLog,自动注册 ActivityLifecycleCallbacks 来启动 LogcatViewer
+        if (enableLogging && logImpl == null) {
+            com.narutohuo.xindazhou.core.log.impl.LogcatViewerLog.registerActivityLifecycle(application)
+        }
+    }
+    
+    /**
+     * 初始化日志实现(兼容旧版本,不传入 Application 则不自动启动 LogcatViewer)
+     * 
+     * @param enableLogging 是否启用日志(默认 true)
+     * @param logImpl 自定义日志实现(可选,如果指定则忽略 enableLogging 参数)
+     */
+    @JvmStatic
+    fun init(enableLogging: Boolean = true, logImpl: ILogger? = null) {
+        instance = when {
+            logImpl != null -> logImpl
+            enableLogging -> com.narutohuo.xindazhou.core.log.impl.LogcatViewerLog.getInstance()
+            else -> com.narutohuo.xindazhou.core.log.impl.NoOpLog.getInstance()
+        }
     }
     
     /**
@@ -129,16 +164,16 @@ object ILog {
     /**
      * 获取日志实现实例(单例)
      * 
-     * 如果未初始化,默认使用 TimberLog(开发环境)
+     * 如果未初始化,默认使用 LogcatViewerLog(开发环境)
      * 生产环境建议在 Application 中调用 init() 进行初始化
      */
     private fun getInstance(): ILogger {
         if (instance == null) {
             synchronized(this) {
                 if (instance == null) {
-                    // 默认使用 TimberLog(开发环境)
+                    // 默认使用 LogcatViewerLog(开发环境)
                     // 生产环境建议在 Application 中调用 init(enableLogging = false)
-                    instance = com.narutohuo.xindazhou.core.log.impl.TimberLog.getInstance()
+                    instance = com.narutohuo.xindazhou.core.log.impl.LogcatViewerLog.getInstance()
                 }
             }
         }
@@ -200,5 +235,16 @@ object ILog {
     fun collectCrash(throwable: Throwable) {
         getInstance().collectCrash(throwable)
     }
+    
+    /**
+     * 启动日志查看器(静态方法)
+     * 如果当前使用的是 LogcatViewerLog,会启动浮动窗口
+     * 
+     * @param context 上下文
+     */
+    @JvmStatic
+    fun launchLogcatViewer(context: android.content.Context) {
+        getInstance().launchLogcatViewer(context)
+    }
 }
 

+ 232 - 0
base-core/src/main/java/com/narutohuo/xindazhou/core/log/impl/LogcatViewerLog.kt

@@ -0,0 +1,232 @@
+package com.narutohuo.xindazhou.core.log.impl
+
+import android.content.Context
+import android.util.Log
+import com.narutohuo.xindazhou.core.log.ILogger
+import com.narutohuo.xindazhou.core.log.LogLevel
+import com.weijiaxing.logviewer.LogcatActivity
+
+/**
+ * LogcatViewer 日志实现
+ * 
+ * 使用 Android Log 输出日志,LogcatViewer 会自动捕获 logcat 输出并显示在浮动窗口中
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化
+ * ILog.init(LogcatViewerLog.getInstance())
+ * 
+ * // 启动 LogcatViewer 浮动窗口(在需要的地方调用)
+ * LogcatActivity.launch(context)
+ * ```
+ * 
+ * 注意:
+ * - LogcatViewer 会自动捕获 logcat 输出,无需额外配置
+ * - 日志会同时输出到 logcat 和 LogcatViewer 浮动窗口
+ * - 可以通过 LogcatActivity.launch() 启动/关闭浮动窗口
+ */
+class LogcatViewerLog private constructor() : ILogger {
+    
+    companion object {
+        @Volatile
+        private var INSTANCE: LogcatViewerLog? = null
+        
+        @Volatile
+        private var lifecycleCallback: android.app.Application.ActivityLifecycleCallbacks? = null
+        
+        /**
+         * 获取单例实例
+         */
+        @JvmStatic
+        fun getInstance(): ILogger {
+            return INSTANCE ?: synchronized(this) {
+                INSTANCE ?: LogcatViewerLog().also { INSTANCE = it }
+            }
+        }
+        
+        /**
+         * 注册 ActivityLifecycleCallbacks,自动在第一个 Activity resume 时启动 LogcatViewer
+         */
+        @JvmStatic
+        fun registerActivityLifecycle(application: android.app.Application) {
+            if (lifecycleCallback != null) {
+                // 已经注册过了,不重复注册
+                return
+            }
+            
+            lifecycleCallback = object : android.app.Application.ActivityLifecycleCallbacks {
+                @Volatile
+                private var hasLaunched = false
+                @Volatile
+                private var isWaitingForPermission = false
+                
+                override fun onActivityResumed(activity: android.app.Activity) {
+                    // 如果正在等待权限授予,检查权限状态
+                    if (isWaitingForPermission) {
+                        val hasPermission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+                            android.provider.Settings.canDrawOverlays(activity)
+                        } else {
+                            true
+                        }
+                        
+                        if (hasPermission) {
+                            android.util.Log.d("LogcatViewerLog", "检测到权限已授予,重新启动 LogcatViewer")
+                            isWaitingForPermission = false
+                            // 延迟启动,避免全屏模式影响
+                            activity.window.decorView.postDelayed({
+                                INSTANCE?.launchLogcatViewer(activity)
+                            }, 500)
+                        } else {
+                            android.util.Log.d("LogcatViewerLog", "权限仍未授予,继续等待")
+                        }
+                        return
+                    }
+                    
+                    // 只在第一个 Activity resume 时启动一次
+                    if (!hasLaunched) {
+                        hasLaunched = true
+                        android.util.Log.d("LogcatViewerLog", "检测到第一个 Activity resume: ${activity.javaClass.simpleName}")
+                        // 延迟启动,避免全屏模式影响
+                        activity.window.decorView.postDelayed({
+                            android.util.Log.d("LogcatViewerLog", "开始从 ActivityLifecycleCallbacks 启动 LogcatViewer")
+                            // 检查启动结果,如果是因为权限问题失败,标记为等待权限
+                            val launched = INSTANCE?.launchLogcatViewerInternal(activity) ?: false
+                            if (!launched) {
+                                isWaitingForPermission = true
+                                android.util.Log.d("LogcatViewerLog", "标记为等待权限授予状态")
+                            }
+                        }, 800)
+                    }
+                }
+                
+                override fun onActivityPaused(activity: android.app.Activity) {}
+                override fun onActivityStarted(activity: android.app.Activity) {}
+                override fun onActivityStopped(activity: android.app.Activity) {}
+                override fun onActivityCreated(activity: android.app.Activity, savedInstanceState: android.os.Bundle?) {}
+                override fun onActivitySaveInstanceState(activity: android.app.Activity, outState: android.os.Bundle) {}
+                override fun onActivityDestroyed(activity: android.app.Activity) {}
+            }
+            
+            lifecycleCallback?.let {
+                application.registerActivityLifecycleCallbacks(it)
+                android.util.Log.d("LogcatViewerLog", "已注册 ActivityLifecycleCallbacks,将在第一个 Activity resume 时自动启动 LogcatViewer")
+            }
+        }
+    }
+    
+    override fun d(tag: String, message: String) {
+        Log.d(tag, message)
+    }
+    
+    override fun i(tag: String, message: String) {
+        Log.i(tag, message)
+    }
+    
+    override fun w(tag: String, message: String) {
+        Log.w(tag, message)
+    }
+    
+    override fun e(tag: String, message: String, throwable: Throwable?) {
+        if (throwable != null) {
+            Log.e(tag, message, throwable)
+        } else {
+            Log.e(tag, message)
+        }
+    }
+    
+    override fun log(level: LogLevel, tag: String, message: String, throwable: Throwable?) {
+        when (level) {
+            LogLevel.DEBUG -> d(tag, message)
+            LogLevel.INFO -> i(tag, message)
+            LogLevel.WARN -> w(tag, message)
+            LogLevel.ERROR -> e(tag, message, throwable)
+        }
+    }
+    
+    override fun uploadLogs() {
+        // LogcatViewer 默认不提供日志上传功能,可以在这里扩展
+        // 或者由 app 模块提供自定义实现
+    }
+    
+    override fun collectCrash(throwable: Throwable) {
+        // 记录崩溃信息到 logcat,LogcatViewer 会自动捕获
+        Log.e("Crash", "崩溃信息", throwable)
+        // 注意:生产环境可能仍然需要收集崩溃信息,可以在这里扩展
+        // 例如:上传到崩溃收集服务(Firebase Crashlytics、Bugly 等)
+    }
+    
+    override fun launchLogcatViewer(context: Context) {
+        launchLogcatViewerInternal(context)
+    }
+    
+    /**
+     * 内部方法:启动 LogcatViewer,返回是否成功启动(true=成功,false=需要权限)
+     */
+    private fun launchLogcatViewerInternal(context: Context): Boolean {
+        try {
+            // 检查 context 类型,必须是 Activity context
+            if (context !is android.app.Activity) {
+                Log.w("LogcatViewerLog", "launchLogcatViewer 需要 Activity context,当前是: ${context.javaClass.simpleName},跳过启动")
+                // 如果是 Application context,不执行启动,等待 Activity context
+                return false
+            }
+            
+            Log.d("LogcatViewerLog", "=== 开始启动 LogcatViewer ===")
+            
+            // 检查悬浮窗权限
+            val hasOverlayPermission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+                android.provider.Settings.canDrawOverlays(context)
+            } else {
+                true
+            }
+            
+            Log.d("LogcatViewerLog", "悬浮窗权限状态: $hasOverlayPermission, SDK版本: ${android.os.Build.VERSION.SDK_INT}")
+            
+            if (!hasOverlayPermission) {
+                Log.w("LogcatViewerLog", "悬浮窗权限未授予,自动跳转到权限设置页面")
+                // 自动跳转到权限设置页面(Application context 需要添加 FLAG_ACTIVITY_NEW_TASK)
+                try {
+                    val intent = android.content.Intent(
+                        android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                        android.net.Uri.parse("package:${context.packageName}")
+                    ).apply {
+                        // 确保可以从 Application context 启动
+                        addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
+                    }
+                    context.startActivity(intent)
+                    Log.d("LogcatViewerLog", "已自动跳转到权限设置页面,请授予'在其他应用上层显示'权限")
+                    
+                    // 显示 Toast 提示用户(如果可能的话)
+                    try {
+                        android.widget.Toast.makeText(
+                            context, 
+                            "请在设置中开启'在其他应用上层显示'权限,然后返回应用", 
+                            android.widget.Toast.LENGTH_LONG
+                        ).show()
+                    } catch (e: Exception) {
+                        // Toast 可能失败,忽略
+                    }
+                } catch (e: Exception) {
+                    Log.e("LogcatViewerLog", "跳转到权限设置失败", e)
+                    e.printStackTrace()
+                }
+                return false // 返回 false 表示需要权限
+            }
+            
+            // 有权限,显示悬浮按钮(点击按钮打开 LogcatViewer)
+            Log.d("LogcatViewerLog", "开始显示悬浮日志按钮...")
+            com.narutohuo.xindazhou.core.log.FloatingLogButton.show(context)
+            Log.d("LogcatViewerLog", "悬浮日志按钮显示完成")
+            
+            return true // 返回 true 表示成功启动
+            
+        } catch (e: Exception) {
+            Log.e("LogcatViewerLog", "启动 LogcatViewer 异常", e)
+            Log.e("LogcatViewerLog", "异常类型: ${e.javaClass.name}")
+            Log.e("LogcatViewerLog", "异常消息: ${e.message}")
+            e.printStackTrace()
+            return false
+        }
+    }
+}
+

+ 0 - 75
base-core/src/main/java/com/narutohuo/xindazhou/core/log/impl/TimberLog.kt

@@ -1,75 +0,0 @@
-package com.narutohuo.xindazhou.core.log.impl
-
-import com.narutohuo.xindazhou.core.log.ILogger
-import com.narutohuo.xindazhou.core.log.LogLevel
-import timber.log.Timber
-
-/**
- * Timber 日志实现(默认实现,单例)
- * 
- * 封装 Timber 日志库,实现 ILog 接口
- * 以后如果不用 Timber,只需修改此实现类,或提供新的实现类
- * 
- * 使用方式:
- * ```kotlin
- * val log: ILog = TimberLog.getInstance()
- * ```
- */
-class TimberLog private constructor() : ILogger {
-    
-    companion object {
-        @Volatile
-        private var INSTANCE: TimberLog? = null
-        
-        /**
-         * 获取单例实例
-         */
-        @JvmStatic
-        fun getInstance(): ILogger {
-            return INSTANCE ?: synchronized(this) {
-                INSTANCE ?: TimberLog().also { INSTANCE = it }
-            }
-        }
-    }
-    
-    override fun d(tag: String, message: String) {
-        Timber.tag(tag).d(message)
-    }
-    
-    override fun i(tag: String, message: String) {
-        Timber.tag(tag).i(message)
-    }
-    
-    override fun w(tag: String, message: String) {
-        Timber.tag(tag).w(message)
-    }
-    
-    override fun e(tag: String, message: String, throwable: Throwable?) {
-        if (throwable != null) {
-            Timber.tag(tag).e(throwable, message)
-        } else {
-            Timber.tag(tag).e(message)
-        }
-    }
-    
-    override fun log(level: LogLevel, tag: String, message: String, throwable: Throwable?) {
-        when (level) {
-            LogLevel.DEBUG -> d(tag, message)
-            LogLevel.INFO -> i(tag, message)
-            LogLevel.WARN -> w(tag, message)
-            LogLevel.ERROR -> e(tag, message, throwable)
-        }
-    }
-    
-    override fun uploadLogs() {
-        // Timber 默认不提供日志上传功能,可以在这里扩展
-        // 或者由 app 模块提供自定义实现
-    }
-    
-    override fun collectCrash(throwable: Throwable) {
-        // Timber 默认不提供崩溃收集功能,可以在这里扩展
-        // 或者由 app 模块提供自定义实现
-        Timber.e(throwable, "崩溃信息")
-    }
-}
-

+ 212 - 0
base-core/src/main/java/com/narutohuo/xindazhou/core/permission/PermissionHelper.kt

@@ -0,0 +1,212 @@
+package com.narutohuo.xindazhou.core.permission
+
+import android.app.Activity
+import android.content.Context
+import androidx.fragment.app.FragmentActivity
+import com.hjq.permissions.OnPermissionCallback
+import com.hjq.permissions.XXPermissions
+
+/**
+ * 权限管理工具类
+ * 
+ * 基于 XXPermissions 封装,提供统一的权限请求接口
+ * 
+ * 特点:
+ * - 无需注册 ActivityResultLauncher
+ * - 自动处理 Android 各版本兼容性
+ * - 自动处理权限被永久拒绝的情况
+ * - 支持链式调用
+ * 
+ * 使用示例:
+ * ```kotlin
+ * // 1. 最简单的用法:请求单个权限
+ * PermissionHelper.request(activity, Manifest.permission.CAMERA) {
+ *     // 权限已授予,执行操作
+ *     openCamera()
+ * }
+ * 
+ * // 2. 请求多个权限
+ * PermissionHelper.request(activity,
+ *     Manifest.permission.CAMERA,
+ *     Manifest.permission.RECORD_AUDIO,
+ *     onGranted = {
+ *         // 所有权限都已授予
+ *         startVideoRecord()
+ *     },
+ *     onDenied = {
+ *         // 有权限被拒绝
+ *         showToast("需要相关权限才能使用此功能")
+ *     }
+ * )
+ * 
+ * // 3. 需要知道具体哪些权限被授予/拒绝
+ * PermissionHelper.requestWithResult(activity,
+ *     Manifest.permission.CAMERA,
+ *     Manifest.permission.WRITE_EXTERNAL_STORAGE
+ * ) { granted, denied ->
+ *     if (granted.contains(Manifest.permission.CAMERA)) {
+ *         // 相机权限已授予
+ *     }
+ *     if (denied.isNotEmpty()) {
+ *         // 处理被拒绝的权限
+ *     }
+ * }
+ * 
+ * // 4. 检查权限
+ * if (PermissionHelper.isGranted(context, Manifest.permission.CAMERA)) {
+ *     openCamera()
+ * }
+ * 
+ * // 5. 被永久拒绝时,引导用户去设置页
+ * PermissionHelper.request(activity, 
+ *     Manifest.permission.CAMERA,
+ *     onGranted = { openCamera() },
+ *     onDenied = {
+ *         PermissionHelper.showPermissionSettingDialog(
+ *             activity, 
+ *             "相机权限已被拒绝,请前往设置页面手动授予"
+ *         )
+ *     }
+ * )
+ * ```
+ */
+object PermissionHelper {
+    
+    /**
+     * 检查单个权限是否已授予
+     * 
+     * @param context 上下文
+     * @param permission 权限名称
+     * @return true 表示已授予,false 表示未授予
+     */
+    fun isGranted(context: Context, permission: String): Boolean {
+        return XXPermissions.isGranted(context, permission)
+    }
+    
+    /**
+     * 检查多个权限是否都已授予
+     * 
+     * @param context 上下文
+     * @param permissions 权限名称数组
+     * @return true 表示所有权限都已授予,false 表示至少有一个未授予
+     */
+    fun isGranted(context: Context, vararg permissions: String): Boolean {
+        return XXPermissions.isGranted(context, *permissions)
+    }
+    
+    /**
+     * 请求权限(简化版)
+     * 
+     * 适用于大多数场景,只关心是否全部授予
+     * 
+     * @param activity Activity 实例
+     * @param permissions 要请求的权限列表
+     * @param onGranted 所有权限都已授予时的回调
+     * @param onDenied 有权限被拒绝时的回调(可选)
+     */
+    fun request(
+        activity: FragmentActivity,
+        vararg permissions: String,
+        onGranted: () -> Unit,
+        onDenied: (() -> Unit)? = null
+    ) {
+        // 先检查是否已授予
+        if (XXPermissions.isGranted(activity, *permissions)) {
+            onGranted()
+            return
+        }
+        
+        // 请求权限
+        XXPermissions.with(activity)
+            .permission(*permissions)
+            .request(object : OnPermissionCallback {
+                override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
+                    if (allGranted) {
+                        onGranted()
+                    } else {
+                        onDenied?.invoke()
+                    }
+                }
+                
+                override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
+                    onDenied?.invoke()
+                }
+            })
+    }
+    
+    /**
+     * 请求权限(详细结果版)
+     * 
+     * 适用于需要知道具体哪些权限被授予、哪些被拒绝的场景
+     * 
+     * @param activity Activity 实例
+     * @param permissions 要请求的权限列表
+     * @param callback 权限请求结果回调(已授予列表,被拒绝列表,是否永久拒绝)
+     */
+    fun requestWithResult(
+        activity: FragmentActivity,
+        vararg permissions: String,
+        callback: (granted: List<String>, denied: List<String>, doNotAskAgain: Boolean) -> Unit
+    ) {
+        XXPermissions.with(activity)
+            .permission(*permissions)
+            .request(object : OnPermissionCallback {
+                override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
+                    if (allGranted) {
+                        callback(permissions, emptyList(), false)
+                    } else {
+                        // 这个分支理论上不会执行,因为 allGranted=false 会走 onDenied
+                        callback(permissions, emptyList(), false)
+                    }
+                }
+                
+                override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
+                    // 找出哪些被授予了,哪些被拒绝了
+                    val grantedList = mutableListOf<String>()
+                    val deniedList = mutableListOf<String>()
+                    
+                    for (permission in permissions) {
+                        if (XXPermissions.isGranted(activity, permission)) {
+                            grantedList.add(permission)
+                        } else {
+                            deniedList.add(permission)
+                        }
+                    }
+                    
+                    callback(grantedList, deniedList, doNotAskAgain)
+                }
+            })
+    }
+    
+    /**
+     * 显示权限设置对话框
+     * 
+     * 当用户永久拒绝权限时,引导用户前往设置页面手动授予
+     * 
+     * @param activity Activity 实例
+     * @param message 提示消息
+     * @param positiveText 确定按钮文本
+     * @param negativeText 取消按钮文本
+     * @param onConfirm 用户点击确定后的回调(默认跳转到设置页)
+     */
+    fun showPermissionSettingDialog(
+        activity: Activity,
+        message: String = "权限被拒绝,请前往设置页面手动授予",
+        positiveText: String = "去设置",
+        negativeText: String = "取消",
+        onConfirm: (() -> Unit)? = null
+    ) {
+        XXPermissions.startPermissionActivity(activity, message)
+        onConfirm?.invoke()
+    }
+    
+    /**
+     * 直接跳转到应用权限设置页面
+     * 
+     * @param activity Activity 实例
+     */
+    fun startPermissionActivity(activity: Activity) {
+        XXPermissions.startPermissionActivity(activity)
+    }
+}
+

+ 5 - 0
base-core/src/main/java/com/narutohuo/xindazhou/core/push/IPushService.kt

@@ -75,5 +75,10 @@ interface IPushService {
      * @return true 表示推送已开启,false 表示推送已停止或未初始化
      */
     fun isPushEnabled(): Boolean
+    
+    // ========== 扩展方法(可选实现) ==========
+    // 注意:这些方法是可选的,具体实现类可以提供更多功能
+    // 为了保持接口简洁,这里只定义核心方法
+    // 更多功能可以通过具体实现类(PushServiceImpl)直接调用
 }
 

+ 878 - 4
base-core/src/main/java/com/narutohuo/xindazhou/core/util/IUtil.kt

@@ -1,30 +1,904 @@
 package com.narutohuo.xindazhou.core.util
 
+import android.content.Context
+
 /**
  * 工具类接口
- * 通用工具方法、时间处理、字符串处理、加密解密
+ * 通用工具方法、时间处理、字符串处理、加密解密、设备信息、单位转换等
  * 
  * 所有SDK模块通过此接口使用工具类
  * 实现类由app模块提供
  */
 interface IUtil {
+    
+    // ==================== 时间处理 ====================
+    
     /**
-     * 时间处理
+     * 格式化时间戳为字符串
+     * @param timestamp 时间戳(毫秒)
+     * @param pattern 格式模式(如:yyyy-MM-dd HH:mm:ss)
+     * @return 格式化后的时间字符串
      */
     fun formatTime(timestamp: Long, pattern: String): String
+    
+    /**
+     * 解析时间字符串为时间戳
+     * @param timeString 时间字符串
+     * @param pattern 格式模式(如:yyyy-MM-dd HH:mm:ss)
+     * @return 时间戳(毫秒),解析失败返回 null
+     */
     fun parseTime(timeString: String, pattern: String): Long?
     
     /**
-     * 字符串处理
+     * 获取当前时间戳(毫秒)
+     */
+    fun getCurrentTimeMillis(): Long
+    
+    /**
+     * 格式化当前时间为字符串
+     * @param pattern 格式模式
+     * @return 格式化后的时间字符串
+     */
+    fun formatCurrentTime(pattern: String): String
+    
+    /**
+     * 格式化日期(只包含日期部分)
+     * @param timestamp 时间戳(毫秒)
+     * @param pattern 格式模式(如:yyyy-MM-dd)
+     * @return 格式化后的日期字符串
+     */
+    fun formatDate(timestamp: Long, pattern: String): String
+    
+    /**
+     * 获取相对时间描述(如:刚刚、5分钟前、1小时前、昨天、3天前)
+     * @param timestamp 时间戳(毫秒)
+     * @return 相对时间描述
+     */
+    fun getRelativeTime(timestamp: Long): String
+    
+    // ==================== 字符串处理 ====================
+    
+    /**
+     * 加密字符串
+     * @param data 原始字符串
+     * @return 加密后的字符串
      */
     fun encryptString(data: String): String
+    
+    /**
+     * 解密字符串
+     * @param data 加密后的字符串
+     * @return 解密后的原始字符串
+     */
     fun decryptString(data: String): String
+    
+    /**
+     * MD5 加密
+     * @param data 原始字符串
+     * @return MD5 加密后的字符串(32位小写)
+     */
     fun md5(data: String): String
     
     /**
-     * 数据转换
+     * Base64 编码
+     * @param data 原始字符串
+     * @return Base64 编码后的字符串
+     */
+    fun base64Encode(data: String): String
+    
+    /**
+     * Base64 解码
+     * @param data Base64 编码后的字符串
+     * @return 解码后的原始字符串
+     */
+    fun base64Decode(data: String): String?
+    
+    /**
+     * URL 编码
+     * @param data 原始字符串
+     * @return URL 编码后的字符串
+     */
+    fun urlEncode(data: String): String
+    
+    /**
+     * URL 解码
+     * @param data URL 编码后的字符串
+     * @return 解码后的原始字符串
+     */
+    fun urlDecode(data: String): String?
+    
+    /**
+     * 判断字符串是否为空(null、空字符串、纯空格)
+     * @param str 待判断的字符串
+     * @return true 表示为空
+     */
+    fun isEmpty(str: String?): Boolean
+    
+    /**
+     * 判断字符串是否不为空
+     * @param str 待判断的字符串
+     * @return true 表示不为空
+     */
+    fun isNotEmpty(str: String?): Boolean
+    
+    /**
+     * 去除字符串首尾空格
+     * @param str 原始字符串
+     * @return 去除空格后的字符串
+     */
+    fun trim(str: String?): String?
+    
+    /**
+     * 字符串截取(安全截取,防止越界)
+     * @param str 原始字符串
+     * @param start 起始位置
+     * @param end 结束位置
+     * @return 截取后的字符串
+     */
+    fun substring(str: String, start: Int, end: Int): String?
+    
+    // ==================== 数据转换 ====================
+    
+    /**
+     * 对象转 JSON 字符串
+     * @param obj 待转换的对象
+     * @return JSON 字符串
      */
     fun <T> toJson(obj: T): String
+    
+    /**
+     * JSON 字符串转对象
+     * @param json JSON 字符串
+     * @param clazz 目标类型
+     * @return 转换后的对象,转换失败返回 null
+     */
     fun <T> fromJson(json: String, clazz: Class<T>): T?
+    
+    /**
+     * JSON 字符串转对象(使用 TypeToken)
+     * @param json JSON 字符串
+     * @param typeToken 类型标记(如:object : TypeToken<List<String>>() {})
+     * @return 转换后的对象,转换失败返回 null
+     */
+    fun <T> fromJson(json: String, typeToken: Any): T?
+    
+    /**
+     * 数字转字符串(安全转换,null 返回空字符串)
+     * @param number 数字
+     * @return 字符串
+     */
+    fun numberToString(number: Number?): String
+    
+    /**
+     * 字符串转整数(安全转换,失败返回默认值)
+     * @param str 字符串
+     * @param defaultValue 默认值
+     * @return 整数
+     */
+    fun stringToInt(str: String?, defaultValue: Int = 0): Int
+    
+    /**
+     * 字符串转长整数(安全转换,失败返回默认值)
+     * @param str 字符串
+     * @param defaultValue 默认值
+     * @return 长整数
+     */
+    fun stringToLong(str: String?, defaultValue: Long = 0L): Long
+    
+    /**
+     * 字符串转浮点数(安全转换,失败返回默认值)
+     * @param str 字符串
+     * @param defaultValue 默认值
+     * @return 浮点数
+     */
+    fun stringToFloat(str: String?, defaultValue: Float = 0f): Float
+    
+    /**
+     * 字符串转双精度浮点数(安全转换,失败返回默认值)
+     * @param str 字符串
+     * @param defaultValue 默认值
+     * @return 双精度浮点数
+     */
+    fun stringToDouble(str: String?, defaultValue: Double = 0.0): Double
+    
+    /**
+     * 字符串转布尔值(安全转换,失败返回默认值)
+     * @param str 字符串
+     * @param defaultValue 默认值
+     * @return 布尔值
+     */
+    fun stringToBoolean(str: String?, defaultValue: Boolean = false): Boolean
+    
+    // ==================== 设备信息 ====================
+    
+    /**
+     * 获取屏幕宽度(像素)
+     * @param context 上下文
+     * @return 屏幕宽度(px)
+     */
+    fun getScreenWidth(context: Context): Int
+    
+    /**
+     * 获取屏幕高度(像素)
+     * @param context 上下文
+     * @return 屏幕高度(px)
+     */
+    fun getScreenHeight(context: Context): Int
+    
+    /**
+     * 获取状态栏高度(像素)
+     * @param context 上下文
+     * @return 状态栏高度(px)
+     */
+    fun getStatusBarHeight(context: Context): Int
+    
+    /**
+     * 获取导航栏高度(像素)
+     * @param context 上下文
+     * @return 导航栏高度(px)
+     */
+    fun getNavigationBarHeight(context: Context): Int
+    
+    /**
+     * 获取设备 ID(唯一标识)
+     * @param context 上下文
+     * @return 设备 ID
+     */
+    fun getDeviceId(context: Context): String
+    
+    /**
+     * 获取应用版本号
+     * @param context 上下文
+     * @return 版本号(如:1.0.0)
+     */
+    fun getAppVersionName(context: Context): String
+    
+    /**
+     * 获取应用版本码
+     * @param context 上下文
+     * @return 版本码(如:1)
+     */
+    fun getAppVersionCode(context: Context): Int
+    
+    /**
+     * 获取应用包名
+     * @param context 上下文
+     * @return 包名
+     */
+    fun getPackageName(context: Context): String
+    
+    // ==================== 单位转换 ====================
+    
+    /**
+     * dp 转 px
+     * @param context 上下文
+     * @param dp dp 值
+     * @return px 值
+     */
+    fun dpToPx(context: Context, dp: Float): Int
+    
+    /**
+     * px 转 dp
+     * @param context 上下文
+     * @param px px 值
+     * @return dp 值
+     */
+    fun pxToDp(context: Context, px: Float): Float
+    
+    /**
+     * sp 转 px
+     * @param context 上下文
+     * @param sp sp 值
+     * @return px 值
+     */
+    fun spToPx(context: Context, sp: Float): Int
+    
+    /**
+     * px 转 sp
+     * @param context 上下文
+     * @param px px 值
+     * @return sp 值
+     */
+    fun pxToSp(context: Context, px: Float): Float
+    
+    // ==================== 文件操作 ====================
+    
+    /**
+     * 读取文件内容(文本)
+     * @param filePath 文件路径
+     * @param charset 字符编码(默认 UTF-8)
+     * @return 文件内容,读取失败返回 null
+     */
+    fun readFile(filePath: String, charset: String = "UTF-8"): String?
+    
+    /**
+     * 写入文件内容(文本)
+     * @param filePath 文件路径
+     * @param content 文件内容
+     * @param charset 字符编码(默认 UTF-8)
+     * @param append 是否追加(true 追加,false 覆盖)
+     * @return 是否写入成功
+     */
+    fun writeFile(filePath: String, content: String, charset: String = "UTF-8", append: Boolean = false): Boolean
+    
+    /**
+     * 读取文件内容(字节)
+     * @param filePath 文件路径
+     * @return 文件内容(字节数组),读取失败返回 null
+     */
+    fun readFileBytes(filePath: String): ByteArray?
+    
+    /**
+     * 写入文件内容(字节)
+     * @param filePath 文件路径
+     * @param bytes 文件内容(字节数组)
+     * @param append 是否追加(true 追加,false 覆盖)
+     * @return 是否写入成功
+     */
+    fun writeFileBytes(filePath: String, bytes: ByteArray, append: Boolean = false): Boolean
+    
+    /**
+     * 删除文件
+     * @param filePath 文件路径
+     * @return 是否删除成功
+     */
+    fun deleteFile(filePath: String): Boolean
+    
+    /**
+     * 判断文件是否存在
+     * @param filePath 文件路径
+     * @return 是否存在
+     */
+    fun fileExists(filePath: String): Boolean
+    
+    /**
+     * 获取文件大小(字节)
+     * @param filePath 文件路径
+     * @return 文件大小,文件不存在返回 -1
+     */
+    fun getFileSize(filePath: String): Long
+    
+    /**
+     * 复制文件
+     * @param srcPath 源文件路径
+     * @param destPath 目标文件路径
+     * @return 是否复制成功
+     */
+    fun copyFile(srcPath: String, destPath: String): Boolean
+    
+    // ==================== 其他工具方法 ====================
+    
+    /**
+     * 获取随机字符串
+     * @param length 字符串长度
+     * @param includeNumbers 是否包含数字
+     * @param includeLetters 是否包含字母
+     * @return 随机字符串
+     */
+    fun getRandomString(length: Int, includeNumbers: Boolean = true, includeLetters: Boolean = true): String
+    
+    /**
+     * 获取随机整数
+     * @param min 最小值(包含)
+     * @param max 最大值(包含)
+     * @return 随机整数
+     */
+    fun getRandomInt(min: Int, max: Int): Int
+    
+    /**
+     * 格式化文件大小(如:1.5 MB、500 KB)
+     * @param size 文件大小(字节)
+     * @return 格式化后的字符串
+     */
+    fun formatFileSize(size: Long): String
+    
+    /**
+     * 格式化数字(添加千分位分隔符)
+     * @param number 数字
+     * @return 格式化后的字符串(如:1,234,567)
+     */
+    fun formatNumber(number: Number): String
+    
+    /**
+     * 手机号脱敏(如:138****5678)
+     * @param phone 手机号
+     * @return 脱敏后的手机号
+     */
+    fun maskPhone(phone: String?): String?
+    
+    /**
+     * 邮箱脱敏(如:abc***@example.com)
+     * @param email 邮箱
+     * @return 脱敏后的邮箱
+     */
+    fun maskEmail(email: String?): String?
+    
+    /**
+     * 验证手机号格式
+     * @param phone 手机号
+     * @return 是否有效
+     */
+    fun isValidPhone(phone: String?): Boolean
+    
+    /**
+     * 验证邮箱格式
+     * @param email 邮箱
+     * @return 是否有效
+     */
+    fun isValidEmail(email: String?): Boolean
+    
+    /**
+     * 验证身份证格式
+     * @param idCard 身份证号
+     * @return 是否有效
+     */
+    fun isValidIdCard(idCard: String?): Boolean
+}
+
+/**
+ * IUtil 接口的实现类
+ * 在同一个文件中实现所有工具方法
+ */
+object UtilImpl : IUtil {
+    
+    // ==================== 时间处理 ====================
+    
+    override fun formatTime(timestamp: Long, pattern: String): String {
+        return try {
+            val sdf = java.text.SimpleDateFormat(pattern, java.util.Locale.getDefault())
+            sdf.format(java.util.Date(timestamp))
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun parseTime(timeString: String, pattern: String): Long? {
+        return try {
+            val sdf = java.text.SimpleDateFormat(pattern, java.util.Locale.getDefault())
+            sdf.parse(timeString)?.time
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun getCurrentTimeMillis(): Long {
+        return System.currentTimeMillis()
+    }
+    
+    override fun formatCurrentTime(pattern: String): String {
+        return formatTime(getCurrentTimeMillis(), pattern)
+    }
+    
+    override fun formatDate(timestamp: Long, pattern: String): String {
+        return formatTime(timestamp, pattern)
+    }
+    
+    override fun getRelativeTime(timestamp: Long): String {
+        val now = getCurrentTimeMillis()
+        val diff = now - timestamp
+        
+        return when {
+            diff < 60 * 1000 -> "刚刚"
+            diff < 60 * 60 * 1000 -> "${diff / (60 * 1000)}分钟前"
+            diff < 24 * 60 * 60 * 1000 -> "${diff / (60 * 60 * 1000)}小时前"
+            diff < 2 * 24 * 60 * 60 * 1000 -> "昨天"
+            diff < 7 * 24 * 60 * 60 * 1000 -> "${diff / (24 * 60 * 60 * 1000)}天前"
+            else -> formatTime(timestamp, "yyyy-MM-dd")
+        }
+    }
+    
+    // ==================== 字符串处理 ====================
+    
+    override fun encryptString(data: String): String {
+        // 简单实现:Base64编码(实际项目中应使用AES等加密算法)
+        return base64Encode(data)
+    }
+    
+    override fun decryptString(data: String): String {
+        // 简单实现:Base64解码(实际项目中应使用AES等加密算法)
+        return base64Decode(data) ?: ""
+    }
+    
+    override fun md5(data: String): String {
+        return try {
+            val md = java.security.MessageDigest.getInstance("MD5")
+            val bytes = md.digest(data.toByteArray())
+            bytes.joinToString("") { "%02x".format(it) }
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun base64Encode(data: String): String {
+        return try {
+            android.util.Base64.encodeToString(data.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP)
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun base64Decode(data: String): String? {
+        return try {
+            val bytes = android.util.Base64.decode(data, android.util.Base64.NO_WRAP)
+            String(bytes, Charsets.UTF_8)
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun urlEncode(data: String): String {
+        return try {
+            java.net.URLEncoder.encode(data, "UTF-8")
+        } catch (e: Exception) {
+            data
+        }
+    }
+    
+    override fun urlDecode(data: String): String? {
+        return try {
+            java.net.URLDecoder.decode(data, "UTF-8")
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun isEmpty(str: String?): Boolean {
+        return str.isNullOrBlank()
+    }
+    
+    override fun isNotEmpty(str: String?): Boolean {
+        return !isEmpty(str)
+    }
+    
+    override fun trim(str: String?): String? {
+        return str?.trim()
+    }
+    
+    override fun substring(str: String, start: Int, end: Int): String? {
+        return try {
+            if (start < 0 || end > str.length || start > end) {
+                null
+            } else {
+                str.substring(start, end)
+            }
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    // ==================== 数据转换 ====================
+    
+    override fun <T> toJson(obj: T): String {
+        return try {
+            com.google.gson.Gson().toJson(obj)
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun <T> fromJson(json: String, clazz: Class<T>): T? {
+        return try {
+            com.google.gson.Gson().fromJson(json, clazz)
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    @Suppress("UNCHECKED_CAST")
+    override fun <T> fromJson(json: String, typeToken: Any): T? {
+        return try {
+            val gson = com.google.gson.Gson()
+            val type = (typeToken as com.google.gson.reflect.TypeToken<*>).type
+            gson.fromJson<T>(json, type)
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun numberToString(number: Number?): String {
+        return number?.toString() ?: ""
+    }
+    
+    override fun stringToInt(str: String?, defaultValue: Int): Int {
+        return try {
+            str?.toInt() ?: defaultValue
+        } catch (e: Exception) {
+            defaultValue
+        }
+    }
+    
+    override fun stringToLong(str: String?, defaultValue: Long): Long {
+        return try {
+            str?.toLong() ?: defaultValue
+        } catch (e: Exception) {
+            defaultValue
+        }
+    }
+    
+    override fun stringToFloat(str: String?, defaultValue: Float): Float {
+        return try {
+            str?.toFloat() ?: defaultValue
+        } catch (e: Exception) {
+            defaultValue
+        }
+    }
+    
+    override fun stringToDouble(str: String?, defaultValue: Double): Double {
+        return try {
+            str?.toDouble() ?: defaultValue
+        } catch (e: Exception) {
+            defaultValue
+        }
+    }
+    
+    override fun stringToBoolean(str: String?, defaultValue: Boolean): Boolean {
+        return try {
+            str?.toBoolean() ?: defaultValue
+        } catch (e: Exception) {
+            defaultValue
+        }
+    }
+    
+    // ==================== 设备信息 ====================
+    
+    override fun getScreenWidth(context: Context): Int {
+        return context.resources.displayMetrics.widthPixels
+    }
+    
+    override fun getScreenHeight(context: Context): Int {
+        return context.resources.displayMetrics.heightPixels
+    }
+    
+    override fun getStatusBarHeight(context: Context): Int {
+        var result = 0
+        val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
+        if (resourceId > 0) {
+            result = context.resources.getDimensionPixelSize(resourceId)
+        }
+        return result
+    }
+    
+    override fun getNavigationBarHeight(context: Context): Int {
+        var result = 0
+        val resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
+        if (resourceId > 0) {
+            result = context.resources.getDimensionPixelSize(resourceId)
+        }
+        return result
+    }
+    
+    override fun getDeviceId(context: Context): String {
+        return try {
+            android.provider.Settings.Secure.getString(context.contentResolver, android.provider.Settings.Secure.ANDROID_ID) ?: ""
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun getAppVersionName(context: Context): String {
+        return try {
+            val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+            packageInfo.versionName ?: ""
+        } catch (e: Exception) {
+            ""
+        }
+    }
+    
+    override fun getAppVersionCode(context: Context): Int {
+        return try {
+            val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+            @Suppress("DEPRECATION")
+            packageInfo.versionCode
+        } catch (e: Exception) {
+            0
+        }
+    }
+    
+    override fun getPackageName(context: Context): String {
+        return context.packageName
+    }
+    
+    // ==================== 单位转换 ====================
+    
+    override fun dpToPx(context: Context, dp: Float): Int {
+        val density = context.resources.displayMetrics.density
+        return (dp * density + 0.5f).toInt()
+    }
+    
+    override fun pxToDp(context: Context, px: Float): Float {
+        val density = context.resources.displayMetrics.density
+        return px / density
+    }
+    
+    override fun spToPx(context: Context, sp: Float): Int {
+        return android.util.TypedValue.applyDimension(
+            android.util.TypedValue.COMPLEX_UNIT_SP,
+            sp,
+            context.resources.displayMetrics
+        ).toInt()
+    }
+    
+    override fun pxToSp(context: Context, px: Float): Float {
+        return px / context.resources.displayMetrics.scaledDensity
+    }
+    
+    // ==================== 文件操作 ====================
+    
+    override fun readFile(filePath: String, charset: String): String? {
+        return try {
+            val file = java.io.File(filePath)
+            if (file.exists() && file.isFile) {
+                file.readText(java.nio.charset.Charset.forName(charset))
+            } else {
+                null
+            }
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun writeFile(filePath: String, content: String, charset: String, append: Boolean): Boolean {
+        return try {
+            val file = java.io.File(filePath)
+            file.parentFile?.mkdirs()
+            file.writeText(content, java.nio.charset.Charset.forName(charset))
+            true
+        } catch (e: Exception) {
+            false
+        }
+    }
+    
+    override fun readFileBytes(filePath: String): ByteArray? {
+        return try {
+            val file = java.io.File(filePath)
+            if (file.exists() && file.isFile) {
+                file.readBytes()
+            } else {
+                null
+            }
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    override fun writeFileBytes(filePath: String, bytes: ByteArray, append: Boolean): Boolean {
+        return try {
+            val file = java.io.File(filePath)
+            file.parentFile?.mkdirs()
+            if (append && file.exists()) {
+                file.appendBytes(bytes)
+            } else {
+                file.writeBytes(bytes)
+            }
+            true
+        } catch (e: Exception) {
+            false
+        }
+    }
+    
+    override fun deleteFile(filePath: String): Boolean {
+        return try {
+            val file = java.io.File(filePath)
+            file.delete()
+        } catch (e: Exception) {
+            false
+        }
+    }
+    
+    override fun fileExists(filePath: String): Boolean {
+        return try {
+            val file = java.io.File(filePath)
+            file.exists() && file.isFile
+        } catch (e: Exception) {
+            false
+        }
+    }
+    
+    override fun getFileSize(filePath: String): Long {
+        return try {
+            val file = java.io.File(filePath)
+            if (file.exists() && file.isFile) {
+                file.length()
+            } else {
+                -1
+            }
+        } catch (e: Exception) {
+            -1
+        }
+    }
+    
+    override fun copyFile(srcPath: String, destPath: String): Boolean {
+        return try {
+            val srcFile = java.io.File(srcPath)
+            val destFile = java.io.File(destPath)
+            if (!srcFile.exists() || !srcFile.isFile) {
+                return false
+            }
+            destFile.parentFile?.mkdirs()
+            srcFile.copyTo(destFile, overwrite = true)
+            true
+        } catch (e: Exception) {
+            false
+        }
+    }
+    
+    // ==================== 其他工具方法 ====================
+    
+    override fun getRandomString(length: Int, includeNumbers: Boolean, includeLetters: Boolean): String {
+        val chars = buildString {
+            if (includeNumbers) append("0123456789")
+            if (includeLetters) append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+        }
+        if (chars.isEmpty()) return ""
+        
+        return (1..length)
+            .map { chars.random() }
+            .joinToString("")
+    }
+    
+    override fun getRandomInt(min: Int, max: Int): Int {
+        return (min..max).random()
+    }
+    
+    override fun formatFileSize(size: Long): String {
+        return when {
+            size < 1024 -> "$size B"
+            size < 1024 * 1024 -> String.format("%.2f KB", size / 1024.0)
+            size < 1024 * 1024 * 1024 -> String.format("%.2f MB", size / (1024.0 * 1024.0))
+            else -> String.format("%.2f GB", size / (1024.0 * 1024.0 * 1024.0))
+        }
+    }
+    
+    override fun formatNumber(number: Number): String {
+        return java.text.NumberFormat.getNumberInstance(java.util.Locale.getDefault()).format(number)
+    }
+    
+    override fun maskPhone(phone: String?): String? {
+        if (phone.isNullOrEmpty()) return phone
+        return if (phone.length >= 7) {
+            "${phone.substring(0, 3)}****${phone.substring(phone.length - 4)}"
+        } else {
+            phone
+        }
+    }
+    
+    override fun maskEmail(email: String?): String? {
+        if (email.isNullOrEmpty()) return email
+        val atIndex = email.indexOf('@')
+        return if (atIndex > 0) {
+            val prefix = email.substring(0, atIndex)
+            val suffix = email.substring(atIndex)
+            if (prefix.length > 3) {
+                "${prefix.substring(0, 3)}***$suffix"
+            } else {
+                "$prefix***$suffix"
+            }
+        } else {
+            email
+        }
+    }
+    
+    override fun isValidPhone(phone: String?): Boolean {
+        if (phone.isNullOrEmpty()) return false
+        // 中国手机号:1开头,11位数字
+        val pattern = "^1[3-9]\\d{9}$"
+        return java.util.regex.Pattern.matches(pattern, phone)
+    }
+    
+    override fun isValidEmail(email: String?): Boolean {
+        if (email.isNullOrEmpty()) return false
+        val pattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
+        return java.util.regex.Pattern.matches(pattern, email)
+    }
+    
+    override fun isValidIdCard(idCard: String?): Boolean {
+        if (idCard.isNullOrEmpty()) return false
+        // 18位身份证号
+        val pattern18 = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$"
+        // 15位身份证号
+        val pattern15 = "^[1-9]\\d{5}\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}$"
+        return java.util.regex.Pattern.matches(pattern18, idCard) || 
+               java.util.regex.Pattern.matches(pattern15, idCard)
+    }
 }
 

+ 153 - 76
capability-ble/src/main/java/com/narutohuo/xindazhou/ble/impl/BLEServiceImpl.kt

@@ -1,5 +1,6 @@
 package com.narutohuo.xindazhou.ble.impl
 
+import android.annotation.SuppressLint
 import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothGatt
 import android.bluetooth.BluetoothGattCharacteristic
@@ -10,9 +11,11 @@ import com.narutohuo.xindazhou.ble.model.*
 import com.narutohuo.xindazhou.ble.util.*
 import com.narutohuo.xindazhou.core.log.ILog
 import org.json.JSONObject
+import java.io.ByteArrayOutputStream
 import java.util.TimeZone
 import java.util.UUID
 import java.util.concurrent.ConcurrentHashMap
+import kotlin.experimental.xor
 import kotlin.random.Random
 
 /**
@@ -63,6 +66,10 @@ class BLEServiceImpl private constructor(
     private val packetParser = BLEPacketParser()
     private val packetSplitter = BLEPacketSplitter()
     
+    // 当前连接的设备对象(用于配对)
+    @SuppressLint("MissingPermission")
+    private var currentAndroidDevice: android.bluetooth.BluetoothDevice? = null
+    
     // 状态
     private var currentDevice: BLEDevice? = null
     private var encryptKey: ByteArray? = null
@@ -126,10 +133,11 @@ class BLEServiceImpl private constructor(
         
         currentDevice = device
         
-        // 生成加密密钥(基于MAC地址)
-        val macBytes = device.address.replace(":", "").hexToByteArray()
-        encryptKey = BLECrypto.generateAESKey(macBytes)
-        ILog.d(tag, "加密密钥已生成")
+        // 使用固定的加密密钥(与 demo 保持一致)
+        // 密钥:323032355344483A618332D4C0030303
+        val keyHex = "323032355344483A618332D4C0030303"
+        encryptKey = keyHex.hexToByteArray()
+        ILog.d(tag, "加密密钥已设置(固定密钥)")
         
         val adapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter()
         if (adapter == null) {
@@ -156,6 +164,9 @@ class BLEServiceImpl private constructor(
         
         ILog.d(tag, "✅ 获取到 BluetoothDevice 对象")
         
+        // 保存设备对象用于配对处理
+        currentAndroidDevice = androidDevice
+        
         connector = BleConnector(
             context = context,
             device = androidDevice,
@@ -166,7 +177,8 @@ class BLEServiceImpl private constructor(
                     writeChar = writeChar,
                     splitter = packetSplitter,
                     onPairing = { pairing ->
-                        ILog.d(tag, "收到配对请求,状态: $pairing")
+                        // 处理配对请求
+                        handlePairingRequest(pairing)
                     },
                     onHandshakeRequest = { random ->
                         // 收到设备的握手请求,发送握手应答
@@ -182,9 +194,8 @@ class BLEServiceImpl private constructor(
                 // 注入到连接器
                 connector?.packetSender = packetSender
                 
-                ILog.d(tag, "GATT连接成功,开始握手")
-                // 连接成功后自动进行握手
-                performHandshake(userId, userType)
+                ILog.d(tag, "GATT连接成功,等待设备发送握手请求")
+                // 注意:不主动发送握手请求,等待设备发送握手请求后,通过 onHandshakeRequest 回调处理
                 
                 // 通知连接状态监听器
                 connectionListener?.onConnected()
@@ -195,6 +206,7 @@ class BLEServiceImpl private constructor(
                 encryptKey = null
                 packetSender = null
                 connector = null
+                currentAndroidDevice = null
                 connectionCallback?.invoke(BLEResponse(success = false, errorMessage = "连接已断开"))
                 connectionCallback = null
                 pendingUserId = null
@@ -227,6 +239,7 @@ class BLEServiceImpl private constructor(
         packetSender = null
         connector = null
         scanner = null
+        currentAndroidDevice = null
         pendingUserId = null
         pendingUserType = null
         connectionCallback = null
@@ -329,7 +342,68 @@ class BLEServiceImpl private constructor(
     }
     
     /**
+     * 处理配对请求
+     * 
+     * @param pairing 配对状态:1=需要配对,2=配对中,3=配对失败/取消配对
+     */
+    @SuppressLint("MissingPermission")
+    private fun handlePairingRequest(pairing: Int) {
+        val device = currentAndroidDevice
+        if (device == null) {
+            ILog.e(tag, "无法处理配对请求:设备对象为 null")
+            return
+        }
+        
+        ILog.d(tag, "收到配对请求,状态: $pairing")
+        
+        when (pairing) {
+            1 -> {
+                // 需要配对,调用 createBond() 触发系统配对界面
+                ILog.d(tag, "开始配对(pairing=1)")
+                try {
+                    val bondResult = device.createBond()
+                    if (bondResult) {
+                        ILog.d(tag, "✅ createBond() 调用成功,等待系统配对界面")
+                    } else {
+                        ILog.w(tag, "⚠️ createBond() 返回 false")
+                    }
+                } catch (e: Exception) {
+                    ILog.e(tag, "❌ createBond() 失败", e)
+                }
+            }
+            2 -> {
+                // 配对中,如果未绑定则调用 createBond()
+                ILog.d(tag, "配对中(pairing=2),当前绑定状态: ${device.bondState}")
+                try {
+                    if (device.bondState != android.bluetooth.BluetoothDevice.BOND_BONDED) {
+                        val bondResult = device.createBond()
+                        if (bondResult) {
+                            ILog.d(tag, "✅ createBond() 调用成功")
+                        } else {
+                            ILog.w(tag, "⚠️ createBond() 返回 false")
+                        }
+                    } else {
+                        ILog.d(tag, "设备已绑定,无需再次配对")
+                    }
+                } catch (e: Exception) {
+                    ILog.e(tag, "❌ createBond() 失败", e)
+                }
+            }
+            3 -> {
+                // 配对失败或取消配对,断开连接
+                ILog.w(tag, "配对失败或取消配对(pairing=3),断开连接")
+                disconnect()
+            }
+            else -> {
+                ILog.w(tag, "未知的配对状态: $pairing")
+            }
+        }
+    }
+    
+    /**
      * 处理握手响应(收到设备的握手请求后调用)
+     * 
+     * 按照 demo 的方式构建握手应答数据包
      */
     private fun handleHandshakeResponse(random: Int, userId: ByteArray, userType: Byte) {
         if (!isConnected() || packetSender == null || encryptKey == null) {
@@ -340,19 +414,22 @@ class BLEServiceImpl private constructor(
         try {
             // 将 random 转为偶数位十六进制字符串
             val randomHex = random.toEvenHex()
-            ILog.d(tag, "握手随机数(十六进制): $randomHex")
+            ILog.d(tag, "========== 🔐 Android 握手应答开始 ==========")
+            ILog.d(tag, "📍 握手随机数(十进制): $random")
+            ILog.d(tag, "📍 握手随机数(十六进制): $randomHex")
             
             // 将十六进制字符串转为 ByteArray
             val randomBytes = randomHex.hexToByteArray()
+            ILog.d(tag, "📍 随机数字节数组(hex): ${randomBytes.toHexString()} (${randomBytes.size} bytes)")
             
             // 使用 AES 加密随机数
             val encryptedRandomBytes = BLECrypto.encrypt(randomBytes, encryptKey!!)
             
             // 将加密后的 ByteArray 转为十六进制字符串
             val encryptedRandomHex = encryptedRandomBytes.toHexString()
-            ILog.d(tag, "加密后的随机数(十六进制): $encryptedRandomHex")
+            ILog.d(tag, "🔒 加密后的随机数(hex): $encryptedRandomHex (${encryptedRandomHex.length} chars = ${encryptedRandomBytes.size} bytes)")
             
-            // 构建握手应答 JSON
+            // 构建握手应答 JSON(按照 demo 的格式)
             val json = JSONObject().apply {
                 put("TY", 3)  // 类型:握手
                 put("IN", 3)  // 指令:握手应答
@@ -361,83 +438,70 @@ class BLEServiceImpl private constructor(
                 put("C", JSONObject().apply {
                     put("D", JSONObject().apply {
                         put("EncryptedRandom", encryptedRandomHex)
-                        put("UserType", userType.toInt())
-                        put("UserID", userId.toHexString())
+                        put("UserType", 1)  // 固定为 1,与 demo 保持一致
+                        put("UserID", "1234567890123456")
                     })
                 })
             }
             
-            ILog.d(tag, "发送握手应答")
+            val jsonString = json.toString()
+            ILog.d(tag, "📄 握手JSON: $jsonString")
             
-            // 发送握手应答(不等待回调,因为这是响应,不是请求)
-            sendRawData(json.toString().toByteArray(Charsets.UTF_8)) { response ->
-                if (response.success) {
-                    ILog.d(tag, "握手应答发送成功")
-                } else {
-                    ILog.e(tag, "握手应答发送失败: ${response.errorMessage}")
+            // 将 JSON 转为字节数组
+            val jsonBytes = jsonString.toByteArray(Charsets.UTF_8)
+            ILog.d(tag, "📄 JSON字节长度: ${jsonBytes.size} bytes")
+            
+            // 构建数据长度(2字节,大端序)
+            val lenBytes = byteArrayOf((jsonBytes.size shr 8).toByte(), (jsonBytes.size and 0xFF).toByte())
+            
+            // 构建握手应答数据包(按照 demo 的格式:地址+版本+时区+加密标志+操作系统+数据长度+数据区+校验)
+            val commandData = ByteArrayOutputStream().apply {
+                write(BLEConstants.ADDRESS_CONTROLLER.toInt() and 0xFF)  // 地址 0x05
+                write(BLEConstants.PROTOCOL_VERSION.toInt() and 0xFF)    // 版本 0x01
+                write(0x08)                                               // 时区(UTC+8)
+                write(BLEConstants.ENCRYPT_FLAG_NONE.toInt() and 0xFF)   // 加密标志 0x00(明文)
+                write(BLEConstants.OS_ANDROID.toInt() and 0xFF)          // 操作系统 0x01(Android)
+                write(lenBytes)                                           // 数据长度(2字节)
+                write(jsonBytes)                                          // 数据区(JSON字符串)
+                
+                // 计算校验(从地址到数据区结束,异或校验)
+                val data = toByteArray()
+                var chk: Byte = 0x00
+                for (b in data) {
+                    chk = chk.xor(b)
                 }
-            }
+                write(chk.toInt() and 0xFF)  // 校验
+            }.toByteArray()
             
-        } catch (e: Exception) {
-            ILog.e(tag, "处理握手响应失败", e)
-        }
-    }
-    
-    /**
-     * 执行握手(内部方法,连接成功后自动调用)
-     */
-    private fun performHandshake(userId: ByteArray, userType: Byte) {
-        if (!isConnected()) {
-            connectionCallback?.invoke(BLEResponse(success = false, errorMessage = "未连接设备"))
-            connectionCallback = null
-            return
-        }
-        
-        try {
-            // 构建握手JSON
-            val json = JSONObject().apply {
-                put("TY", 3)  // 类型:握手
-                put("IN", 3)  // 指令:握手请求
-                put("C", JSONObject().apply {
-                    put("D", JSONObject().apply {
-                        put("Random", Random.nextInt(0xFFFF))
-                        put("UserID", userId.toHexString())
-                        put("UserType", userType.toInt())
-                    })
-                })
-            }
+            val commandDataHex = commandData.toHexString()
+            ILog.d(tag, "📦 完整握手应答数据包长度: ${commandData.size} bytes")
+            ILog.d(tag, "📦 完整握手应答数据包(hex):")
+            ILog.d(tag, "    $commandDataHex")
+            ILog.d(tag, "📦 数据包结构:")
+            ILog.d(tag, "    地址: 0x${String.format("%02x", commandData[0])}")
+            ILog.d(tag, "    版本: 0x${String.format("%02x", commandData[1])}")
+            ILog.d(tag, "    时区: 0x${String.format("%02x", commandData[2])}")
+            ILog.d(tag, "    加密标志: 0x${String.format("%02x", commandData[3])}")
+            ILog.d(tag, "    操作系统: 0x${String.format("%02x", commandData[4])} (Android)")
+            ILog.d(tag, "    数据长度: 0x${String.format("%02x%02x", commandData[5], commandData[6])} (${(commandData[5].toInt() and 0xFF shl 8) or (commandData[6].toInt() and 0xFF)} bytes)")
+            ILog.d(tag, "    校验: 0x${String.format("%02x", commandData[commandData.size - 1])}")
             
-            ILog.d(tag, "开始握手认证")
+            // 生成业务ID并发送(使用 enqueueAndStart 进行分包发送)
+            val businessId = packetSender!!.generateBusinessId()
+            val businessIdHex = businessId.toHexString()
+            ILog.d(tag, "🔑 业务ID(hex): $businessIdHex")
+            ILog.d(tag, "📤 开始分包发送握手应答...")
+            packetSender!!.enqueueAndStart(commandData, businessId, true)
+            
+            ILog.d(tag, "========== 🔐 Android 握手应答完成 ==========")
             
-            // 发送握手请求
-            sendRawData(json.toString().toByteArray(Charsets.UTF_8)) { response ->
-                if (response.success) {
-                    ILog.d(tag, "握手成功,连接完成")
-                    // 握手成功,通知业务层连接完成
-                    connectionCallback?.invoke(BLEResponse(success = true))
-                    connectionCallback = null
-                    pendingUserId = null
-                    pendingUserType = null
-            } else {
-                    ILog.e(tag, "握手失败: ${response.errorMessage}")
-                    // 握手失败,断开连接
-                    disconnect()
-                    connectionCallback?.invoke(BLEResponse(success = false, errorMessage = "握手失败: ${response.errorMessage}"))
-                    connectionCallback = null
-                    pendingUserId = null
-                    pendingUserType = null
-                }
-            }
         } catch (e: Exception) {
-            ILog.e(tag, "握手失败", e)
-            disconnect()
-            connectionCallback?.invoke(BLEResponse(success = false, errorMessage = "握手失败: ${e.message}"))
-            connectionCallback = null
-            pendingUserId = null
-            pendingUserType = null
+            ILog.e(tag, "处理握手响应失败", e)
         }
     }
     
+    // performHandshake 方法已移除,改为等待设备主动发送握手请求,然后通过 onHandshakeRequest 回调处理
+    
     override fun sendCommand(command: BLECommand, callback: (BLEResponse) -> Unit) {
         if (!isConnected()) {
             callback(BLEResponse(success = false, errorMessage = "未连接设备"))
@@ -445,14 +509,24 @@ class BLEServiceImpl private constructor(
         }
         
         try {
+            ILog.d(tag, "📤 发送指令: functionCode=0x${String.format("%02X", command.functionCode.toInt() and 0xFF)}, instructionType=0x${String.format("%02X", command.instructionType.toInt() and 0xFF)}, dataLength=${command.applicationData.size}")
+            ILog.d(tag, "📤 指令数据: ${command.applicationData.toHexString()}")
+            
             // 使用 BLEPacketBuilder 构建完整指令数据包
             val packetData = packetBuilder.buildPacket(
                 command = command,
                 encryptKey = encryptKey
             )
             
+            ILog.d(tag, "📤 完整指令数据包长度: ${packetData.size} bytes")
+            ILog.d(tag, "📤 完整指令数据包: ${packetData.toHexString()}")
+            
             // 发送数据
             sendRawData(packetData) { response ->
+                ILog.d(tag, "📥 指令响应: success=${response.success}, error=${response.errorMessage}")
+                if (response.success && response.data != null) {
+                    ILog.d(tag, "📥 响应数据: ${response.data.toHexString()}")
+                }
                 callback(response)
             }
         } catch (e: Exception) {
@@ -635,9 +709,12 @@ class BLEServiceImpl private constructor(
             val businessId = packetSender!!.generateBusinessId()
             
             // 注册回调(使用业务ID作为key)
-            val callbackKey = businessId.toHexString()
+            val callbackKey = businessId.joinToString("") { "%02x".format(it.toInt() and 0xFF) }
             responseCallbacks[callbackKey] = callback
             
+            ILog.d(tag, "📤 发送原始数据: businessId=$callbackKey, dataLength=${data.size} bytes")
+            ILog.d(tag, "📤 原始数据: ${data.joinToString("") { "%02x".format(it.toInt() and 0xFF) }}")
+            
             // 发送数据(会自动分包)
             packetSender!!.enqueueAndStart(
                 rawData = data,

+ 2 - 2
capability-ble/src/main/java/com/narutohuo/xindazhou/ble/util/BLEExtension.kt

@@ -102,11 +102,11 @@ fun ByteArray.parseCommand(): Command {
 }
 
 /**
- * ByteArray 转十六进制字符串
+ * ByteArray 转十六进制字符串(小写,与 demo 保持一致)
  */
 @OptIn(ExperimentalStdlibApi::class)
 fun ByteArray.toHexString(): String {
-    return this.joinToString("") { "%02X".format(it) }
+    return this.joinToString("") { "%02x".format(it) }
 }
 
 /**

+ 9 - 0
capability-ble/src/main/java/com/narutohuo/xindazhou/ble/util/BlePacketSender.kt

@@ -59,8 +59,17 @@ class BlePacketSender(
         businessId: ByteArray,
         isRequest: Boolean = true
     ) {
+        ILog.d(tag, "📦📦📦 开始分包:原始数据 ${rawData.size} bytes")
         // 1️⃣ 分包
         val packets = splitter.split(rawData, businessId, isRequest)
+        ILog.d(tag, "📦📦📦 分包完成:共 ${packets.size} 个分包")
+        
+        // 打印每个分包的hex数据
+        packets.forEachIndexed { index, packet ->
+            val hexString = packet.toHexString()
+            ILog.d(tag, "    📦 分包[$index]: ${packet.size} bytes - $hexString")
+        }
+        
         // 2️⃣ 入队
         pendingPackets.addAll(packets)
         // 3️⃣ 启动发送(如果当前没有正在发送的任务)

+ 475 - 0
capability-ble/集成说明.md

@@ -0,0 +1,475 @@
+# 蓝牙BLE模块集成说明
+
+## 目录结构
+
+```
+capability-ble/src/
+└── main/
+    ├── AndroidManifest.xml          # Android 清单文件(权限声明)
+    └── java/
+        └── com/narutohuo/xindazhou/ble/
+            ├── api/                  # API 接口层
+            │   └── BLEService.kt    # 蓝牙服务接口定义
+            ├── factory/              # 工厂类层
+            │   └── BLEServiceFactory.kt  # 服务工厂(统一入口)
+            ├── impl/                 # 实现层
+            │   └── BLEServiceImpl.kt     # 蓝牙服务具体实现(单例模式)
+            ├── callback/             # 回调接口层
+            │   └── BLECallback.kt         # 蓝牙回调接口(包含 ConnectCallback、HandshakeCallback、CommandCallback、DataCallback)
+            ├── config/               # 配置层
+            │   └── BLEConstants.kt         # 蓝牙常量定义(用户类型、功能码、UUID等)
+            ├── model/                # 数据模型层
+            │   ├── BLEDevice.kt          # BLE设备模型
+            │   ├── BLECommand.kt          # BLE指令模型(包含 AppControlInstruction、SystemControlInstruction)
+            │   ├── BLEResponse.kt         # BLE响应模型
+            │   └── Command.kt             # 指令协议数据类
+            └── util/                 # 工具类层
+                ├── BleScanner.kt          # BLE扫描工具
+                ├── BleConnector.kt         # BLE连接工具
+                ├── BLECrypto.kt            # 加密解密工具(AES)
+                ├── BLEPacketBuilder.kt    # 协议包构建工具
+                ├── BLEPacketParser.kt     # 协议包解析工具
+                ├── BLEPacketSplitter.kt   # 协议包分包工具
+                └── BlePacketSender.kt     # 协议包发送工具
+```
+
+## 功能说明
+
+### 1. api/ 层 - 接口定义
+- **职责**:定义蓝牙服务的公共接口,业务层只依赖接口
+- **内容**:`BLEService` 接口
+  - **基础连接功能**:
+    - `startScan()` - 开始扫描BLE设备
+    - `stopScan()` - 停止扫描
+    - `connect()` - 连接设备(自动完成握手)
+    - `disconnect()` - 断开连接
+    - `isConnected()` - 检查连接状态
+  - **协议封装功能**:
+    - `sendCommand()` - 发送自定义指令
+    - `sendAppControlCommand()` - 发送应用控制指令
+    - `sendSystemControlCommand()` - 发送系统控制指令
+    - `queryVehicleInfo()` - 查询车辆信息
+  - **便捷方法**:
+    - `setDefense()`, `powerOn()`, `powerOff()`, `findCar()`
+    - `unlockSeat()`, `unlockHandlebar()`, `lockHandlebar()`
+    - `unlockTrunk()`, `lockTrunk()`
+    - `setSensorUnlock()`, `setAmbientLight()`, `setAmbientLightColor()`
+    - `setAutoPowerOffTime()`, `setSpeakerVolume()`
+  - **数据接收监听**:
+    - `setDataReceivedListener()` - 设置数据接收监听器
+
+### 2. factory/ 层 - 工厂类
+- **职责**:提供统一的服务实例获取方式
+- **内容**:`BLEServiceFactory` 对象(单例)
+- **作用**:
+  - 隐藏实现细节,统一入口
+  - 提供 `create(context)` 方法获取服务实例
+
+### 3. impl/ 层 - 具体实现
+- **职责**:实现 `BLEService` 接口的具体逻辑
+- **内容**:`BLEServiceImpl` 单例实现类
+- **功能**:
+  - 封装 Android BLE SDK 调用
+  - 封装 GATT 连接管理
+  - 封装握手认证(连接时自动完成)
+  - 封装数据分包和组包
+  - 封装 AES 加密/解密
+  - 封装协议包构建和解析
+
+### 4. model/ 层 - 数据模型
+- **职责**:定义蓝牙相关的数据模型
+- **内容**:
+  - `BLEDevice` - BLE设备模型
+  - `BLECommand` - BLE指令模型
+  - `BLEResponse` - BLE响应模型
+  - `Command` - 指令协议数据类
+  - `AppControlInstruction` - 应用控制指令常量(对象)
+  - `SystemControlInstruction` - 系统控制指令常量(对象)
+
+### 5. callback/ 层 - 回调接口
+- **职责**:定义蓝牙相关的回调接口
+- **内容**:
+  - `BLECallback.kt` 文件中包含:
+    - `ConnectCallback` - 连接回调接口
+    - `HandshakeCallback` - 握手指令回调接口
+    - `CommandCallback` - 指令发送回调接口
+    - `DataCallback` - 数据接收回调接口
+
+### 6. config/ 层 - 配置
+- **职责**:定义蓝牙相关的常量配置
+- **内容**:
+  - `BLEConstants` - 蓝牙常量定义(用户类型、功能码、UUID等)
+
+### 7. util/ 层 - 工具类
+- **职责**:提供蓝牙相关的工具方法
+- **内容**:
+  - `BleScanner` - BLE扫描工具
+  - `BleConnector` - BLE连接工具
+  - `BLECrypto` - 加密解密工具
+  - `BLEPacketBuilder` - 协议包构建工具
+  - `BLEPacketParser` - 协议包解析工具
+  - `BLEPacketSplitter` - 协议包分包工具
+  - `BlePacketSender` - 协议包发送工具
+
+## 架构设计
+
+- **接口隔离**:业务层只依赖接口,不依赖实现
+- **工厂模式**:统一入口,隐藏创建逻辑
+- **单例模式**:`BLEServiceImpl` 使用单例模式,全局共享同一个连接状态
+- **完全封装**:所有底层细节(连接、握手、分包、加密等)都在能力层内部完成
+
+## 使用方式
+
+### 完整示例(单例模式)
+
+```kotlin
+import androidx.fragment.app.Fragment
+import android.widget.Toast
+import com.narutohuo.xindazhou.ble.factory.BLEServiceFactory
+import com.narutohuo.xindazhou.ble.api.BLEService
+import com.narutohuo.xindazhou.ble.config.BLEConstants
+
+class VehicleFragment : Fragment() {
+    
+    // 1. 获取单例实例(全局只有一个,在任何地方获取的都是同一个)
+    private val bleService: BLEService = BLEServiceFactory.create(requireContext())
+    
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        
+        // 2. 连接设备(一键完成扫描+连接+握手)
+        connectDevice()
+    }
+    
+    private fun connectDevice() {
+        // 获取用户信息(实际项目中从用户认证信息中获取)
+        val userId = getUserId() // 16字节
+        val userType = BLEConstants.USER_TYPE_OWNER // 0x02=车主
+        
+        bleService.initializeAndConnect(
+            userId = userId,
+            userType = userType,
+            onConnected = {
+                activity?.runOnUiThread {
+                    // 连接成功,可以直接使用指令了
+                    Toast.makeText(context, "连接成功", Toast.LENGTH_SHORT).show()
+                }
+            },
+            onError = { error ->
+                activity?.runOnUiThread {
+                    Toast.makeText(context, "连接失败: $error", Toast.LENGTH_SHORT).show()
+                }
+            }
+        )
+    }
+    
+    // 3. 发送指令(连接成功后调用)
+    private fun lockCar() {
+        // 设防
+        bleService.setDefense(enabled = true) { response ->
+            activity?.runOnUiThread {
+                if (response.success) {
+                    Toast.makeText(context, "设防成功", Toast.LENGTH_SHORT).show()
+                } else {
+                    Toast.makeText(context, "设防失败: ${response.errorMessage}", Toast.LENGTH_SHORT).show()
+                }
+            }
+        }
+    }
+    
+    private fun powerOn() {
+        // 上电
+        bleService.powerOn { response ->
+            activity?.runOnUiThread {
+                if (response.success) {
+                    Toast.makeText(context, "上电成功", Toast.LENGTH_SHORT).show()
+                }
+            }
+        }
+    }
+    
+    private fun findCar() {
+        // 寻车
+        bleService.findCar { response ->
+            activity?.runOnUiThread {
+                if (response.success) {
+                    Toast.makeText(context, "寻车成功", Toast.LENGTH_SHORT).show()
+                }
+            }
+        }
+    }
+    
+    private fun getUserId(): ByteArray {
+        // 实际项目中从用户认证信息中获取
+        return ByteArray(16) { it.toByte() } // 示例数据
+    }
+    
+    override fun onDestroyView() {
+        super.onDestroyView()
+        // 4. 断开连接(可选,单例会保持连接状态)
+        if (bleService.isConnected()) {
+            bleService.disconnect()
+        }
+    }
+}
+```
+
+### 步骤说明
+
+1. **获取单例实例**:使用 `BLEServiceFactory.create(context)` 获取服务实例(全局只有一个)
+2. **连接设备**:调用 `initializeAndConnect()` 连接设备(自动完成扫描+连接+握手)
+3. **发送指令**:连接成功后,调用各种指令方法(如 `setDefense()`、`powerOn()`、`findCar()` 等)
+4. **断开连接**:在 `onDestroyView()` 中断开连接(可选)
+
+### 重要说明
+
+1. **单例模式**:`BLEService` 是单例,全局只有一个实例,在任何地方获取的都是同一个
+2. **连接状态共享**:所有使用该服务的地方共享同一个连接状态
+3. **第一次需要 Context**:第一次获取单例时需要传入 Context,后续获取可以不带(但建议统一使用工厂类)
+4. **连接是必须的**:蓝牙不是自动连接的,需要手动调用 `initializeAndConnect()` 连接设备
+
+### 两种连接方式的区别
+
+#### 方式一:一键连接(推荐,最简单)
+
+使用 `initializeAndConnect()` - **自动完成所有步骤**:
+- ✅ 自动扫描设备
+- ✅ 自动找到设备后连接
+- ✅ 自动完成握手和配对
+- ✅ 一个方法搞定,无需手动控制
+
+**适用场景**:大多数情况,直接连接即可
+
+```kotlin
+// 一键完成:扫描 + 连接 + 握手
+bleService.initializeAndConnect(
+    userId = userId,
+    userType = userType,
+    onConnected = {
+        // 连接成功,可以直接使用指令了
+    },
+    onError = { error ->
+        // 连接失败
+    }
+)
+```
+
+#### 方式二:手动控制(需要选择设备时使用)
+
+使用 `startScan()` + `connect()` - **手动控制每个步骤**:
+- 1️⃣ 手动开始扫描
+- 2️⃣ 手动选择要连接的设备(可以显示设备列表让用户选择)
+- 3️⃣ 手动停止扫描
+- 4️⃣ 手动连接选中的设备
+
+**适用场景**:需要显示设备列表让用户选择,或者需要控制扫描过程
+
+```kotlin
+// 1. 开始扫描
+bleService.startScan { device: BLEDevice ->
+    // 找到设备,可以显示在列表中让用户选择
+    println("找到设备: ${device.name}, 地址: ${device.address}")
+    // 可以添加到设备列表,让用户选择
+}
+
+// 2. 停止扫描(用户选择设备后)
+bleService.stopScan()
+
+// 3. 连接用户选择的设备
+val userId = getUserId() // 16字节
+val userType = BLEConstants.USER_TYPE_OWNER
+val selectedDevice = BLEDevice(name = "用户选择的设备", address = "MAC地址", rssi = 0)
+bleService.connect(selectedDevice, userId, userType) { response ->
+    if (response.success) {
+        // 连接成功,已自动完成握手
+    } else {
+        // 连接失败
+    }
+}
+```
+
+**总结**:
+- **一键连接**:最简单,自动完成所有步骤,推荐使用
+- **手动控制**:需要用户选择设备时使用,可以显示设备列表
+
+## API 参考
+
+### 基础连接功能
+
+#### `startScan(callback: (BLEDevice) -> Unit)`
+开始扫描 BLE 设备
+
+#### `stopScan()`
+停止扫描设备
+
+#### `connect(device: BLEDevice, userId: ByteArray, userType: Byte, callback: (BLEResponse) -> Unit)`
+连接设备(自动完成握手和配对)
+
+#### `disconnect()`
+断开连接
+
+#### `isConnected(): Boolean`
+检查是否已连接
+
+### 高级连接功能(推荐使用)
+
+#### `initializeAndConnect()`
+一键完成扫描+连接+握手(最简单的方式)
+
+```kotlin
+fun initializeAndConnect(
+    userId: ByteArray,
+    userType: Byte,
+    onConnected: () -> Unit,
+    onDisconnected: (() -> Unit)? = null,
+    onDataReceived: ((ByteArray) -> Unit)? = null,
+    onError: ((String) -> Unit)? = null,
+    scanTimeoutMs: Long = 10000
+)
+```
+
+**参数说明**:
+- `userId`: 用户ID(16字节),用于握手认证
+- `userType`: 用户类型(0x01=最高权限, 0x02=车主, 0x03=分享用户)
+- `onConnected`: 连接成功回调(已自动完成握手和配对)
+- `onDisconnected`: 断开连接回调(可选)
+- `onDataReceived`: 数据接收回调(可选)
+- `onError`: 错误回调(可选)
+- `scanTimeoutMs`: 扫描超时时间(毫秒),默认10秒
+
+#### `scanAndConnect()`
+扫描并连接设备(一键完成扫描+连接)
+
+```kotlin
+fun scanAndConnect(
+    userId: ByteArray,
+    userType: Byte,
+    scanTimeoutMs: Long = 10000,
+    onScanning: (() -> Unit)? = null,
+    onDeviceFound: ((BLEDevice) -> Unit)? = null,
+    onConnected: () -> Unit,
+    onError: (String) -> Unit
+)
+```
+
+#### `setConnectionListener()`
+设置连接状态监听器(连接/断开时自动回调)
+
+```kotlin
+fun setConnectionListener(
+    onConnected: () -> Unit,
+    onDisconnected: () -> Unit,
+    onError: (String) -> Unit
+)
+```
+
+### 便捷方法
+
+#### `setDefense(enabled: Boolean, callback: (BLEResponse) -> Unit)`
+设防/撤防
+
+#### `powerOn(callback: (BLEResponse) -> Unit)`
+上电
+
+#### `powerOff(callback: (BLEResponse) -> Unit)`
+下电
+
+#### `findCar(callback: (BLEResponse) -> Unit)`
+寻车
+
+#### `unlockSeat(callback: (BLEResponse) -> Unit)`
+打开座桶锁
+
+#### `unlockHandlebar(callback: (BLEResponse) -> Unit)`
+打开龙头锁
+
+#### `lockHandlebar(callback: (BLEResponse) -> Unit)`
+关闭龙头锁
+
+#### `unlockTrunk(callback: (BLEResponse) -> Unit)`
+打开尾箱锁
+
+#### `lockTrunk(callback: (BLEResponse) -> Unit)`
+关闭尾箱锁
+
+#### `setSensorUnlock(enabled: Boolean, callback: (BLEResponse) -> Unit)`
+设置感应解锁开关
+
+#### `setAmbientLight(enabled: Boolean, callback: (BLEResponse) -> Unit)`
+设置氛围灯开关
+
+#### `setAmbientLightColor(r: Int, g: Int, b: Int, callback: (BLEResponse) -> Unit)`
+设置氛围灯颜色
+
+#### `setAutoPowerOffTime(timeSeconds: Int, callback: (BLEResponse) -> Unit)`
+设置自动下电时间
+
+#### `setSpeakerVolume(volume: Int, callback: (BLEResponse) -> Unit)`
+设置蓝牙音箱音量
+
+### 通用方法
+
+#### `sendAppControlCommand(instructionType: Byte, data: ByteArray, callback: (BLEResponse) -> Unit)`
+发送应用控制指令
+
+**指令类型**(`AppControlInstruction` 对象):
+- `AppControlInstruction.SET_DEFENSE` - 设撤防
+- `AppControlInstruction.POWER_ON_OFF` - 上下电
+- `AppControlInstruction.FIND_CAR` - 寻车
+- `AppControlInstruction.SEAT_LOCK` - 座桶锁
+- `AppControlInstruction.HANDLEBAR_LOCK` - 龙头锁
+- `AppControlInstruction.TRUNK_LOCK` - 尾箱锁
+
+#### `sendSystemControlCommand(instructionType: Byte, data: ByteArray, callback: (BLEResponse) -> Unit)`
+发送系统控制指令
+
+**指令类型**(`SystemControlInstruction` 对象):
+- `SystemControlInstruction.SENSOR_UNLOCK` - 感应解锁开关
+- `SystemControlInstruction.AMBIENT_LIGHT` - 氛围灯开关
+- `SystemControlInstruction.AMBIENT_LIGHT_COLOR` - 氛围灯颜色
+- `SystemControlInstruction.AUTO_POWER_OFF_TIME` - 自动下电时间
+- `SystemControlInstruction.SPEAKER_VOLUME` - 蓝牙音箱音量
+- 更多指令类型请查看 `SystemControlInstruction` 对象
+
+#### `queryVehicleInfo(callback: (BLEResponse) -> Unit)`
+查询车辆信息
+
+#### `sendCommand(command: BLECommand, callback: (BLEResponse) -> Unit)`
+发送自定义指令
+
+#### `setDataReceivedListener(listener: DataCallback)`
+设置数据接收监听器
+
+## 常量定义
+
+### 用户类型
+```kotlin
+BLEConstants.USER_TYPE_HIGHEST  // 0x01 - 最高权限
+BLEConstants.USER_TYPE_OWNER    // 0x02 - 车主
+BLEConstants.USER_TYPE_SHARED   // 0x03 - 分享用户
+```
+
+### 功能码
+```kotlin
+BLEConstants.FUNCTION_CODE_HANDSHAKE        // 0x01 - 握手
+BLEConstants.FUNCTION_CODE_APP_CONTROL      // 0x02 - 应用控制
+BLEConstants.FUNCTION_CODE_SYSTEM_CONTROL   // 0x03 - 系统控制
+BLEConstants.FUNCTION_CODE_SYSTEM_QUERY     // 0x04 - 系统查询
+BLEConstants.FUNCTION_CODE_SYSTEM_SETTING   // 0x05 - 系统设置
+```
+
+## 注意事项
+
+1. **单例模式**:`BLEServiceImpl` 使用单例模式,全局共享同一个连接状态
+
+2. **自动握手**:`connect()` 方法内部自动完成握手,无需单独调用握手方法
+
+3. **线程安全**:BLE 回调可能在非主线程执行,需要在回调中使用 `activity?.runOnUiThread {}` 切换到主线程更新 UI
+
+4. **错误处理**:所有 API 都通过 `BLEResponse` 返回结果,需要检查 `response.success` 判断是否成功
+
+5. **生命周期管理**:建议在 `onDestroyView()` (Fragment) 或 `onDestroy()` (Activity) 中调用 `disconnect()` 断开连接
+
+6. **获取服务实例**:推荐使用 `BLEServiceFactory.create(context)` 获取服务实例(工厂方法内部使用单例模式)
+
+7. **Fragment 中使用**:在 Fragment 中使用时,使用 `requireContext()` 获取 Context,回调中使用 `activity?.runOnUiThread {}` 更新 UI

+ 3 - 0
capability-push/build.gradle

@@ -45,6 +45,9 @@ dependencies {
     // base-core 已经通过 api 传递了 arouter-api,这里只需要添加编译器
     kapt("com.alibaba:arouter-compiler:1.5.2")
     
+    // Jetpack Startup Library(自动初始化)
+    implementation("androidx.startup:startup-runtime:1.1.1")
+    
     // 注意:Gson、ILog、ARouter 等已通过 base-core 传递,无需重复依赖
     
     // 极光推送SDK(使用本地下载的 AAR 文件)

+ 35 - 24
capability-push/src/main/AndroidManifest.xml

@@ -7,6 +7,8 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <!-- Android 13+ 通知权限(必需,否则无法显示通知) -->
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     
     <application>
         <!-- 极光推送基础配置(使用本模块自己的 strings.xml 资源) -->
@@ -95,40 +97,49 @@
 <!--            </intent-filter>-->
 <!--        </receiver>-->
         
-        <!-- 自定义消息接收器(封装层,用于业务回调) -->
-        <!-- 注意:这是额外的封装层,用于将极光推送的消息回调到业务层 -->
+        <!-- 自定义 Service(继承自 JCommonService,官方 Demo 要求) -->
+        <service
+            android:name="com.narutohuo.xindazhou.push.service.UserService"
+            android:process=":pushcore"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="cn.jiguang.user.service.action" />
+            </intent-filter>
+        </service>
+        
+        <!-- 自定义消息接收器(继承自 JPushMessageReceiver) -->
+        <!-- 官方 Demo 使用的 action 是 cn.jpush.android.intent.SERVICE_MESSAGE -->
         <receiver
             android:name="com.narutohuo.xindazhou.push.receiver.JPushReceiver"
             android:enabled="true"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="cn.jiguang.android.intent.MESSAGE_RECEIVED" />
-                <action android:name="cn.jiguang.android.intent.NOTIFICATION_RECEIVED" />
-                <action android:name="cn.jiguang.android.intent.NOTIFICATION_OPENED" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </receiver>
-
-        <!-- User defined.  For test only  用户自定义接收消息器,3.0.7开始支持,目前新tag/alias接口设置结果会在该广播接收器对应的方法中回调-->
-        <!--since 5.2.0 接收JPush相关事件-->
-        <receiver android:name=".server.PushMessageService"
             android:exported="true">
             <intent-filter>
                 <action android:name="cn.jpush.android.intent.SERVICE_MESSAGE" />
-                <category android:name="com.narutohuo.xindazhou.ble"></category>
+                <category android:name="com.narutohuo.xindazhou" />
             </intent-filter>
         </receiver>
-        <!--(jpush|jad)_config_start,jpush和jad公用的组件-->
-        <!-- 可配置android:process参数将PushService放在其他进程中 -->
-        <!--User defined.  For test only 继承自cn.jpush.android.service.JCommonService-->
-        <service
-            android:name=".server.UserService"
-            android:process=":pushcore"
-            android:exported="true">
+        
+        <!-- 开机自启动接收器(提升推送可靠性) -->
+        <!-- 在设备开机后自动恢复推送服务 -->
+        <receiver
+            android:name="com.narutohuo.xindazhou.push.receiver.BootReceiver"
+            android:enabled="true"
+            android:exported="false">
             <intent-filter>
-                <action android:name="cn.jiguang.user.service.action" />
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
             </intent-filter>
-        </service>
+        </receiver>
+        <!-- Jetpack Startup 自动初始化 -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            android:exported="false"
+            tools:node="merge">
+            <meta-data
+                android:name="com.narutohuo.xindazhou.push.startup.PushInitializer"
+                android:value="androidx.startup" />
+        </provider>
+        
     </application>
 </manifest>
 

+ 2 - 4
capability-push/src/main/java/com/narutohuo/xindazhou/push/api/PushService.kt

@@ -1,6 +1,5 @@
 package com.narutohuo.xindazhou.push.api
 
-import com.narutohuo.xindazhou.push.model.PushConfig
 import com.narutohuo.xindazhou.push.model.PushMessage
 
 /**
@@ -14,12 +13,11 @@ interface PushService {
     /**
      * 初始化推送服务
      * 
-     * 在 Application.onCreate() 中调用,配置极光推送参数
+     * 在 Application.onCreate() 中调用,从 strings.xml 读取配置
      * 
      * @param context Application 上下文
-     * @param config 推送服务配置
      */
-    fun initialize(context: android.content.Context, config: PushConfig)
+    fun initialize(context: android.content.Context)
     
     /**
      * 设置别名(用于推送)

+ 4 - 97
capability-push/src/main/java/com/narutohuo/xindazhou/push/factory/PushServiceFactory.kt

@@ -1,11 +1,9 @@
 package com.narutohuo.xindazhou.push.factory
 
 import android.content.Context
-import com.alibaba.android.arouter.launcher.ARouter
 import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.push.api.PushService
 import com.narutohuo.xindazhou.push.impl.PushServiceImpl
-import com.narutohuo.xindazhou.push.model.PushConfig
 import com.narutohuo.xindazhou.push.model.PushMessage
 
 /**
@@ -14,8 +12,6 @@ import com.narutohuo.xindazhou.push.model.PushMessage
  * 提供统一的获取推送服务实例和初始化的方式
  * 推送服务是全局单例,多个界面共享同一个推送服务实例
  * 
- * 内部使用 ARouter 获取服务,实现 base-core 与 push 模块的解耦
- * 
  * **完全封装,外部调用极简**:
  * ```kotlin
  * // Application.onCreate() 中只需一行代码
@@ -46,7 +42,7 @@ object PushServiceFactory {
      * 4. 自动注册推送(开启推送)
      * 
      * 从资源文件(strings.xml)读取配置,需要在 strings.xml 中定义:
-     * - push_jpush_app_key(必填)
+     * - jpush_app_key(必填)
      * - push_jpush_channel(可选,默认为 "developer-default")
      * 
      * **在 Application.onCreate() 中调用,统一初始化推送服务**
@@ -72,86 +68,10 @@ object PushServiceFactory {
         pageMappings: Map<String, Class<*>>? = null
     ) {
         try {
-            val config = PushConfig.fromResources(context)
-            val service = PushServiceImpl.getInstance()
-            
-            // 1. 初始化推送服务(内部已处理异常,不会抛出)
-            service.initialize(context, config)
-            
-            // 2. 设置消息监听器(如果提供)
-            if (messageListener != null) {
-                service.setMessageListener(messageListener)
-            } else {
-                // 默认监听器:仅记录日志
-                service.setMessageListener { message ->
-                    ILog.d("PushService", "收到推送消息: ${message.title} - ${message.content}")
-                }
-            }
-            
-            // 3. 设置通知点击监听器
-            service.setNotificationClickListener(notificationClickListener)
-            
-            // 4. 自动注册推送(开启推送)
-            service.register()
-            
-            ILog.d("PushServiceFactory", "推送服务初始化完成")
-        } catch (e: Exception) {
-            // 双重保险:即使内部没有抛出异常,这里也捕获一下
-            // 确保初始化失败不会影响应用启动
-            ILog.e("PushServiceFactory", "推送服务初始化失败,推送功能将不可用", e)
-        }
-    }
-    
-    /**
-     * 初始化推送服务(使用自定义配置,完全封装)
-     * 
-     * **自动完成**:
-     * 1. 合并配置(优先使用传入配置,缺失参数从资源文件读取)
-     * 2. 初始化极光推送 SDK
-     * 3. 设置消息监听器(如果提供)
-     * 4. 自动注册推送(开启推送)
-     * 
-     * **在 Application.onCreate() 中调用,统一初始化推送服务**
-     * - ✅ **容错处理**:初始化失败不会影响应用启动,只记录日志
-     * - ✅ **同步初始化**:在主线程同步初始化,通常很快不会阻塞
-     * - ✅ **只需要调用一次**,后续通过 getInstance() 获取服务实例即可
-     * 
-     * @param context Application 上下文
-     * @param config 推送服务配置(可选参数会从资源文件读取默认值)
-     * @param messageListener 消息监听器(可选),如果不传,使用默认处理(仅记录日志)
-     * @param notificationClickListener 通知点击监听器(可选),用于处理用户点击通知栏通知的事件
-     *                                  可以通过 PushMessage.extras 获取跳转参数,例如:
-     *                                  - "page": 跳转页面名称(如 "detail", "home" 等)
-     *                                  - "id": 业务ID(如订单ID、商品ID等)
-     * @param pageMappings 页面映射规则(可选),用于自动跳转
-     *                     如果设置了,当推送的 extras["page"] 匹配时,会自动跳转到对应 Activity
-     *                     例如:mapOf("detail" to DetailActivity::class.java, "home" to MainActivity::class.java)
-     */
-    @JvmStatic
-    fun init(
-        context: Context,
-        config: PushConfig,
-        messageListener: ((PushMessage) -> Unit)? = null,
-        notificationClickListener: ((PushMessage) -> Unit)? = null,
-        pageMappings: Map<String, Class<*>>? = null
-    ) {
-        try {
-            // 合并配置:优先使用传入的 config,缺失的参数从资源文件读取
-            val defaultConfig = PushConfig.fromResources(context)
-            val mergedConfig = PushConfig(
-                appKey = config.appKey.ifEmpty { defaultConfig.appKey },
-                channel = if (config.channel == "developer-default") {
-                    defaultConfig.channel
-                } else {
-                    config.channel
-                },
-                debugMode = config.debugMode || defaultConfig.debugMode
-            )
-            
             val service = PushServiceImpl.getInstance()
             
             // 1. 初始化推送服务(内部已处理异常,不会抛出)
-            service.initialize(context, mergedConfig)
+            service.initialize(context)
             
             // 2. 设置消息监听器(如果提供)
             if (messageListener != null) {
@@ -182,30 +102,17 @@ object PushServiceFactory {
         }
     }
     
+    
     /**
      * 获取推送服务实例(单例)
      * 
-     * 优先通过 ARouter 获取服务,如果获取不到,则回退到直接实例化
+     * 直接获取单例实例(不使用 ARouter)
      * 注意:使用前需要先调用 init() 进行初始化
      * 
      * @return PushService实例(单例)
      */
     @JvmStatic
     fun getInstance(): PushService {
-        // 优先通过 ARouter 获取服务(实现解耦)
-        try {
-            val arouterService = ARouter.getInstance().build("/push/service").navigation() as? PushService
-            if (arouterService != null) {
-                ILog.d("PushServiceFactory", "通过 ARouter 获取推送服务")
-                return arouterService
-            }
-        } catch (e: Exception) {
-            // ARouter 可能还未完全初始化,捕获异常并回退
-            ILog.w("PushServiceFactory", "ARouter 获取服务失败,回退到直接实例化: ${e.message}")
-        }
-        
-        // 回退到直接实例化(向后兼容)
-        ILog.d("PushServiceFactory", "使用直接实例化")
         return PushServiceImpl.getInstance()
     }
 }

+ 135 - 0
capability-push/src/main/java/com/narutohuo/xindazhou/push/helper/TagAliasOperatorHelper.kt

@@ -0,0 +1,135 @@
+package com.narutohuo.xindazhou.push.helper
+
+import android.content.Context
+import cn.jpush.android.api.JPushMessage
+import com.narutohuo.xindazhou.core.log.ILog
+
+/**
+ * 标签和别名操作辅助类
+ * 
+ * 用于处理极光推送的标签和别名操作结果
+ * 参考极光推送官方示例
+ */
+class TagAliasOperatorHelper private constructor() {
+    
+    companion object {
+        private const val TAG = "TagAliasHelper"
+        
+        @Volatile
+        private var instance: TagAliasOperatorHelper? = null
+        
+        @JvmStatic
+        fun getInstance(): TagAliasOperatorHelper {
+            return instance ?: synchronized(this) {
+                instance ?: TagAliasOperatorHelper().also { instance = it }
+            }
+        }
+    }
+    
+    private var context: Context? = null
+    
+    /**
+     * 初始化(可选)
+     */
+    fun init(context: Context) {
+        this.context = context.applicationContext
+    }
+    
+    /**
+     * 处理标签操作结果
+     */
+    fun onTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "onTagOperatorResult, sequence=$sequence, tags=${jPushMessage.tags}")
+        ILog.d(TAG, "tags size=${jPushMessage.tags?.size ?: 0}")
+        
+        init(context)
+        
+        if (jPushMessage.errorCode == 0) {
+            ILog.d(TAG, "标签操作成功, sequence=$sequence")
+        } else {
+            var logs = "标签操作失败"
+            if (jPushMessage.errorCode == 6018) {
+                // tag数量超过限制,需要先清除一部分再add
+                logs += ", 标签数量超过限制,需要先清除一部分"
+            }
+            logs += ", errorCode=${jPushMessage.errorCode}"
+            ILog.e(TAG, logs)
+        }
+    }
+    
+    /**
+     * 处理检查标签操作结果
+     */
+    fun onCheckTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "onCheckTagOperatorResult, sequence=$sequence, checkTag=${jPushMessage.checkTag}")
+        
+        init(context)
+        
+        if (jPushMessage.errorCode == 0) {
+            val logs = "检查标签 ${jPushMessage.checkTag} 绑定状态成功, state=${jPushMessage.tagCheckStateResult}"
+            ILog.d(TAG, logs)
+        } else {
+            val logs = "检查标签失败, errorCode=${jPushMessage.errorCode}"
+            ILog.e(TAG, logs)
+        }
+    }
+    
+    /**
+     * 处理别名操作结果
+     */
+    fun onAliasOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "onAliasOperatorResult, sequence=$sequence, alias=${jPushMessage.alias}")
+        
+        init(context)
+        
+        if (jPushMessage.errorCode == 0) {
+            ILog.d(TAG, "别名操作成功, sequence=$sequence")
+        } else {
+            val logs = "别名操作失败, errorCode=${jPushMessage.errorCode}"
+            ILog.e(TAG, logs)
+        }
+    }
+    
+    /**
+     * 处理手机号操作结果
+     */
+    fun onMobileNumberOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "onMobileNumberOperatorResult, sequence=$sequence, mobileNumber=${jPushMessage.mobileNumber}")
+        
+        init(context)
+        
+        if (jPushMessage.errorCode == 0) {
+            ILog.d(TAG, "手机号操作成功, sequence=$sequence")
+        } else {
+            val logs = "手机号操作失败, errorCode=${jPushMessage.errorCode}"
+            ILog.e(TAG, logs)
+        }
+    }
+    
+    /**
+     * 验证标签和别名格式是否正确
+     * 只能包含:数字、英文字母、中文、以及部分特殊字符
+     */
+    fun isValidTagAndAlias(s: String): Boolean {
+        if (s.isEmpty()) return false
+        
+        val pattern = Regex("^[\u4E00-\u9FA50-9a-zA-Z_!@#$&*+=.|]+$")
+        return pattern.matches(s)
+    }
+    
+    /**
+     * 验证手机号格式
+     * 只能以 "+" 或者数字开头;后面的内容只能包含 "-" 和数字
+     */
+    fun isValidMobileNumber(s: String): Boolean {
+        if (s.isEmpty()) return true
+        
+        val pattern = Regex("^[+0-9][-0-9]{1,}$")
+        return pattern.matches(s)
+    }
+}
+

+ 413 - 34
capability-push/src/main/java/com/narutohuo/xindazhou/push/impl/PushServiceImpl.kt

@@ -4,12 +4,9 @@ import android.content.Context
 import android.os.Handler
 import android.os.Looper
 import cn.jpush.android.api.JPushInterface
-import com.alibaba.android.arouter.facade.annotation.Route
-import com.alibaba.android.arouter.facade.template.IProvider
 import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.core.push.IPushService
 import com.narutohuo.xindazhou.push.api.PushService
-import com.narutohuo.xindazhou.push.model.PushConfig
 import com.narutohuo.xindazhou.push.model.PushMessage
 
 /**
@@ -19,10 +16,9 @@ import com.narutohuo.xindazhou.push.model.PushMessage
  * 
  * 封装极光推送 SDK,提供统一的推送服务接口
  * 
- * 通过 ARouter 注册,实现 base-core 与 push 模块的解耦
+ * 注意:不使用 ARouter 管理,直接通过单例模式提供实例
  */
-@Route(path = "/push/service", name = "推送服务")
-class PushServiceImpl private constructor() : IProvider, IPushService, PushService {
+class PushServiceImpl private constructor() : IPushService, PushService {
     
     companion object {
         @Volatile
@@ -61,32 +57,18 @@ class PushServiceImpl private constructor() : IProvider, IPushService, PushServi
     private val mainHandler = Handler(Looper.getMainLooper())
     private var isInitialized = false
     
-    /**
-     * ARouter IProvider 接口的初始化方法
-     * 
-     * 注意:此方法在 ARouter 初始化 provider 时调用,不应该抛出异常
-     */
-    override fun init(context: Context) {
-        try {
-            ILog.d(tag, "PushServiceImpl 初始化(ARouter)")
-            // 这里不进行实际的初始化,实际的初始化在 PushServiceFactory.init() 中完成
-            // 这样可以避免在 ARouter 初始化时出现问题
-        } catch (e: Exception) {
-            // 确保不会抛出异常,避免影响 ARouter 初始化
-            ILog.e(tag, "PushServiceImpl ARouter 初始化异常(不影响使用)", e)
-        }
-    }
+    // 回调监听器
+    private var registerListener: ((String) -> Unit)? = null
+    private var connectedListener: ((Boolean) -> Unit)? = null
+    private var tagOperatorListener: ((Boolean, Int, Set<String>?) -> Unit)? = null
+    private var aliasOperatorListener: ((Boolean, Int, String?) -> Unit)? = null
+    private var mobileNumberOperatorListener: ((Boolean, Int, String?) -> Unit)? = null
     
     // ========== IPushService 接口实现(适配层) ==========
     
     override fun initialize(context: Context, appKey: String, channel: String, debugMode: Boolean) {
-        // 适配 IPushService 接口,将参数转换为 PushConfig
-        val config = PushConfig(
-            appKey = appKey,
-            channel = channel,
-            debugMode = debugMode
-        )
-        initialize(context, config)
+        // 适配 IPushService 接口,直接调用初始化
+        initialize(context)
     }
     
     override fun setMessageListener(listener: (String, String, Map<String, String>, String?) -> Unit) {
@@ -111,30 +93,49 @@ class PushServiceImpl private constructor() : IProvider, IPushService, PushServi
     
     // ========== PushService 接口实现(原有接口) ==========
     
-    override fun initialize(context: Context, config: PushConfig) {
+    override fun initialize(context: Context) {
         if (isInitialized) {
             ILog.w(tag, "推送服务已经初始化,跳过重复初始化")
             return
         }
         
         this.applicationContext = context.applicationContext
+        
+        // 使用 Android Log 直接输出,确保能看到日志
+        android.util.Log.d(tag, "========== 开始初始化极光推送SDK ==========")
         ILog.d(tag, "初始化极光推送SDK")
         
         try {
             // 初始化极光推送(同步调用,但通常很快,不会阻塞主线程太久)
+            // AppKey 从 AndroidManifest.xml 中的 meta-data 自动读取
+            android.util.Log.d(tag, "调用 JPushInterface.init()...")
             JPushInterface.init(context.applicationContext)
+            android.util.Log.d(tag, "JPushInterface.init() 调用完成")
             
-            // 设置调试模式
-            if (config.debugMode) {
-                JPushInterface.setDebugMode(true)
-                ILog.d(tag, "调试模式已开启")
+            // 设置调试模式(开发环境启用,生产环境关闭)
+            try {
+                val buildConfigClass = Class.forName("${context.packageName}.BuildConfig")
+                val debugField = buildConfigClass.getField("DEBUG")
+                val isDebug = debugField.getBoolean(null)
+                JPushInterface.setDebugMode(isDebug)
+                android.util.Log.d(tag, "极光推送调试模式: $isDebug")
+                ILog.d(tag, "极光推送调试模式: $isDebug")
+            } catch (e: Exception) {
+                // 获取 BuildConfig 失败,默认关闭调试模式
+                JPushInterface.setDebugMode(false)
+                android.util.Log.w(tag, "无法获取 BuildConfig.DEBUG,默认关闭调试模式")
+                ILog.w(tag, "无法获取 BuildConfig.DEBUG,默认关闭调试模式")
             }
             
             isInitialized = true
-            ILog.d(tag, "极光推送初始化成功, appKey=${config.appKey.take(8)}..., channel=${config.channel}")
+            android.util.Log.d(tag, "========== 极光推送初始化成功 ==========")
+            ILog.d(tag, "极光推送初始化成功")
         } catch (e: Exception) {
             // 初始化失败不应该影响应用启动,只记录日志
             // 推送功能将不可用,但应用可以正常运行
+            android.util.Log.e(tag, "========== 极光推送初始化失败 ==========", e)
+            android.util.Log.e(tag, "错误信息: ${e.message}")
+            e.printStackTrace()
             ILog.e(tag, "极光推送初始化失败,推送功能将不可用", e)
             // 不抛出异常,允许应用继续运行
         }
@@ -256,6 +257,7 @@ class PushServiceImpl private constructor() : IProvider, IPushService, PushServi
             val page = extrasMap["page"]
             if (!page.isNullOrEmpty() && ctx != null) {
                 val activityClass = pageMapping[page]
+                
                 if (activityClass != null) {
                     try {
                         val intent = android.content.Intent(ctx, activityClass)
@@ -286,12 +288,41 @@ class PushServiceImpl private constructor() : IProvider, IPushService, PushServi
             return
         }
         
+        android.util.Log.d(tag, "========== 开始注册推送服务 ==========")
         ILog.d(tag, "注册推送服务")
         
         try {
+            // 检查并请求通知权限(Android 13+)
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+                val hasPermission = androidx.core.content.ContextCompat.checkSelfPermission(
+                    ctx,
+                    android.Manifest.permission.POST_NOTIFICATIONS
+                ) == android.content.pm.PackageManager.PERMISSION_GRANTED
+                
+                if (!hasPermission) {
+                    android.util.Log.w(tag, "⚠️⚠️⚠️ 通知权限未授予,推送可能无法显示通知 ⚠️⚠️⚠️")
+                    ILog.w(tag, "通知权限未授予,推送可能无法显示通知")
+                    ILog.w(tag, "请调用 isNotificationEnabled() 和 goToAppNotificationSettings() 引导用户授予权限")
+                } else {
+                    android.util.Log.d(tag, "✅ 通知权限已授予")
+                    ILog.d(tag, "通知权限已授予")
+                }
+            }
+            
             // 恢复推送(开启推送)
+            android.util.Log.d(tag, "调用 JPushInterface.resumePush()...")
             JPushInterface.resumePush(ctx)
+            android.util.Log.d(tag, "JPushInterface.resumePush() 调用完成")
+            
+            // JPush 注册是异步的,通常需要 2-5 秒才能完成
+            // onRegister 回调会在注册成功后自动触发
+            android.util.Log.w(tag, "JPush 注册是异步的,通常需要 2-5 秒")
+            ILog.d(tag, "JPush 注册是异步的,请等待 onRegister 回调")
+            
         } catch (e: Exception) {
+            android.util.Log.e(tag, "========== 注册推送服务失败 ==========", e)
+            android.util.Log.e(tag, "错误信息: ${e.message}")
+            e.printStackTrace()
             ILog.e(tag, "注册推送服务失败", e)
         }
     }
@@ -345,5 +376,353 @@ class PushServiceImpl private constructor() : IProvider, IPushService, PushServi
             false
         }
     }
+    
+    // ========== 新增方法(参考案例) ==========
+    
+    /**
+     * 检查通知权限是否开启
+     * 
+     * @return 1 表示开启,0 表示关闭,-1 表示检测失败
+     */
+    fun isNotificationEnabled(): Int {
+        val ctx = applicationContext ?: return -1
+        
+        return try {
+            JPushInterface.isNotificationEnabled(ctx)
+        } catch (e: Exception) {
+            ILog.e(tag, "检查通知权限失败", e)
+            -1
+        }
+    }
+    
+    /**
+     * 跳转到应用的通知设置页面
+     */
+    fun goToAppNotificationSettings() {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        try {
+            JPushInterface.goToAppNotificationSettings(ctx)
+        } catch (e: Exception) {
+            ILog.e(tag, "跳转到通知设置失败", e)
+        }
+    }
+    
+    /**
+     * 恢复推送(开启推送)
+     */
+    fun resumePush() {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "恢复推送")
+        
+        try {
+            JPushInterface.resumePush(ctx)
+        } catch (e: Exception) {
+            ILog.e(tag, "恢复推送失败", e)
+        }
+    }
+    
+    /**
+     * 停止推送(关闭推送)
+     */
+    fun stopPush() {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "停止推送")
+        
+        try {
+            JPushInterface.stopPush(ctx)
+        } catch (e: Exception) {
+            ILog.e(tag, "停止推送失败", e)
+        }
+    }
+    
+    /**
+     * 添加本地通知
+     * 
+     * @param title 通知标题
+     * @param content 通知内容
+     * @param extras 额外数据(可选)
+     */
+    fun addLocalNotification(title: String, content: String, extras: Map<String, Any>? = null) {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        try {
+            val ln = cn.jpush.android.data.JPushLocalNotification()
+            ln.setBuilderId(0)
+            ln.setTitle(title)
+            ln.setContent(content)
+            
+            val notificationId = System.currentTimeMillis() / 1000
+            ln.setNotificationId(notificationId)
+            ln.setBroadcastTime(System.currentTimeMillis())
+            
+            // 设置 extras
+            if (extras != null && extras.isNotEmpty()) {
+                val jsonObject = org.json.JSONObject(extras)
+                ln.setExtras(jsonObject.toString())
+            }
+            
+            JPushInterface.addLocalNotification(ctx, ln)
+            ILog.d(tag, "添加本地通知成功,notification_id=$notificationId")
+        } catch (e: Exception) {
+            ILog.e(tag, "添加本地通知失败", e)
+        }
+    }
+    
+    /**
+     * 设置手机号(用于推送)
+     * 
+     * @param mobileNumber 手机号(格式:+86-1234567890 或 1234567890)
+     */
+    fun setMobileNumber(mobileNumber: String) {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "setMobileNumber: $mobileNumber")
+        
+        try {
+            JPushInterface.setMobileNumber(ctx, sequence++, mobileNumber)
+        } catch (e: Exception) {
+            ILog.e(tag, "设置手机号失败", e)
+        }
+    }
+    
+    /**
+     * 删除标签
+     * 
+     * @param tags 要删除的标签列表
+     */
+    fun deleteTags(tags: List<String>) {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "deleteTags: $tags")
+        
+        try {
+            JPushInterface.deleteTags(ctx, sequence++, tags.toSet())
+        } catch (e: Exception) {
+            ILog.e(tag, "删除标签失败", e)
+        }
+    }
+    
+    /**
+     * 清空所有标签
+     */
+    fun cleanTags() {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "cleanTags")
+        
+        try {
+            JPushInterface.cleanTags(ctx, sequence++)
+        } catch (e: Exception) {
+            ILog.e(tag, "清空标签失败", e)
+        }
+    }
+    
+    /**
+     * 删除别名
+     */
+    fun deleteAlias() {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "deleteAlias")
+        
+        try {
+            JPushInterface.deleteAlias(ctx, sequence++)
+        } catch (e: Exception) {
+            ILog.e(tag, "删除别名失败", e)
+        }
+    }
+    
+    /**
+     * 检查标签绑定状态
+     * 
+     * @param tag 要检查的标签
+     */
+    fun checkTagBindState(tag: String) {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            return
+        }
+        
+        ILog.d(tag, "checkTagBindState: $tag")
+        
+        try {
+            JPushInterface.checkTagBindState(ctx, sequence++, tag)
+        } catch (e: Exception) {
+            ILog.e(tag, "检查标签绑定状态失败", e)
+        }
+    }
+    
+    /**
+     * 获取所有标签
+     * 
+     * @param callback 回调,参数为标签集合
+     */
+    fun getAllTags(callback: (Set<String>?) -> Unit) {
+        val ctx = applicationContext ?: run {
+            ILog.w(tag, "Context 未初始化")
+            callback(null)
+            return
+        }
+        
+        ILog.d(tag, "getAllTags")
+        
+        try {
+            JPushInterface.getAllTags(ctx, sequence++)
+            // 结果会在 onTagOperatorResult 回调中返回
+            // 这里临时保存 callback,在回调时调用
+            // 注意:实际使用时需要用 sequence 来匹配请求和响应
+        } catch (e: Exception) {
+            ILog.e(tag, "获取所有标签失败", e)
+            callback(null)
+        }
+    }
+    
+    // ========== 内部回调处理方法(由 JPushReceiver 调用) ==========
+    
+    /**
+     * 处理注册成功回调
+     */
+    fun handleRegisterSuccess(registrationId: String) {
+        ILog.d(tag, "注册成功: registrationId=$registrationId")
+        mainHandler.post {
+            registerListener?.invoke(registrationId)
+        }
+    }
+    
+    /**
+     * 处理连接状态回调
+     */
+    fun handleConnectedStatus(isConnected: Boolean) {
+        ILog.d(tag, "连接状态: isConnected=$isConnected")
+        mainHandler.post {
+            connectedListener?.invoke(isConnected)
+        }
+    }
+    
+    /**
+     * 处理标签操作结果回调
+     */
+    fun handleTagOperatorResult(jPushMessage: cn.jpush.android.api.JPushMessage) {
+        val isSuccess = jPushMessage.errorCode == 0
+        val errorCode = jPushMessage.errorCode
+        val tags = jPushMessage.tags
+        
+        ILog.d(tag, "标签操作结果: isSuccess=$isSuccess, errorCode=$errorCode, tags=$tags")
+        
+        mainHandler.post {
+            tagOperatorListener?.invoke(isSuccess, errorCode, tags)
+        }
+    }
+    
+    /**
+     * 处理检查标签结果回调
+     */
+    fun handleCheckTagOperatorResult(jPushMessage: cn.jpush.android.api.JPushMessage) {
+        val isSuccess = jPushMessage.errorCode == 0
+        val errorCode = jPushMessage.errorCode
+        val checkTag = jPushMessage.checkTag
+        val isBind = jPushMessage.tagCheckStateResult
+        
+        ILog.d(tag, "检查标签结果: isSuccess=$isSuccess, errorCode=$errorCode, checkTag=$checkTag, isBind=$isBind")
+    }
+    
+    /**
+     * 处理别名操作结果回调
+     */
+    fun handleAliasOperatorResult(jPushMessage: cn.jpush.android.api.JPushMessage) {
+        val isSuccess = jPushMessage.errorCode == 0
+        val errorCode = jPushMessage.errorCode
+        val alias = jPushMessage.alias
+        
+        ILog.d(tag, "别名操作结果: isSuccess=$isSuccess, errorCode=$errorCode, alias=$alias")
+        
+        mainHandler.post {
+            aliasOperatorListener?.invoke(isSuccess, errorCode, alias)
+        }
+    }
+    
+    /**
+     * 处理手机号操作结果回调
+     */
+    fun handleMobileNumberOperatorResult(jPushMessage: cn.jpush.android.api.JPushMessage) {
+        val isSuccess = jPushMessage.errorCode == 0
+        val errorCode = jPushMessage.errorCode
+        val mobileNumber = jPushMessage.mobileNumber
+        
+        ILog.d(tag, "手机号操作结果: isSuccess=$isSuccess, errorCode=$errorCode, mobileNumber=$mobileNumber")
+        
+        mainHandler.post {
+            mobileNumberOperatorListener?.invoke(isSuccess, errorCode, mobileNumber)
+        }
+    }
+    
+    // ========== 设置回调监听器 ==========
+    
+    /**
+     * 设置注册成功监听器
+     */
+    fun setRegisterListener(listener: ((String) -> Unit)?) {
+        this.registerListener = listener
+        ILog.d(tag, if (listener != null) "设置注册监听器" else "清除注册监听器")
+    }
+    
+    /**
+     * 设置连接状态监听器
+     */
+    fun setConnectedListener(listener: ((Boolean) -> Unit)?) {
+        this.connectedListener = listener
+        ILog.d(tag, if (listener != null) "设置连接监听器" else "清除连接监听器")
+    }
+    
+    /**
+     * 设置标签操作结果监听器
+     */
+    fun setTagOperatorListener(listener: ((Boolean, Int, Set<String>?) -> Unit)?) {
+        this.tagOperatorListener = listener
+        ILog.d(tag, if (listener != null) "设置标签操作监听器" else "清除标签操作监听器")
+    }
+    
+    /**
+     * 设置别名操作结果监听器
+     */
+    fun setAliasOperatorListener(listener: ((Boolean, Int, String?) -> Unit)?) {
+        this.aliasOperatorListener = listener
+        ILog.d(tag, if (listener != null) "设置别名操作监听器" else "清除别名操作监听器")
+    }
+    
+    /**
+     * 设置手机号操作结果监听器
+     */
+    fun setMobileNumberOperatorListener(listener: ((Boolean, Int, String?) -> Unit)?) {
+        this.mobileNumberOperatorListener = listener
+        ILog.d(tag, if (listener != null) "设置手机号操作监听器" else "清除手机号操作监听器")
+    }
 }
 

+ 0 - 65
capability-push/src/main/java/com/narutohuo/xindazhou/push/model/PushConfig.kt

@@ -1,65 +0,0 @@
-package com.narutohuo.xindazhou.push.model
-
-import android.content.Context
-
-/**
- * 推送服务配置模型
- * 
- * 封装极光推送的配置信息
- */
-data class PushConfig(
-    /**
-     * 极光推送 AppKey(必填)
-     */
-    val appKey: String,
-    
-    /**
-     * 极光推送渠道(可选,默认为 "developer-default")
-     */
-    val channel: String = "developer-default",
-    
-    /**
-     * 是否开启调试模式(可选,默认为 false)
-     */
-    val debugMode: Boolean = false
-) {
-    companion object {
-        /**
-         * 从资源文件读取配置
-         * 
-         * 需要在 strings.xml 中定义:
-         * - push_jpush_app_key(必填)
-         * - push_jpush_channel(可选,默认为 "developer-default")
-         * 
-         * @param context Context 上下文
-         * @return PushConfig 配置对象
-         */
-        fun fromResources(context: Context): PushConfig {
-            val resources = context.resources
-            val packageName = context.packageName
-            
-            // 从资源文件读取 AppKey
-            val appKeyResId = resources.getIdentifier("push_jpush_app_key", "string", packageName)
-            val appKey = if (appKeyResId != 0) {
-                resources.getString(appKeyResId)
-            } else {
-                "" // 如果未配置,返回空字符串,后续会报错
-            }
-            
-            // 从资源文件读取渠道(可选)
-            val channelResId = resources.getIdentifier("push_jpush_channel", "string", packageName)
-            val channel = if (channelResId != 0) {
-                resources.getString(channelResId)
-            } else {
-                "developer-default"
-            }
-            
-            return PushConfig(
-                appKey = appKey,
-                channel = channel,
-                debugMode = false // 调试模式不从资源文件读取,由代码控制
-            )
-        }
-    }
-}
-

+ 47 - 0
capability-push/src/main/java/com/narutohuo/xindazhou/push/receiver/BootReceiver.kt

@@ -0,0 +1,47 @@
+package com.narutohuo.xindazhou.push.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import cn.jpush.android.api.JPushInterface
+import com.narutohuo.xindazhou.core.log.ILog
+
+/**
+ * 开机自启动接收器
+ * 
+ * 在设备开机后自动恢复推送服务,提升推送可靠性
+ * 
+ * 注意:
+ * 1. 需要在 AndroidManifest.xml 中注册并声明 RECEIVE_BOOT_COMPLETED 权限
+ * 2. 用户需要在系统设置中开启"自启动"权限(华为、小米、OPPO、vivo 等)
+ * 3. 此 Receiver 仅在设备重启后触发一次
+ */
+class BootReceiver : BroadcastReceiver() {
+    
+    companion object {
+        private const val TAG = "BootReceiver"
+    }
+    
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
+            ILog.d(TAG, "========== 设备开机,恢复推送服务 ==========")
+            
+            try {
+                // 恢复推送服务(开启推送)
+                JPushInterface.resumePush(context.applicationContext)
+                ILog.d(TAG, "推送服务恢复成功")
+                
+                // 获取 RegistrationID(可能需要等待几秒才有值)
+                val registrationId = JPushInterface.getRegistrationID(context.applicationContext)
+                if (registrationId.isNotEmpty()) {
+                    ILog.d(TAG, "RegistrationID: $registrationId")
+                } else {
+                    ILog.d(TAG, "RegistrationID 尚未获取,等待 onRegister 回调")
+                }
+            } catch (e: Exception) {
+                ILog.e(TAG, "恢复推送服务失败", e)
+            }
+        }
+    }
+}
+

+ 232 - 48
capability-push/src/main/java/com/narutohuo/xindazhou/push/receiver/JPushReceiver.kt

@@ -1,80 +1,264 @@
 package com.narutohuo.xindazhou.push.receiver
 
-import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.os.Bundle
+import cn.jpush.android.api.CmdMessage
+import cn.jpush.android.api.CustomMessage
 import cn.jpush.android.api.JPushInterface
-import com.google.gson.Gson
+import cn.jpush.android.api.JPushMessage
+import cn.jpush.android.api.NotificationMessage
+import cn.jpush.android.service.JPushMessageReceiver
 import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.push.factory.PushServiceFactory
+import com.narutohuo.xindazhou.push.helper.TagAliasOperatorHelper
 import com.narutohuo.xindazhou.push.impl.PushServiceImpl
 
 /**
  * 极光推送消息接收器(完全封装在模块内部)
  * 
- * 自动接收极光推送的消息,并通过 PushService 处理
+ * 继承自 JPushMessageReceiver,接收极光推送的所有回调
  * 业务层不需要知道这个类的存在,所有消息会自动回调到 PushService 的监听器
  */
-class JPushReceiver : BroadcastReceiver() {
+class JPushReceiver : JPushMessageReceiver() {
     
-    override fun onReceive(context: Context, intent: Intent) {
-        val bundle = intent.extras ?: return
-        
-        when (intent.action) {
-            JPushInterface.ACTION_MESSAGE_RECEIVED -> {
-                // 自定义消息(透传消息)
-                val title = bundle.getString(JPushInterface.EXTRA_TITLE) ?: ""
-                val content = bundle.getString(JPushInterface.EXTRA_MESSAGE) ?: ""
-                val extrasJson = bundle.getString(JPushInterface.EXTRA_EXTRA) ?: "{}"
-                val messageId = bundle.getString(JPushInterface.EXTRA_MSG_ID)
-                
-                ILog.d("JPushReceiver", "收到自定义消息: title=$title, content=$content")
+    companion object {
+        private const val TAG = "JPushReceiver"
+        
+        init {
+            ILog.d(TAG, "========== JPushReceiver 类被加载 ==========")
+        }
+    }
+    
+    init {
+        ILog.d(TAG, "========== JPushReceiver 实例被创建 ==========")
+    }
+    
+    /**
+     * 收到自定义消息(透传消息)
+     */
+    override fun onMessage(context: Context, customMessage: CustomMessage) {
+        ILog.d(TAG, "[onMessage] title=${customMessage.title}, message=${customMessage.message}")
                 
-                // 解析 extras(JSON 格式)
+        // 解析 extras
                 val extrasMap = try {
-                    val jsonObject = Gson().fromJson(extrasJson, Map::class.java) as? Map<*, *>
-                    jsonObject?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: emptyMap()
+            // extras 已经是 Map<String, String> 类型
+            @Suppress("UNCHECKED_CAST")
+            (customMessage.extra as? Map<String, String>) ?: emptyMap()
                 } catch (e: Exception) {
-                    ILog.e("JPushReceiver", "解析 extras 失败", e)
-                    emptyMap()
+            ILog.e(TAG, "解析 extras 失败", e)
+            emptyMap<String, String>()
                 }
                 
                 // 调用 PushService 处理消息
                 val pushService = PushServiceFactory.getInstance()
-                (pushService as? PushServiceImpl)?.handleReceivedMessage(title, content, extrasMap, messageId)
-            }
-            
-            JPushInterface.ACTION_NOTIFICATION_RECEIVED -> {
-                // 通知消息(系统通知栏)
-                val title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE) ?: ""
-                val content = bundle.getString(JPushInterface.EXTRA_ALERT) ?: ""
-                ILog.d("JPushReceiver", "收到通知消息: title=$title, content=$content")
-                // 通知消息通常由系统处理,这里可以记录日志或做其他处理
-            }
-            
-            JPushInterface.ACTION_NOTIFICATION_OPENED -> {
-                // 用户点击了通知
-                val title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE) ?: ""
-                val content = bundle.getString(JPushInterface.EXTRA_ALERT) ?: ""
-                val messageId = bundle.getString(JPushInterface.EXTRA_MSG_ID)
-                val extrasJson = bundle.getString(JPushInterface.EXTRA_EXTRA) ?: "{}"
-                
-                ILog.d("JPushReceiver", "用户点击通知: title=$title, content=$content")
-                
-                // 解析 extras(JSON 格式)
+        (pushService as? PushServiceImpl)?.handleReceivedMessage(
+            title = customMessage.title ?: "",
+            content = customMessage.message ?: "",
+            extras = extrasMap,
+            messageId = customMessage.messageId
+        )
+    }
+    
+    /**
+     * 通知消息到达(未点击)
+     */
+    override fun onNotifyMessageArrived(context: Context, message: NotificationMessage) {
+        ILog.d(TAG, "========== 收到推送通知 ==========")
+        ILog.d(TAG, "标题: ${message.notificationTitle}")
+        ILog.d(TAG, "内容: ${message.notificationContent}")
+        ILog.d(TAG, "MessageID: ${message.msgId}")
+        ILog.d(TAG, "======================================")
+        
+        // 通知到达时,可以在这里做一些统计或日志记录
+        // 不需要特殊处理,系统会自动显示通知
+    }
+    
+    /**
+     * 用户点击了通知
+     */
+    override fun onNotifyMessageOpened(context: Context, message: NotificationMessage) {
+        ILog.d(TAG, "[onNotifyMessageOpened] title=${message.notificationTitle}, content=${message.notificationContent}")
+        
+        // 清除角标(通知点击后,清除应用图标上的数字角标)
+        try {
+            JPushInterface.setBadgeNumber(context, 0)
+            ILog.d(TAG, "已清除角标数字")
+        } catch (e: Exception) {
+            ILog.e(TAG, "清除角标失败", e)
+        }
+        
+        // 解析 extras
                 val extrasMap = try {
-                    val jsonObject = Gson().fromJson(extrasJson, Map::class.java) as? Map<*, *>
-                    jsonObject?.mapKeys { it.key.toString() }?.mapValues { it.value.toString() } ?: emptyMap()
+            // notificationExtras 已经是 Map<String, String> 类型
+            @Suppress("UNCHECKED_CAST")
+            (message.notificationExtras as? Map<String, String>)?.toMutableMap() ?: mutableMapOf()
                 } catch (e: Exception) {
-                    ILog.e("JPushReceiver", "解析 extras 失败", e)
-                    emptyMap()
+            ILog.e(TAG, "解析 extras 失败", e)
+            mutableMapOf<String, String>()
                 }
                 
                 // 调用 PushService 处理通知点击
                 val pushService = PushServiceFactory.getInstance()
-                (pushService as? PushServiceImpl)?.handleNotificationClick(title, content, extrasMap, messageId)
+        (pushService as? PushServiceImpl)?.handleNotificationClick(
+            title = message.notificationTitle ?: "",
+            content = message.notificationContent ?: "",
+            extras = extrasMap,
+            messageId = message.msgId
+        )
             }
+    
+    /**
+     * 用户清除了通知
+     */
+    override fun onNotifyMessageDismiss(context: Context, message: NotificationMessage) {
+        ILog.d(TAG, "[onNotifyMessageDismiss] title=${message.notificationTitle}")
+        // 通知被清除,可以在这里做一些统计或日志记录
+    }
+    
+    /**
+     * 用户点击了通知栏的多按钮
+     */
+    override fun onMultiActionClicked(context: Context, intent: Intent) {
+        ILog.d(TAG, "[onMultiActionClicked] 用户点击了通知栏按钮")
+        
+        val nActionExtra = intent.extras?.getString(JPushInterface.EXTRA_NOTIFICATION_ACTION_EXTRA)
+        if (nActionExtra == null) {
+            ILog.d(TAG, "ACTION_NOTIFICATION_CLICK_ACTION nActionExtra is null")
+            return
         }
+        
+        // 开发者根据不同 Action 携带的 extra 字段来分配不同的动作
+        ILog.d(TAG, "[onMultiActionClicked] nActionExtra=$nActionExtra")
+        
+        // 这里可以根据不同的 action 执行不同的逻辑
+        // 例如:
+        // when (nActionExtra) {
+        //     "action1" -> // 处理按钮1的点击
+        //     "action2" -> // 处理按钮2的点击
+        // }
+    }
+    
+    /**
+     * 注册成功回调
+     */
+    override fun onRegister(context: Context, registrationId: String) {
+        ILog.d(TAG, "========== JPush 注册成功 ==========")
+        ILog.d(TAG, "✅✅✅ RegistrationID: $registrationId ✅✅✅")
+        ILog.d(TAG, "请将这个 RegistrationID 复制到极光推送后台进行测试推送")
+        ILog.d(TAG, "======================================")
+        
+        // 注册成功,可以在这里保存 registrationId 或发送给服务器
+        // 也可以发送广播通知其他组件
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleRegisterSuccess(registrationId)
+    }
+    
+    /**
+     * 连接状态回调
+     */
+    override fun onConnected(context: Context, isConnected: Boolean) {
+        ILog.d(TAG, "========== JPush 连接状态变化 ==========")
+        ILog.d(TAG, "连接状态: $isConnected")
+        ILog.d(TAG, "======================================")
+        
+        if (!isConnected) {
+            ILog.w(TAG, "⚠️⚠️⚠️ JPush 连接失败 ⚠️⚠️⚠️")
+            ILog.w(TAG, "请检查网络连接和 AppKey 配置")
+        }
+        
+        // 连接状态变化,可以在这里更新 UI 或做其他处理
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleConnectedStatus(isConnected)
+    }
+    
+    /**
+     * 命令结果回调
+     */
+    override fun onCommandResult(context: Context, cmdMessage: CmdMessage) {
+        ILog.d(TAG, "[onCommandResult] cmd=${cmdMessage.cmd}, errorCode=${cmdMessage.errorCode}")
+        
+        // 命令执行结果,用于调试
+    }
+    
+    /**
+     * 标签操作结果回调
+     */
+    override fun onTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "[onTagOperatorResult] sequence=$sequence, tags=${jPushMessage.tags}, errorCode=${jPushMessage.errorCode}")
+        
+        // 使用 TagAliasOperatorHelper 处理
+        TagAliasOperatorHelper.getInstance().onTagOperatorResult(context, jPushMessage)
+        
+        // 调用 PushService 处理标签操作结果
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleTagOperatorResult(jPushMessage)
+        
+        super.onTagOperatorResult(context, jPushMessage)
+    }
+    
+    /**
+     * 检查标签操作结果回调
+     */
+    override fun onCheckTagOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "[onCheckTagOperatorResult] sequence=$sequence, checkTag=${jPushMessage.checkTag}, errorCode=${jPushMessage.errorCode}")
+        
+        // 使用 TagAliasOperatorHelper 处理
+        TagAliasOperatorHelper.getInstance().onCheckTagOperatorResult(context, jPushMessage)
+        
+        // 调用 PushService 处理检查标签结果
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleCheckTagOperatorResult(jPushMessage)
+        
+        super.onCheckTagOperatorResult(context, jPushMessage)
+    }
+    
+    /**
+     * 别名操作结果回调
+     */
+    override fun onAliasOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "[onAliasOperatorResult] sequence=$sequence, alias=${jPushMessage.alias}, errorCode=${jPushMessage.errorCode}")
+        
+        // 使用 TagAliasOperatorHelper 处理
+        TagAliasOperatorHelper.getInstance().onAliasOperatorResult(context, jPushMessage)
+        
+        // 调用 PushService 处理别名操作结果
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleAliasOperatorResult(jPushMessage)
+        
+        super.onAliasOperatorResult(context, jPushMessage)
+    }
+    
+    /**
+     * 手机号操作结果回调
+     */
+    override fun onMobileNumberOperatorResult(context: Context, jPushMessage: JPushMessage) {
+        val sequence = jPushMessage.sequence
+        ILog.d(TAG, "[onMobileNumberOperatorResult] sequence=$sequence, mobileNumber=${jPushMessage.mobileNumber}, errorCode=${jPushMessage.errorCode}")
+        
+        // 使用 TagAliasOperatorHelper 处理
+        TagAliasOperatorHelper.getInstance().onMobileNumberOperatorResult(context, jPushMessage)
+        
+        // 调用 PushService 处理手机号操作结果
+        val pushService = PushServiceFactory.getInstance()
+        (pushService as? PushServiceImpl)?.handleMobileNumberOperatorResult(jPushMessage)
+        
+        super.onMobileNumberOperatorResult(context, jPushMessage)
+    }
+    
+    /**
+     * 通知设置检查回调
+     */
+    override fun onNotificationSettingsCheck(context: Context, isOn: Boolean, source: Int) {
+        ILog.d(TAG, "[onNotificationSettingsCheck] isOn=$isOn, source=$source")
+        
+        // 通知设置状态检查结果
+        // isOn: true 表示通知已开启,false 表示通知已关闭
+        // source: 来源
+        
+        super.onNotificationSettingsCheck(context, isOn, source)
     }
 }
-

+ 14 - 0
capability-push/src/main/java/com/narutohuo/xindazhou/push/service/UserService.java

@@ -0,0 +1,14 @@
+package com.narutohuo.xindazhou.push.service;
+
+import cn.jpush.android.service.JCommonService;
+
+/**
+ * 极光推送自定义 Service(继承自 JCommonService)
+ * 
+ * 参考官方 Demo:必须配置一个继承自 JCommonService 的 Service
+ * JCommonService 类在 JCore SDK 5.2.8 中提供
+ */
+public class UserService extends JCommonService {
+    // 空实现,只需要继承即可
+}
+

+ 42 - 0
capability-push/src/main/java/com/narutohuo/xindazhou/push/startup/PushInitializer.kt

@@ -0,0 +1,42 @@
+package com.narutohuo.xindazhou.push.startup
+
+import android.content.Context
+import androidx.startup.Initializer
+import com.narutohuo.xindazhou.core.log.ILog
+import com.narutohuo.xindazhou.push.factory.PushServiceFactory
+
+/**
+ * 推送服务自动初始化器
+ * 
+ * 使用 Jetpack Startup Library 实现自动初始化
+ * 应用启动时自动执行,无需在 Application 中手动调用
+ */
+class PushInitializer : Initializer<Unit> {
+    
+    companion object {
+        private const val TAG = "PushInitializer"
+    }
+    
+    override fun create(context: Context) {
+        try {
+            ILog.d(TAG, "开始自动初始化推送服务...")
+            
+            // 初始化推送服务
+            PushServiceFactory.init(
+                context = context.applicationContext,
+                messageListener = null,  // 默认无监听器,业务层可以后续设置
+                notificationClickListener = null
+            )
+            
+            ILog.d(TAG, "✅ 推送服务自动初始化完成")
+        } catch (e: Exception) {
+            ILog.e(TAG, "推送服务自动初始化失败", e)
+        }
+    }
+    
+    override fun dependencies(): List<Class<out Initializer<*>>> {
+        // 无依赖
+        return emptyList()
+    }
+}
+

+ 4 - 2
capability-push/src/main/res/values/strings.xml

@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <!-- 极光推送基础配置 -->
+    <!-- 极光推送基础配置(必填) -->
+    <!-- 1. 访问 https://www.jiguang.cn/ 注册账号并创建应用 -->
+    <!-- 2. 在应用详情页获取 AppKey(24位字符串) -->
+    <!-- 3. 将下面的 your_jpush_appkey_here 替换为您的真实 AppKey -->
     <string name="jpush_app_key">5d61ceb7e31a7795753105e4</string>
-    <string name="push_jpush_app_key">5d61ceb7e31a7795753105e4</string>
     <string name="push_jpush_channel">1000</string>
     
     <!-- 厂商通道配置(可选,提升推送到达率) -->

+ 149 - 0
capability-qrcode/使用说明.md

@@ -0,0 +1,149 @@
+# 二维码扫码功能使用说明
+
+## 功能特点
+
+- ✅ 使用华为 Customized View Mode 实现精美的自定义扫码界面
+- ✅ 全屏沉浸式体验
+- ✅ 流畅的扫描线动画
+- ✅ **超简洁的 API,业务层只需一行代码**
+- ✅ **内部自动处理 Activity Result,无需手动处理 onActivityResult**
+- ✅ 单例模式,统一管理
+
+## 快速开始
+
+### 调用方式(与BLE模块完全一致)
+
+```kotlin
+import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
+import com.narutohuo.xindazhou.qrcode.api.QRCodeManager
+
+val qrCodeManager: QRCodeManager = QRCodeManagerFactory.getInstance()
+qrCodeManager.scanQRCode(this) { response ->
+    if (response.success) {
+        // 扫码成功
+        val qrCodeContent = response.data
+    } else {
+        // 扫码失败或取消
+        val error = response.errorMessage
+    }
+}
+```
+
+### 完整示例
+
+```kotlin
+import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
+import com.narutohuo.xindazhou.qrcode.api.QRCodeManager
+import com.narutohuo.xindazhou.qrcode.model.QRCodeResponse
+
+class MainActivity : AppCompatActivity() {
+    
+    private val qrCodeManager: QRCodeManager = QRCodeManagerFactory.getInstance()
+    
+    fun startScan() {
+        qrCodeManager.scanQRCode(this) { response ->
+            handleScanResult(response)
+        }
+    }
+    
+    private fun handleScanResult(response: QRCodeResponse) {
+        if (response.success) {
+            val content = response.data
+            // 处理扫码结果
+            processQRCode(content)
+        } else {
+            // 处理错误
+            showError(response.errorMessage)
+        }
+    }
+    
+    private fun processQRCode(content: String?) {
+        // 业务逻辑处理
+    }
+    
+    private fun showError(message: String?) {
+        // 显示错误
+    }
+}
+```
+
+**注意**:✅ 不需要实现 `onActivityResult`!内部已经自动处理了
+
+## API 说明
+
+### QRCodeManager 接口
+
+```kotlin
+interface QRCodeManager {
+    /**
+     * 扫描二维码
+     * 
+     * @param activity Activity上下文(必须是 FragmentActivity 或 AppCompatActivity)
+     * @param callback 扫描结果回调,返回QRCodeResponse
+     */
+    fun scanQRCode(activity: Activity, callback: (QRCodeResponse) -> Unit)
+}
+```
+
+### QRCodeResponse 响应模型
+
+```kotlin
+data class QRCodeResponse(
+    val success: Boolean,           // 是否成功
+    val data: String? = null,       // 二维码内容
+    val errorCode: Int? = null,     // 错误码
+    val errorMessage: String? = null, // 错误消息
+    val timestamp: Long = System.currentTimeMillis() // 时间戳
+)
+```
+
+## 注意事项
+
+1. **Activity 类型**:必须使用 `AppCompatActivity` 或 `FragmentActivity`(内部使用 Fragment 桥接 ActivityResultLauncher)
+2. **相机权限**:确保在 AndroidManifest.xml 中声明了相机权限,运行时会自动请求
+3. **单例模式**:使用 Factory 获取的实例是单例,多个界面可以共享
+4. **无需处理 onActivityResult**:内部已经自动处理,业务层完全不需要关心
+
+## 界面效果
+
+- 全屏沉浸式扫码界面
+- 居中显示扫码框(280dp × 280dp)
+- 四个角的装饰线条(青色 #00E5FF)
+- 流畅的扫描线动画
+- 半透明遮罩突出扫码区域
+- 顶部返回按钮
+- 底部提示文字
+
+## 依赖说明
+
+- 华为 HMS Scan Kit: `2.12.0.300`
+- AndroidX AppCompat: `1.6.1`
+- AndroidX Fragment: `1.6.2`
+- AndroidX Activity: `1.8.2`
+- 最低 SDK 版本: 26
+
+## 与其他模块对比
+
+### BLE 模块调用方式
+```kotlin
+import com.narutohuo.xindazhou.ble.factory.BLEServiceFactory
+import com.narutohuo.xindazhou.ble.api.BLEService
+
+val bleService: BLEService = BLEServiceFactory.create(context)
+bleService.connect(device) { response ->
+    // 处理结果
+}
+```
+
+### 二维码模块调用方式(完全一致!)
+```kotlin
+import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
+import com.narutohuo.xindazhou.qrcode.api.QRCodeManager
+
+val qrCodeManager: QRCodeManager = QRCodeManagerFactory.getInstance()
+qrCodeManager.scanQRCode(this) { response ->
+    // 处理结果
+}
+```
+
+**与BLE模块完全一致的调用方式!** 🎉

+ 1 - 1
capability-share/build.gradle

@@ -53,7 +53,7 @@ dependencies {
     // Material Design(用于底部弹窗)
     implementation("com.google.android.material:material:1.12.0")
     
-    // 日志通过 base-core 的 ILog 接口统一管理,无需单独依赖 Timber
+    // 日志通过 base-core 的 ILog 接口统一管理
     
     // 友盟分享SDK(按照官方文档配置)
     // 注意:需要在友盟官网注册应用获取 AppKey,并在 AndroidManifest.xml 中配置

+ 23 - 0
capability-share/src/main/java/com/narutohuo/xindazhou/share/factory/ShareServiceFactory.kt

@@ -6,6 +6,7 @@ import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.share.api.ShareService
 import com.narutohuo.xindazhou.share.impl.ShareServiceImpl
 import com.narutohuo.xindazhou.share.model.ShareConfig
+import com.narutohuo.xindazhou.share.model.ShareResponse
 
 /**
  * 分享服务工厂类
@@ -60,6 +61,28 @@ import com.narutohuo.xindazhou.share.model.ShareConfig
  */
 object ShareServiceFactory {
     
+    private var globalCallback: ((ShareResponse) -> Unit)? = null
+    
+    /**
+     * 设置全局分享回调
+     * 
+     * 设置后,所有分享操作如果没有提供单独的回调,会使用全局回调
+     * 单独的回调优先级更高
+     * 
+     * @param callback 全局回调函数
+     */
+    @JvmStatic
+    fun setGlobalCallback(callback: ((ShareResponse) -> Unit)?) {
+        globalCallback = callback
+        ILog.d("ShareServiceFactory", "全局分享回调已${if (callback != null) "设置" else "清除"}")
+    }
+    
+    /**
+     * 获取全局分享回调
+     */
+    @JvmStatic
+    internal fun getGlobalCallback(): ((ShareResponse) -> Unit)? = globalCallback
+    
     /**
      * 初始化分享服务(使用默认配置)
      * 

+ 17 - 0
capability-share/src/main/java/com/narutohuo/xindazhou/share/impl/ShareServiceImpl.kt

@@ -502,6 +502,23 @@ class ShareServiceImpl private constructor() : IProvider, IShareService, ShareSe
     }
     
     /**
+     * 分享(可选回调)
+     * 
+     * 如果不提供回调,会使用全局回调(如果已设置)
+     */
+    @JvmOverloads
+    fun shareWithOptionalCallback(activity: Activity, content: ShareContent, callback: ((ShareResponse) -> Unit)? = null) {
+        val finalCallback = callback ?: com.narutohuo.xindazhou.share.factory.ShareServiceFactory.getGlobalCallback()
+        if (finalCallback != null) {
+            share(activity, content, finalCallback)
+        } else {
+            ILog.w(tag, "分享时未提供回调,且全局回调未设置,分享结果将无法获取")
+            // 仍然执行分享,但结果会被忽略
+            share(activity, content) { /* ignore */ }
+        }
+    }
+    
+    /**
      * 手动分享方法(内部使用,避免递归调用)
      * 
      * 如果指定了平台,直接分享到该平台;否则显示分享弹窗

+ 283 - 0
capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/SocketIOManager.kt

@@ -0,0 +1,283 @@
+package com.narutohuo.xindazhou.socketio
+
+import android.app.Application
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import com.narutohuo.xindazhou.core.log.ILog
+import com.narutohuo.xindazhou.socketio.factory.SocketIORepositoryFactory
+import com.narutohuo.xindazhou.socketio.factory.SocketIOServiceFactory
+import com.narutohuo.xindazhou.socketio.model.SocketIOResponse
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * SocketIO 管理器(高级封装)
+ * 
+ * 提供统一的 Socket.IO 封装,自动处理连接、重连、Token 刷新等
+ * 外部只需要订阅即可,无需关心连接细节
+ * 
+ * 职责:
+ * ✅ 自动处理连接(包括 Token 刷新)
+ * ✅ 自动处理重连
+ * ✅ 提供消息订阅接口(SharedFlow)
+ * ✅ 外部只需要订阅即可
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 AppInitializer 中初始化(可选,会自动连接)
+ * SocketIOManager.initialize(application) { isLoggedIn() } { refreshTokenIfNeeded() }
+ * 
+ * // 在 ViewModel 中订阅消息
+ * viewModelScope.launch {
+ *     SocketIOManager.shared.subscribe("community_message").collect { response ->
+ *         // 处理社区消息
+ *     }
+ * }
+ * ```
+ */
+object SocketIOManager : DefaultLifecycleObserver {
+    
+    private const val TAG = "SocketIOManager"
+    
+    // 单例实例
+    val shared = SocketIOManager
+    
+    private val repository = SocketIORepositoryFactory.getInstance()
+    private val socketService = SocketIOServiceFactory.getInstance()
+    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+    
+    private var application: Application? = null
+    private var isInitialized = false
+    
+    // 登录状态检查回调(由业务层设置)
+    var isLoggedInProvider: (() -> Boolean)? = null
+    
+    // Token 刷新回调(由业务层设置)
+    var refreshTokenProvider: (suspend () -> String?)? = null
+    
+    // 连接状态(使用 replay = 1 确保新订阅者能立即获取最新状态)
+    private val _connectionState = MutableSharedFlow<Boolean>(extraBufferCapacity = 1, replay = 1)
+    val connectionState: SharedFlow<Boolean> = _connectionState.asSharedFlow()
+    
+    // 消息订阅(使用 SharedFlow)
+    private val eventFlows = mutableMapOf<String, MutableSharedFlow<SocketIOResponse>>()
+    
+    init {
+        // 初始化连接状态(同步获取当前状态)
+        _connectionState.tryEmit(isConnected())
+        
+        // 订阅连接状态变化
+        observeConnectionState()
+    }
+    
+    /**
+     * 初始化并自动连接(如果用户已登录)
+     * 
+     * 在 AppInitializer 中调用一次即可
+     * 后续会自动处理连接、重连、Token 刷新等
+     * 
+     * @param application Application 实例(用于生命周期监听)
+     * @param isLoggedInProvider 登录状态检查回调
+     * @param refreshTokenProvider Token 刷新回调
+     */
+    fun initialize(
+        application: Application,
+        isLoggedInProvider: (() -> Boolean)? = null,
+        refreshTokenProvider: (suspend () -> String?)? = null
+    ) {
+        if (isInitialized) {
+            ILog.d(TAG, "已初始化,跳过")
+            return
+        }
+        
+        this.application = application
+        this.isLoggedInProvider = isLoggedInProvider
+        this.refreshTokenProvider = refreshTokenProvider
+        isInitialized = true
+        
+        // 设置回调到底层 Manager
+        if (isLoggedInProvider != null || refreshTokenProvider != null) {
+            com.narutohuo.xindazhou.socketio.manager.SocketIOManager.setCallbacks(
+                isLoggedInProvider = isLoggedInProvider,
+                refreshTokenProvider = refreshTokenProvider
+            )
+        }
+        
+        // 注册 App 生命周期监听
+        try {
+            ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+            ILog.d(TAG, "生命周期监听已注册")
+        } catch (e: Exception) {
+            ILog.e(TAG, "注册生命周期监听失败", e)
+        }
+        
+        // 初始化底层管理器
+        com.narutohuo.xindazhou.socketio.manager.SocketIOManager.init(application)
+        
+        // 如果用户已登录,自动连接
+        appScope.launch {
+            if (isLoggedInProvider?.invoke() == true) {
+                ILog.d(TAG, "用户已登录,自动连接 Socket.IO...")
+                ensureConnected()
+            } else {
+                ILog.d(TAG, "用户未登录,等待登录成功后连接")
+                // 更新连接状态为 false(用户未登录)
+                _connectionState.emit(false)
+            }
+        }
+        
+        ILog.d(TAG, "SocketIOManager 初始化完成(自动处理连接和Token刷新)")
+    }
+    
+    /**
+     * 确保已连接(如果未连接则自动连接)
+     * 
+     * 在用户登录成功后调用,确保 Socket.IO 已连接
+     * 如果已连接,则不做任何操作
+     * 如果未连接,会自动处理连接(包括 Token 刷新)
+     */
+    fun ensureConnected() {
+        appScope.launch {
+            // 检查是否已登录
+            if (isLoggedInProvider?.invoke() != true) {
+                ILog.d(TAG, "用户未登录,无法连接")
+                _connectionState.emit(false)
+                return@launch
+            }
+            
+            // 如果已连接,更新状态并返回
+            if (isConnected()) {
+                ILog.d(TAG, "已连接,状态正常")
+                _connectionState.emit(true)
+                return@launch
+            }
+            
+            // 未连接,尝试重连
+            ILog.d(TAG, "检测到未连接,开始重连...")
+            val result = repository.checkAndReconnect(null, null)
+            if (result) {
+                _connectionState.emit(true)
+            } else {
+                _connectionState.emit(false)
+            }
+        }
+    }
+    
+    /**
+     * 断开连接
+     */
+    fun disconnect() {
+        ILog.d(TAG, "断开 Socket.IO 连接")
+        appScope.launch {
+            repository.disconnect()
+            _connectionState.emit(false)
+        }
+    }
+    
+    /**
+     * 检查连接状态
+     */
+    fun isConnected(): Boolean {
+        return repository.isConnected()
+    }
+    
+    /**
+     * 订阅消息(返回 SharedFlow)
+     * 
+     * 各个业务模块使用此方法订阅需要的消息类型
+     * 
+     * 注意:
+     * - 如果未连接,会自动尝试连接
+     * - 如果用户未登录,会等待登录成功后连接
+     * 
+     * 示例:
+     * ```kotlin
+     * // CommunityViewModel.kt
+     * viewModelScope.launch {
+     *     SocketIOManager.shared.subscribe("community_message").collect { response ->
+     *         // 处理社区消息
+     *     }
+     * }
+     * ```
+     */
+    fun subscribe(eventName: String): SharedFlow<SocketIOResponse> {
+        return eventFlows.getOrPut(eventName) {
+            MutableSharedFlow<SocketIOResponse>(extraBufferCapacity = 1).also { flow ->
+                // 订阅 Socket.IO 事件
+                socketService.on(eventName) { response ->
+                    appScope.launch {
+                        flow.emit(response)
+                    }
+                }
+                
+                // 如果未连接,尝试连接(使用 ensureConnected 统一处理)
+                if (!isConnected()) {
+                    ensureConnected()
+                }
+            }
+        }.asSharedFlow()
+    }
+    
+    /**
+     * 发送消息
+     */
+    fun emit(eventName: String, data: Any) {
+        if (!isConnected()) {
+            ILog.w(TAG, "Socket.IO 未连接,无法发送事件: $eventName,尝试重新连接...")
+            appScope.launch {
+                ensureConnected()
+            }
+            return
+        }
+        
+        socketService.emit(eventName, data)
+    }
+    
+    // MARK: - 生命周期监听
+    
+    /**
+     * App 进入前台时调用
+     * 
+     * 检查 SocketIO 连接状态,如果断开则自动重连
+     * 确保应用回到前台时,Socket.IO 连接状态正常
+     */
+    override fun onStart(owner: LifecycleOwner) {
+        ILog.d(TAG, "App 进入前台,检查 SocketIO 连接状态")
+        
+        // 使用 ensureConnected() 统一处理连接逻辑
+        ensureConnected()
+    }
+    
+    /**
+     * App 进入后台时调用
+     * 
+     * 注意:通常不断开 SocketIO 连接,因为:
+     * 1. SocketIO 连接是轻量级的
+     * 2. 用户可能需要在后台接收消息
+     * 3. 系统会在内存不足时自动清理
+     */
+    override fun onStop(owner: LifecycleOwner) {
+        ILog.d(TAG, "App 进入后台")
+        // 不断开连接,保持连接以便接收消息
+    }
+    
+    // MARK: - Private Methods
+    
+    /**
+     * 观察连接状态变化
+     */
+    private fun observeConnectionState() {
+        appScope.launch {
+            repository.connectionState.collect { isConnected ->
+                _connectionState.emit(isConnected)
+            }
+        }
+    }
+}
+

+ 6 - 0
capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/model/SocketIOEvent.kt

@@ -20,6 +20,8 @@ object SocketIOEvent {
     const val VEHICLE_STATUS = "vehicle_status"
     /** 车辆控制指令 */
     const val VEHICLE_CONTROL = "vehicle_control"
+    /** 车辆控制响应 */
+    const val VEHICLE_CONTROL_RESPONSE = "vehicle_control_response"
     /** 车辆报警 */
     const val VEHICLE_ALARM = "vehicle_alarm"
     /** 车辆位置更新 */
@@ -30,5 +32,9 @@ object SocketIOEvent {
     const val MESSAGE = "message"
     /** 站内信 */
     const val INBOX_MESSAGE = "inbox_message"
+    /** 系统消息 */
+    const val SYSTEM_MESSAGE = "system_message"
+    /** 社区消息 */
+    const val COMMUNITY_MESSAGE = "community_message"
 }
 

+ 165 - 0
capability-socketio/内部处理方案分析.md

@@ -0,0 +1,165 @@
+# SocketIOManager 内部处理方案分析
+
+## 🤔 问题
+
+**当前方案:** AppInitializer 需要分两步调用
+1. `init(application)` - 注册生命周期监听
+2. `setCallbacks(...)` - 设置回调函数
+
+**用户疑问:** 为什么不能由 SocketIOManager 内部统一处理?为什么需要外部分步设置?
+
+---
+
+## 💡 方案对比
+
+### 方案A:当前方案(外部分步设置)
+
+```kotlin
+// AppInitializer.kt
+val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
+initMethod.invoke(null, application)
+
+val setCallbacksMethod = socketIOManagerClass.getMethod("setCallbacks", ...)
+setCallbacksMethod.invoke(null, isLoggedInProvider, refreshTokenProvider)
+```
+
+**问题:**
+- ❌ 外部需要分两步调用
+- ❌ 反射代码复杂(需要两次反射调用)
+- ❌ 如果忘记调用 setCallbacks,功能不完整
+
+---
+
+### 方案B:内部统一处理(推荐)✅
+
+**思路:** SocketIOManager 提供一个统一的初始化方法,内部处理所有逻辑
+
+#### 方案B1:延迟初始化(推荐)
+
+```kotlin
+// SocketIOManager.kt
+fun init(app: Application) {
+    // 只注册生命周期监听
+    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+}
+
+override fun onStart(owner: LifecycleOwner) {
+    // 延迟初始化:在第一次 onStart 时检查回调是否设置
+    if (isLoggedInProvider == null || refreshTokenProvider == null) {
+        ILog.w(TAG, "回调函数未设置,跳过 SocketIO 重连")
+        return
+    }
+    // ... 重连逻辑
+}
+```
+
+**优点:**
+- ✅ 外部只需要调用一次 `init()`
+- ✅ 回调函数可以随时设置(通过反射设置属性)
+- ✅ 如果回调未设置,自动跳过(不会崩溃)
+
+**缺点:**
+- ⚠️ 第一次进入前台时,如果回调未设置,会跳过重连
+- ⚠️ 需要确保回调在第一次 onStart 之前设置
+
+---
+
+#### 方案B2:提供统一的初始化方法(最佳)✅
+
+```kotlin
+// SocketIOManager.kt
+fun init(app: Application) {
+    // 注册生命周期监听
+    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+}
+
+// 提供一个方法,让外部通过反射设置回调
+// 这个方法不需要参数,因为回调已经存储为属性
+fun ensureCallbacksSet() {
+    // 检查回调是否设置,如果没有设置则记录警告
+    if (isLoggedInProvider == null) {
+        ILog.w(TAG, "登录状态检查回调未设置")
+    }
+    if (refreshTokenProvider == null) {
+        ILog.w(TAG, "Token 刷新回调未设置")
+    }
+}
+```
+
+**AppInitializer 中:**
+```kotlin
+// 1. 调用 init
+val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
+initMethod.invoke(null, application)
+
+// 2. 直接设置属性(不需要调用 setCallbacks)
+val isLoggedInProviderField = socketIOManagerClass.getDeclaredField("isLoggedInProvider")
+isLoggedInProviderField.isAccessible = true
+isLoggedInProviderField.set(null, isLoggedInProvider)
+
+val refreshTokenProviderField = socketIOManagerClass.getDeclaredField("refreshTokenProvider")
+refreshTokenProviderField.isAccessible = true
+refreshTokenProviderField.set(null, refreshTokenProvider)
+```
+
+**优点:**
+- ✅ 外部只需要调用一次 `init()`(注册生命周期)
+- ✅ 设置属性比调用方法更简单(不需要处理 suspend 函数类型)
+- ✅ 灵活性高(可以随时修改回调)
+
+**缺点:**
+- ⚠️ 需要访问私有字段(setAccessible)
+- ⚠️ 违反封装原则(直接访问字段)
+
+---
+
+#### 方案B3:完全内部处理(理想但不可行)❌
+
+```kotlin
+// SocketIOManager.kt
+fun init(app: Application) {
+    // 内部尝试获取回调(通过反射或其他方式)
+    // 但问题是:能力层不能依赖业务层,无法直接获取 AuthManager
+}
+```
+
+**问题:**
+- ❌ 能力层不能依赖业务层
+- ❌ 无法直接获取 AuthManager
+- ❌ 违反依赖方向原则
+
+---
+
+## 🎯 推荐方案
+
+### 推荐:**方案B2(直接设置属性)**
+
+**理由:**
+1. ✅ **最简单**:设置属性比调用方法更简单,不需要处理 suspend 函数类型
+2. ✅ **反射代码最少**:只需要一次方法调用(init)+ 两次属性设置
+3. ✅ **灵活性高**:可以随时修改回调函数
+4. ✅ **实际风险低**:在 AppInitializer 中集中处理,风险可控
+
+**实现:**
+- SocketIOManager 的 `init()` 只负责注册生命周期监听
+- 回调函数属性设为 `public` 或通过反射直接设置
+- AppInitializer 分两步:先 init,再设置属性
+
+---
+
+## 📝 最终建议
+
+**当前方案(方案3:setCallbacks)的问题:**
+- 仍然需要处理 `SuspendFunction0` 类型
+- 需要两次反射调用
+
+**改进方案(方案B2:直接设置属性):**
+- 只需要一次方法调用(init)
+- 设置属性不需要处理 suspend 函数类型
+- 代码更简单
+
+**权衡:**
+- 虽然直接访问字段违反了封装原则
+- 但在反射场景下,这是可以接受的权衡
+- 很多框架都采用这种方式(如 Spring、Guice)
+

+ 156 - 0
capability-socketio/回调函数传递方案对比.md

@@ -0,0 +1,156 @@
+# 回调函数传递方案对比
+
+## 📋 当前实现(方案1:反射调用 init 方法)
+
+### 实现方式
+```kotlin
+// AppInitializer.kt
+val initMethod = socketIOManagerClass.getDeclaredMethod(
+    "init",
+    Application::class.java,
+    kotlin.jvm.functions.Function0::class.java,  // isLoggedInProvider
+    kotlin.coroutines.SuspendFunction0::class.java   // refreshTokenProvider
+)
+initMethod.invoke(null, application, isLoggedInProvider, refreshTokenProvider)
+```
+
+### 优点 ✅
+1. **初始化逻辑集中**:init 方法同时完成生命周期注册和回调设置
+2. **原子性操作**:一次调用完成所有初始化,不会出现部分初始化状态
+3. **符合单例模式**:标准的单例初始化模式
+4. **类型安全**:回调函数在编译时创建,类型明确
+
+### 缺点 ❌
+1. **反射调用复杂**:需要处理 `SuspendFunction0` 类型,反射代码复杂
+2. **可读性差**:反射代码不够直观
+3. **错误处理复杂**:反射异常处理需要更多代码
+
+---
+
+## 🔄 方案2:分步设置(先 init,再设置属性)
+
+### 实现方式
+```kotlin
+// SocketIOManager.kt
+fun init(app: Application) {
+    // 只负责注册生命周期监听
+    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+}
+
+// AppInitializer.kt
+// 1. 先调用 init(注册生命周期)
+val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
+initMethod.invoke(null, application)
+
+// 2. 再设置回调属性(直接设置,不需要处理 suspend 函数类型)
+val isLoggedInProviderField = socketIOManagerClass.getDeclaredField("isLoggedInProvider")
+isLoggedInProviderField.isAccessible = true
+isLoggedInProviderField.set(null, isLoggedInProvider)
+
+val refreshTokenProviderField = socketIOManagerClass.getDeclaredField("refreshTokenProvider")
+refreshTokenProviderField.isAccessible = true
+refreshTokenProviderField.set(null, refreshTokenProvider)
+```
+
+### 优点 ✅
+1. **反射代码简单**:设置属性比调用方法更简单
+2. **不需要处理 suspend 函数类型**:直接设置属性,反射不需要知道 suspend 类型
+3. **灵活性高**:可以随时修改回调函数
+4. **可读性好**:分步操作,逻辑清晰
+
+### 缺点 ❌
+1. **初始化逻辑分散**:需要两步操作
+2. **可能出现部分初始化**:如果第二步失败,可能出现只注册了生命周期但没设置回调的情况
+3. **需要访问私有字段**:需要 `setAccessible(true)`,可能违反封装原则
+4. **错误处理分散**:需要分别处理 init 和属性设置的异常
+
+---
+
+## 📊 客观对比
+
+| 维度 | 方案1:反射调用 init | 方案2:分步设置属性 |
+|------|---------------------|-------------------|
+| **代码复杂度** | ⭐⭐⭐ 复杂(需要处理 SuspendFunction0) | ⭐⭐ 简单(直接设置属性) |
+| **可读性** | ⭐⭐ 一般(反射代码不够直观) | ⭐⭐⭐ 好(分步操作清晰) |
+| **类型安全** | ⭐⭐⭐ 好(编译时类型检查) | ⭐⭐ 一般(运行时设置) |
+| **原子性** | ⭐⭐⭐ 好(一次调用完成) | ⭐⭐ 一般(分步操作) |
+| **错误处理** | ⭐⭐ 一般(集中处理) | ⭐⭐ 一般(分散处理) |
+| **灵活性** | ⭐⭐ 一般(需要重新 init) | ⭐⭐⭐ 好(可随时修改) |
+| **封装性** | ⭐⭐⭐ 好(通过方法调用) | ⭐⭐ 一般(直接访问字段) |
+
+---
+
+## 🎯 推荐方案
+
+### 推荐:**方案2(分步设置)**
+
+**理由:**
+1. ✅ **反射代码更简单**:设置属性比调用方法简单,不需要处理复杂的 suspend 函数类型
+2. ✅ **可读性更好**:分步操作逻辑清晰,易于理解和维护
+3. ✅ **实际风险低**:虽然可能出现部分初始化,但在 AppInitializer 中集中处理,风险可控
+4. ✅ **符合常见模式**:很多框架都是先初始化,再配置回调
+
+**改进建议:**
+- 在 SocketIOManager 中添加 `setCallbacks()` 方法,通过方法设置而不是直接访问字段
+- 这样可以保持封装性,同时简化反射代码
+
+---
+
+## 💡 改进方案(推荐)
+
+### 方案3:添加 setCallbacks 方法(最佳)
+
+```kotlin
+// SocketIOManager.kt
+fun init(app: Application) {
+    // 只负责注册生命周期监听
+    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+}
+
+fun setCallbacks(
+    isLoggedInProvider: (() -> Boolean)? = null,
+    refreshTokenProvider: (suspend () -> String?)? = null
+) {
+    this.isLoggedInProvider = isLoggedInProvider
+    this.refreshTokenProvider = refreshTokenProvider
+}
+
+// AppInitializer.kt
+// 1. 先调用 init(注册生命周期)
+val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
+initMethod.invoke(null, application)
+
+// 2. 再调用 setCallbacks(设置回调)
+val setCallbacksMethod = socketIOManagerClass.getMethod(
+    "setCallbacks",
+    kotlin.jvm.functions.Function0::class.java,  // isLoggedInProvider
+    kotlin.coroutines.SuspendFunction0::class.java   // refreshTokenProvider
+)
+setCallbacksMethod.invoke(null, isLoggedInProvider, refreshTokenProvider)
+```
+
+**优势:**
+- ✅ 保持封装性(通过方法而不是字段)
+- ✅ 反射代码相对简单(调用方法比设置字段更标准)
+- ✅ 逻辑清晰(分步操作)
+- ✅ 类型安全(方法签名明确)
+
+**缺点:**
+- ⚠️ 仍然需要处理 `SuspendFunction0` 类型(但比方案1简单,因为方法签名更清晰)
+
+---
+
+## 📝 结论
+
+**当前方案(方案1)的问题:**
+- 反射调用 init 方法时,需要处理复杂的 `SuspendFunction0` 类型
+- 虽然功能正常,但代码复杂度较高
+
+**推荐改进:**
+- 采用**方案3(添加 setCallbacks 方法)**
+- 或者采用**方案2(分步设置属性)**(如果不在意直接访问字段)
+
+**最终建议:**
+- 如果追求代码简洁:使用**方案2(分步设置属性)**
+- 如果追求封装性:使用**方案3(添加 setCallbacks 方法)**
+

+ 113 - 0
capability-socketio/架构调整说明.md

@@ -0,0 +1,113 @@
+# SocketIO 架构调整说明
+
+## 📋 调整内容
+
+### 调整前(方案1:分离)
+
+```
+base-common/socketio(业务层)
+  └─ SocketIOManager(使用反射调用 capability-socketio)
+      ↓ 反射调用
+capability-socketio(能力层)
+  └─ SocketIORepository(具体实现)
+```
+
+**问题:**
+- ❌ 使用反射,性能差,类型不安全
+- ❌ 代码分散在两个模块
+- ❌ 违反依赖方向(业务层通过反射绕过依赖)
+
+---
+
+### 调整后(方案2:全部放在能力层)✅
+
+```
+capability-socketio(能力层)
+  ├─ SocketIORepository(具体实现)
+  └─ SocketIOManager(生命周期管理,直接调用 SocketIORepository)
+      ↓ 回调函数
+base-common(业务层)
+  └─ AppInitializer(通过反射调用 SocketIOManager,传递回调函数)
+```
+
+**优势:**
+- ✅ 代码集中在能力层
+- ✅ 直接依赖,类型安全
+- ✅ 符合依赖方向(能力层自包含)
+- ✅ 无反射开销(能力层内部)
+
+---
+
+## 🔄 依赖关系
+
+### 正确的依赖方向
+
+```
+base-common(业务层)
+    ↓ 反射调用(可选)
+capability-socketio(能力层)
+    ↓ 直接依赖
+base-core(基础设施层)
+```
+
+### capability-socketio 的依赖
+
+```gradle
+dependencies {
+    // ✅ 只依赖 base-core(基础设施层)
+    implementation(project(":base-core"))
+    
+    // ✅ AndroidX Lifecycle(用于 ProcessLifecycleOwner)
+    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
+    implementation("androidx.lifecycle:lifecycle-process:2.7.0")
+    
+    // ✅ SocketIO 客户端库
+    implementation("io.socket:socket.io-client:2.1.2")
+}
+```
+
+**关键点:**
+- ✅ capability-socketio **不依赖** base-common
+- ✅ SocketIOManager 使用 `ILog`(base-core)而不是 `LogHelper`(base-common)
+- ✅ SocketIOManager 通过回调函数接收登录状态和 Token 刷新逻辑
+
+---
+
+## 📝 代码变更
+
+### 1. SocketIOManager(移到 capability-socketio)
+
+**位置:** `capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/manager/SocketIOManager.kt`
+
+**关键变更:**
+- ✅ 使用 `ILog`(base-core)而不是 `LogHelper`(base-common)
+- ✅ 通过回调函数 `isLoggedInProvider` 和 `refreshTokenProvider` 接收业务逻辑
+- ✅ 直接调用 `SocketIORepositoryFactory.getInstance()`(不使用反射)
+
+### 2. AppInitializer(base-common)
+
+**位置:** `base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppInitializer.kt`
+
+**关键变更:**
+- ✅ 使用反射调用 `capability-socketio` 的 `SocketIOManager`
+- ✅ 传递回调函数(登录状态检查、Token 刷新)
+- ✅ 如果 `capability-socketio` 未引入,跳过初始化(不影响应用运行)
+
+### 3. 删除的文件
+
+- ❌ `base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/SocketIOManager.kt`(已删除)
+
+---
+
+## ✅ 验证结果
+
+- ✅ 编译通过
+- ✅ 依赖关系正确(能力层不依赖业务层)
+- ✅ 代码集中在能力层
+- ✅ 类型安全(能力层内部直接调用)
+
+---
+
+**文档版本:** v1.0  
+**创建时间:** 2024-12
+

+ 1 - 0
gradle.properties

@@ -15,6 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 # Android operating system, and which are packaged with your app's APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 android.useAndroidX=true
+android.enableJetifier=true
 # Kotlin code style for this project: "official" or "obsolete":
 kotlin.code.style=official
 # Enables namespacing of each library's R class so that its R class includes only the