initialize static method

Future<void> initialize({
  1. required Size designSize,
  2. required List<MyRoute<GetxController>> routes,
  3. String? appName,
  4. List<MyService>? services,
  5. MyTray? tray,
  6. MyFloatPanel? floatPanel,
  7. MySplash? splash,
  8. Transition pageTransitionStyle = Transition.fade,
  9. Duration pageTransitionDuration = const Duration(milliseconds: 300),
  10. Widget appBuilder(
    1. BuildContext,
    2. Widget?
    )?,
  11. Size? minimumSize,
  12. bool centerWindowOnInit = true,
  13. bool showWindowOnInit = true,
  14. bool focusWindowOnInit = true,
  15. bool draggable = true,
  16. bool resizable = true,
  17. bool doubleClickToFullScreen = false,
  18. bool setTitleBarHidden = true,
  19. bool setWindowButtonVisibility = false,
  20. bool setSkipTaskbar = false,
  21. bool setMaximizable = true,
  22. bool setAspectRatio = true,
  23. bool setAspectRatioEnabled = true,
  24. ThemeData? theme,
  25. bool showDebugTag = true,
  26. bool dragToMoveArea = true,
  27. LogicalKeyboardKey? keyToRollBack,
  28. String exitInfoText = '再按一次退出App',
  29. String backInfoText = '再按一次返回上一页',
  30. Duration exitGapTime = const Duration(seconds: 2),
  31. bool useOKToast = true,
  32. bool enableDebugLogging = false,
  33. bool singleInstance = true,
  34. String? singleInstanceKey,
  35. bool singleInstanceActivateOnSecond = true,
  36. bool ensureScreenSize = true,
  37. bool initializeWidgetsBinding = true,
  38. bool initializeWindowManager = true,
  39. bool initializeGetStorage = true,
  40. bool installErrorHandlers = true,
  41. void onError(
    1. Object error,
    2. StackTrace stack
    )?,
  42. bool enableZoneGuard = false,
})

Implementation

