# 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)