内部处理方案分析.md 4.9 KB

SocketIOManager 内部处理方案分析

🤔 问题

当前方案: AppInitializer 需要分两步调用

  1. init(application) - 注册生命周期监听
  2. setCallbacks(...) - 设置回调函数

用户疑问: 为什么不能由 SocketIOManager 内部统一处理?为什么需要外部分步设置?


💡 方案对比

方案A:当前方案(外部分步设置)

// 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:延迟初始化(推荐)

// 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:提供统一的初始化方法(最佳)✅

// 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 中:

// 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:完全内部处理(理想但不可行)❌

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