static Future<void> initialize({
  // 核必需参数
  required Size designSize,
  required List<MyRoute> routes,
  String? appName,

  // 服务配置
  List<MyService>? services,

  // 托盘配置 - 简化配置方式
  MyTray? tray,

  // 浮动面板 - 类似托盘的全局管理器(参数名不加 my,类型为 MyFloatPanel)
  MyFloatPanel? floatPanel,

  // 路由和页面配置
  MySplash? splash,
  Transition pageTransitionStyle = Transition.fade,
  Duration pageTransitionDuration = const Duration(milliseconds: 300),
  // UI自定义配置
  Widget Function(BuildContext, Widget?)?
      appBuilder, // 应用构建器,用于自定义UI层级(如全局遮罩等)

  // 窗口基础配置
  Size? minimumSize,
  bool centerWindowOnInit = true,
  bool showWindowOnInit = true,
  bool focusWindowOnInit = true,

  // 窗口交互配置
  bool draggable = true,
  bool resizable = true,
  bool doubleClickToFullScreen = false,

  // 窗口行为配置
  bool setTitleBarHidden = true,
  bool setWindowButtonVisibility = false,
  bool setSkipTaskbar = false,
  bool setMaximizable = true,
  bool setAspectRatio = true,
  bool setAspectRatioEnabled = true,

  // UI和主题配置
  ThemeData? theme,
  bool showDebugTag = true,
  bool dragToMoveArea = true,

  // 其他功能配置
  LogicalKeyboardKey? keyToRollBack,
  String exitInfoText = '再按一次退出App',
  String backInfoText = '再按一次返回上一页',
  Duration exitGapTime = const Duration(seconds: 2),
  bool useOKToast = true,
  bool enableDebugLogging = false, // 包调试日志开关(默认关闭,避免污染用户日志)

  // 单实例配置
  bool singleInstance = true,
  String? singleInstanceKey,
  bool singleInstanceActivateOnSecond = true,

  // 初始化配置
  bool ensureScreenSize = true,
  bool initializeWidgetsBinding = true,
  bool initializeWindowManager = true,
  bool initializeGetStorage = true,

  // ── 异常处理(开箱即用) ──────────────────────────
  // 默认装好 FlutterError.onError + PlatformDispatcher.instance.onError 两个 root-level
  // 异常 hook,无需用户自己包 runZonedGuarded 即可覆盖 widget 树异常和未捕获异步异常。
  // 检测到外部已设置过这两个 hook 时会跳过自动安装并打 warning,不抢用户既有逻辑。
  bool installErrorHandlers = true,
  // 自定义异常 sink;不传则统一走 XlyLogger.error 打日志。
  // 接 Sentry / Crashlytics 时把上报回调传进来即可,例如:
  //   onError: (e, st) => Sentry.captureException(e, stackTrace: st)
  void Function(Object error, StackTrace stack)? onError,

  // ── Zone 守护(默认关闭,向后兼容保留参数) ─────────
  // ⚠️ 0.38.2 起默认值由 true 改为 false。
  // 默认关的原因:Zone Guard 是"应用边界"决策,不是库的责任。开了之后,xly 的内部 Zone
  // 会跟用户在 main 里自己写的 runZonedGuarded / WidgetsFlutterBinding.ensureInitialized()
  // 冲突,启动时抛 `Zone mismatch`(典型踩坑:Sentry / Crashlytics 标准接入流程)。
  // 默认走 `installErrorHandlers` 路线已经能覆盖 99% 的应用异常需求,无需 Zone。
  // 仍想用 Zone Guard(拦截自定义 print/Timer/Microtask 等)时显式传 true 即可,
  // binding 已经被前置到 Zone 之外初始化,不会再有 mismatch。
  bool enableZoneGuard = false,
}) async {
  // 首先初始化日志系统,以便后续初始化步骤可以使用日志
  XlyLogger.init(enabled: enableDebugLogging);
  XlyLogger.info('MyApp 初始化开始');

  // ⚠️ binding 必须在调用方当前 Zone 中初始化,并且必须先于任何可能新建 Zone 的逻辑
  // (包括下面的 enableZoneGuard 分支)。Flutter 铁律:binding 与 runApp 必须同 Zone,
  // 否则 framework 启动时直接抛 `Zone mismatch`。`ensureInitialized()` 本身幂等,
  // 用户在 main 里已经手动调过也不会冲突。
  if (initializeWidgetsBinding) {
    WidgetsFlutterBinding.ensureInitialized();
  }

  // 安装全局异常 hook(FlutterError.onError + PlatformDispatcher.onError)
  // 见 _installErrorHandlers 注释:检测到外部已设置就跳过,不抢用户逻辑。
  if (installErrorHandlers) {
    _installErrorHandlers(onError);
  }

  Future<void> initializeInCurrentZone() async {
    if (ensureScreenSize) {
      await ScreenUtil.ensureScreenSize();
    }
    if (initializeGetStorage) {
      await GetStorage.init();
    }

    // 单实例检查 - 在其他初始化之前进行,以免创建多余的窗口
    if (singleInstance) {
      final instanceKey = singleInstanceKey ?? appName ?? 'XlyFlutterApp';
      final isFirstInstance = await SingleInstanceManager.instance.initialize(
        instanceKey: instanceKey,
        activateExisting: singleInstanceActivateOnSecond,
        onActivate: MyPlatform.isDesktop
            ? () async {
                // 当收到激活请求时,显示并聚焦窗口
                try {
                  await windowManager.show();
                  await windowManager.focus();
                  await windowManager.setAlwaysOnTop(true);
                  // 短暂置顶后取消,避免影响用户体验
                  Future.delayed(const Duration(milliseconds: 100), () async {
                    try {
                      await windowManager.setAlwaysOnTop(false);
                    } catch (e) {
                      XlyLogger.error('取消窗口置顶失败', e);
                    }
                  });
                } catch (e) {
                  XlyLogger.error('激活窗口失败', e);
                }
              }
            : null,
      );

      if (!isFirstInstance) {
        // 不是首个实例,退出当前实例
        XlyLogger.info('检测到应用已在运行,当前实例即将退出');
        await exitApp();
        return;
      }
    }

    if (MyPlatform.isDesktop) {
      if (initializeWindowManager) {
        await _initializeWindowManager(
          designSize,
          appName,
          setTitleBarHidden: setTitleBarHidden,
          setWindowButtonVisibility: setWindowButtonVisibility,
          setSkipTaskbar: setSkipTaskbar,
          setResizable: resizable,
          setMaximizable: setMaximizable,
          centerWindow: centerWindowOnInit,
          focusWindow: focusWindowOnInit,
          showWindow: showWindowOnInit,
          setAspectRatio: setAspectRatio && setAspectRatioEnabled,
          minimumSize: minimumSize,
        );
      }
    }

    // 1. 应用直接参数作为基础配置
    _globalEnableResizable.value = resizable;
    _globalEnableDoubleClickMaximize.value = doubleClickToFullScreen;
    _globalEnableDraggable.value = draggable;
    _globalEnableAspectRatio.value = setAspectRatioEnabled;
    _globalEnableAspectRatio.value = setAspectRatioEnabled;

    // 2. 处理tray参数,自动转换为MyService
    List<MyService> finalServices =
        services != null ? List.from(services) : [];

    if (tray != null) {
      // 检查services中是否已有MyTray服务
      bool hasTrayService = finalServices.any((service) {
        try {
          // 检查同步服务
          if (service.service != null) {
            return service.service!() is MyTray;
          }
          // 检查异步服务类型
          if (service.asyncService != null) {
            return service.asyncService.runtimeType
                .toString()
                .contains('MyTray');
          }
          return false;
        } catch (e) {
          return false;
        }
      });

      if (hasTrayService) {
        // 如果services中已有MyTray,移除它并使用tray参数提供的配置
        finalServices.removeWhere((service) {
          try {
            if (service.service != null) {
              return service.service!() is MyTray;
            }
            if (service.asyncService != null) {
              return service.asyncService.runtimeType
                  .toString()
                  .contains('MyTray');
            }
            return false;
          } catch (e) {
            return false;
          }
        });

        XlyLogger.warning('MyApp: 检测到services中已有MyTray配置,将使用tray参数提供的配置覆盖');
      }

      // 添加tray参数提供的MyTray服务
      finalServices.add(
        MyService<MyTray>(
          service: () => tray,
          permanent: true,
        ),
      );
    }

    // 3. 若提供 floatPanel,则作为全局服务注册(类似 tray)
    if (floatPanel != null) {
      finalServices.add(MyService<MyFloatPanel>(
        service: () => floatPanel,
        permanent: true,
      ));
    }

    // 4. 在runApp之前注册所有服务(支持异步服务)
    // 注意:此时ScreenUtil已通过ensureScreenSize()初始化,服务可以安全使用
    // 按照用户输入的顺序依次注册,保证服务依赖关系
    // 如果服务注册失败,异常会自然向上传播,终止应用初始化
    for (final service in finalServices) {
      await service.registerService();
    }

    // 5. 在所有配置应用完毕后,设置路由并运行应用
    if (appName != null) {
      _globalWindowTitle.value = appName;
    }
    runApp(MyApp._(
      designSize: designSize,
      theme: theme,
      routes: routes,
      services: null, // 服务已注册,不再需要传递
      appBuilder: appBuilder,
      appName: appName,
      useToast: useOKToast,
      dragToMoveArea: dragToMoveArea,
      showDebugTag: showDebugTag,
      splash: splash,
      keyToRollBack: keyToRollBack,
      exitGapTime: exitGapTime,
      exitInfoText: exitInfoText,
      backInfoText: backInfoText,
      pageTransitionStyle: pageTransitionStyle,
      pageTransitionDuration: pageTransitionDuration,
      enableDoubleClickFullScreen: doubleClickToFullScreen,
      draggable: draggable,
    ));
  }

  if (enableZoneGuard) {
    // 老用户显式开 Zone Guard 时仍然支持。
    // binding 已在 Zone 外初始化(见函数顶部),不会触发 Zone mismatch。
    await runZonedGuarded(
      () async {
        await initializeInCurrentZone();
      },
      (error, stackTrace) {
        XlyLogger.error('MyApp 初始化阶段未捕获异常 (Zone Guard)', error, stackTrace);
        onError?.call(error, stackTrace);
        Error.throwWithStackTrace(error, stackTrace);
      },
    );
    return;
  }

  // 默认路径:try/catch 兜底初始化阶段异常。
  // 运行时异步异常由 _installErrorHandlers 装的 root-level hook 兜底,无需 Zone。
  try {
    await initializeInCurrentZone();
  } catch (error, stackTrace) {
    XlyLogger.error('MyApp 初始化失败', error, stackTrace);
    // 初始化失败是致命的:直接 rethrow 让 main 看到,避免被 onError 沉默吞掉
    rethrow;
  }
}