SocketIOManager.ets 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { Context, UIAbility } from '@kit.AbilityKit';
  2. import { ILog, StorageImpl } from '@xdz/base-core';
  3. import { SocketIORepositoryFactory } from '@xdz/capability-socketio';
  4. import { AuthManager } from '../auth/AuthManager';
  5. const TAG = "SocketIOManager";
  6. /**
  7. * SocketIO 连接管理器(业务封装层)
  8. *
  9. * 与 AuthManager、VersionUpdateManager 类似,提供统一的 Socket.IO 封装
  10. * 自动处理连接、重连、Token 刷新等,外部只需要初始化即可
  11. *
  12. * 负责自动管理 SocketIO 连接的生命周期:
  13. * - App 进入前台时自动重连
  14. * - Token 过期时自动刷新并重连
  15. * - 监听应用生命周期
  16. *
  17. * 使用方式:
  18. * ```typescript
  19. * // 在 EntryAbility.onCreate() 中初始化
  20. * SocketIOManager.init(context, ability)
  21. *
  22. * // 在 EntryAbility.onForeground() 中调用
  23. * await SocketIOManager.onForeground()
  24. *
  25. * // 在 EntryAbility.onBackground() 中调用
  26. * SocketIOManager.onBackground()
  27. * ```
  28. */
  29. export class SocketIOManager {
  30. private static instance: SocketIOManager | null = null;
  31. private context: Context | null = null;
  32. private initialized: boolean = false;
  33. private ability: UIAbility | null = null;
  34. private constructor() {
  35. // 私有构造函数,单例模式
  36. }
  37. /**
  38. * 获取单例实例
  39. */
  40. static getInstance(): SocketIOManager {
  41. if (!SocketIOManager.instance) {
  42. SocketIOManager.instance = new SocketIOManager();
  43. }
  44. return SocketIOManager.instance;
  45. }
  46. /**
  47. * 初始化 SocketIO 管理器
  48. *
  49. * 需要在 EntryAbility.onCreate() 中调用
  50. * 会自动注册生命周期监听
  51. *
  52. * @param context Application Context 实例
  53. * @param ability UIAbility 实例(用于监听生命周期)
  54. */
  55. static init(context: Context, ability?: UIAbility): void {
  56. const instance = SocketIOManager.getInstance();
  57. if (instance.initialized) {
  58. ILog.w(TAG, "SocketIOManager 已初始化,跳过重复初始化");
  59. return;
  60. }
  61. instance.context = context;
  62. instance.ability = ability || null;
  63. instance.initialized = true;
  64. // 如果提供了 ability,注册生命周期监听
  65. if (ability) {
  66. // HarmonyOS 的生命周期监听通过重写 ability 的方法实现
  67. // 这里我们使用一个包装器来监听生命周期
  68. instance.setupLifecycleListener(ability);
  69. }
  70. ILog.d(TAG, "SocketIOManager 初始化完成(生命周期监听已注册)");
  71. }
  72. /**
  73. * 设置生命周期监听器
  74. *
  75. * 注意:HarmonyOS 的生命周期管理需要通过 Ability 的 onForeground/onBackground 方法
  76. * 这里提供一个辅助方法,需要在 EntryAbility 中手动调用
  77. */
  78. private setupLifecycleListener(ability: UIAbility): void {
  79. // HarmonyOS 的生命周期监听需要在 EntryAbility 中手动调用
  80. // 这里只是保存引用,实际的生命周期回调在 EntryAbility 中调用
  81. this.ability = ability;
  82. }
  83. /**
  84. * App 进入前台时调用(需要在 EntryAbility.onForeground() 中手动调用)
  85. *
  86. * 检查 SocketIO 连接状态,如果断开则自动重连
  87. * 如果连接失败(可能是 Token 过期),会自动刷新 Token 后重连
  88. */
  89. static async onForeground(): Promise<void> {
  90. ILog.d(TAG, "App 进入前台,检查 SocketIO 连接状态");
  91. // 检查是否已登录
  92. if (!await AuthManager.isLoggedIn()) {
  93. ILog.d(TAG, "用户未登录,跳过 SocketIO 重连");
  94. return;
  95. }
  96. // 在后台执行重连逻辑(使用 Promise,不依赖 ExecutorManager)
  97. Promise.resolve().then(async () => {
  98. try {
  99. // 直接获取 SocketIORepository(不使用反射)
  100. const socketIORepository = SocketIORepositoryFactory.getInstance();
  101. // 检查是否已连接
  102. if (socketIORepository.isConnected()) {
  103. ILog.d(TAG, "SocketIO 已连接,无需重连");
  104. return;
  105. }
  106. // 检查 token 是否存在
  107. const token = await StorageImpl.getInstance().getString("access_token");
  108. if (!token || token === '') {
  109. ILog.w(TAG, "Token 为空,无法重连 SocketIO");
  110. return;
  111. }
  112. // 先尝试连接(不传参数,让它从存储中获取)
  113. ILog.d(TAG, "SocketIO 未连接,开始重连...");
  114. const reconnected = await socketIORepository.checkAndReconnect(null, null);
  115. if (!reconnected) {
  116. // 等待 2 秒,检查连接是否成功
  117. await new Promise<void>((resolve) => {
  118. setTimeout(() => {
  119. resolve();
  120. }, 2000);
  121. });
  122. // 如果连接仍然失败,可能是 Token 过期,尝试刷新 Token 后重连
  123. if (!socketIORepository.isConnected()) {
  124. ILog.d(TAG, "连接失败,可能是 Token 过期,尝试刷新 Token 后重连");
  125. const refreshedToken = await AuthManager.refreshTokenIfNeeded();
  126. if (refreshedToken && refreshedToken !== token) {
  127. ILog.d(TAG, "Token 已刷新,使用新 Token 重连");
  128. await socketIORepository.checkAndReconnect(null, null);
  129. } else {
  130. ILog.w(TAG, "Token 刷新失败或未刷新,无法重连");
  131. }
  132. } else {
  133. ILog.d(TAG, "SocketIO 重连成功");
  134. }
  135. }
  136. } catch (e) {
  137. ILog.e(TAG, "SocketIO 重连失败", e as Error);
  138. }
  139. });
  140. }
  141. /**
  142. * App 进入后台时调用(需要在 EntryAbility.onBackground() 中手动调用)
  143. *
  144. * 注意:通常不断开 SocketIO 连接,因为:
  145. * 1. SocketIO 连接是轻量级的
  146. * 2. 用户可能需要在后台接收消息
  147. * 3. 系统会在内存不足时自动清理
  148. */
  149. static onBackground(): void {
  150. ILog.d(TAG, "App 进入后台");
  151. // 不断开连接,保持连接以便接收消息
  152. }
  153. }