main.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. // main.dart - 优化版本
  2. // 保留设备信息加载,但优化初始化流程
  3. import 'dart:async';
  4. import 'dart:developer' as dev;
  5. import 'dart:io';
  6. import 'package:device_info_plus/device_info_plus.dart';
  7. import 'package:firebase_core/firebase_core.dart';
  8. import 'package:firebase_crashlytics/firebase_crashlytics.dart';
  9. import 'package:flutter/foundation.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:flutter/services.dart';
  12. import 'package:logging/logging.dart';
  13. import 'package:path_provider/path_provider.dart';
  14. import 'package:provider/provider.dart';
  15. import 'package:puzzleweave/ads/applovin_ads_controller.dart';
  16. import 'package:puzzleweave/app_lifecycle/app_lifecycle.dart';
  17. import 'package:puzzleweave/audio/jc_audio_controller.dart';
  18. import 'package:puzzleweave/firebase/firebase_options.dart';
  19. import 'package:puzzleweave/homepage/home_screen.dart';
  20. import 'package:puzzleweave/l10n/app_localizations.dart';
  21. import 'package:puzzleweave/models/data.dart';
  22. import 'package:puzzleweave/models/items.dart';
  23. import 'package:puzzleweave/persistence/persistence.dart';
  24. import 'package:puzzleweave/play/board_play.dart';
  25. import 'package:puzzleweave/remote_config/remote_config.dart';
  26. import 'package:puzzleweave/settings/settings_controller.dart';
  27. import 'package:puzzleweave/utils/utils.dart';
  28. import 'package:puzzleweave/utils/memory_monitor.dart';
  29. import 'config/config.dart' as cfg;
  30. import 'config/config.dart';
  31. import 'config/device.dart';
  32. Logger _log = Logger('main.dart');
  33. final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
  34. // ✅ 优化点1: 移除所有阻塞性 await
  35. void main() {
  36. // Subscribe to log messages.
  37. Logger.root.onRecord.listen((record) {
  38. dev.log(
  39. record.message,
  40. time: record.time,
  41. level: record.level.value,
  42. name: record.loggerName,
  43. zone: record.zone,
  44. error: record.error,
  45. stackTrace: record.stackTrace,
  46. );
  47. });
  48. WidgetsFlutterBinding.ensureInitialized();
  49. // 强制竖屏
  50. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  51. // 进入全屏沉浸式
  52. if (Platform.isAndroid) {
  53. SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  54. }
  55. SystemChrome.setSystemUIOverlayStyle(
  56. const SystemUiOverlayStyle(statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light),
  57. );
  58. // ✅ 立即启动 UI,不等待任何异步初始化
  59. runApp(const MyApp());
  60. }
  61. class MyApp extends StatefulWidget {
  62. const MyApp({super.key});
  63. @override
  64. State<MyApp> createState() => _MyAppState();
  65. }
  66. class _MyAppState extends State<MyApp> {
  67. bool _isInitialized = false;
  68. String? _initError;
  69. late Directory _baseDir;
  70. @override
  71. void initState() {
  72. super.initState();
  73. _initializeAsync();
  74. }
  75. // ✅ 优化点2: 并行初始化,减少总时间
  76. Future<void> _initializeAsync() async {
  77. try {
  78. // 并行执行所有初始化任务
  79. final results = await Future.wait([_initFirebase(), _initPersistence(), _initBaseDirectory()], eagerError: false);
  80. _baseDir = results[2] as Directory;
  81. // 非阻塞性初始化(不等待完成)
  82. _initRemoteConfig();
  83. _prepareFirstRunData();
  84. setState(() {
  85. _isInitialized = true;
  86. });
  87. // 启动内存监控
  88. if (Config.isDebug) {
  89. MemoryMonitor().startMonitoring(
  90. interval: const Duration(seconds: 3),
  91. onHighMemory: () => _log.warning('High memory detected'),
  92. onCriticalMemory: () => _log.severe('Critical memory - emergency cleanup triggered'),
  93. );
  94. }
  95. _log.info('App initialization completed successfully');
  96. } catch (e, stack) {
  97. _log.severe('App initialization failed', e, stack);
  98. setState(() {
  99. _initError = e.toString();
  100. _isInitialized = true; // 即使失败也显示 UI
  101. });
  102. }
  103. }
  104. // ✅ 优化点3: Firebase 初始化独立,带超时保护
  105. Future<void> _initFirebase() async {
  106. if (kIsWeb || !Platform.isAndroid) return;
  107. try {
  108. await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).timeout(Duration(seconds: 10));
  109. FlutterError.onError = (errorDetails) {
  110. FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
  111. };
  112. PlatformDispatcher.instance.onError = (error, stack) {
  113. final errorStr = error.toString();
  114. final bool isEnvironmentIssue =
  115. error is SocketException ||
  116. error is HttpException ||
  117. error is HandshakeException ||
  118. errorStr.contains('ClientException') ||
  119. errorStr.contains('SocketException') ||
  120. errorStr.contains('HandshakeException') ||
  121. errorStr.contains('Failed host lookup') ||
  122. errorStr.contains('Network is unreachable') ||
  123. errorStr.contains('Connection timed out') ||
  124. errorStr.contains('Connection closed') ||
  125. errorStr.contains('Connection reset') ||
  126. errorStr.contains('Unable to connect');
  127. if (isEnvironmentIssue) {
  128. _log.warning('已拦截环境异常: $errorStr');
  129. FirebaseCrashlytics.instance.log('Env Error (Ignored): $errorStr');
  130. return true;
  131. }
  132. if (error is MissingPluginException) {
  133. _log.warning('插件未找到: $errorStr');
  134. return true;
  135. }
  136. _log.severe('未处理的逻辑错误', error, stack);
  137. FirebaseCrashlytics.instance.recordError(error, stack, fatal: false);
  138. return true;
  139. };
  140. _log.info('Firebase initialized successfully');
  141. } catch (e) {
  142. _log.warning("Firebase initialization failed: $e");
  143. }
  144. }
  145. Future<void> _initPersistence() async {
  146. try {
  147. await Persistence().initialize().timeout(Duration(seconds: 5));
  148. Persistence().lastRunTime = DateTime.now();
  149. _log.info('Persistence initialized successfully');
  150. } catch (e) {
  151. _log.severe('Persistence initialization failed: $e');
  152. rethrow;
  153. }
  154. }
  155. Future<Directory> _initBaseDirectory() async {
  156. try {
  157. return await getApplicationDocumentsDirectory();
  158. } catch (e) {
  159. _log.severe('Failed to get base directory: $e');
  160. rethrow;
  161. }
  162. }
  163. void _initRemoteConfig() {
  164. try {
  165. RemoteConfig().initialize();
  166. _log.info('RemoteConfig initialized');
  167. } catch (e) {
  168. _log.warning('RemoteConfig initialization failed: $e');
  169. }
  170. }
  171. Future<void> _prepareFirstRunData() async {
  172. if (!Persistence().firstRun) return;
  173. try {
  174. final json = await loadJSONFromAsset('assets/builtin/${cfg.Config.firstId}.json');
  175. await saveJson('work/${cfg.Config.firstId}.json', json);
  176. _log.info('First run data prepared');
  177. } catch (e) {
  178. _log.warning('Failed to prepare first run data: $e');
  179. }
  180. }
  181. @override
  182. Widget build(BuildContext context) {
  183. // 显示加载界面
  184. if (!_isInitialized) {
  185. return MaterialApp(
  186. home: Scaffold(
  187. backgroundColor: Color(0xfff4f2e9),
  188. body: Center(
  189. child: Column(
  190. mainAxisAlignment: MainAxisAlignment.center,
  191. children: [
  192. // 使用原生最轻量的进度指示器
  193. SizedBox(width: 40, height: 40, child: CircularProgressIndicator(strokeWidth: 3, valueColor: AlwaysStoppedAnimation<Color>(Colors.green))),
  194. const SizedBox(height: 20),
  195. // 可选:添加一个简单的文字,让用户知道在加载
  196. Text(
  197. "Loading...",
  198. style: TextStyle(color: Color.fromARGB(255, 38, 96, 12), fontSize: 14, fontWeight: FontWeight.w500),
  199. ),
  200. ],
  201. ),
  202. ),
  203. ),
  204. );
  205. }
  206. // 初始化失败时显示错误界面
  207. if (_initError != null) {
  208. return MaterialApp(
  209. home: Scaffold(
  210. backgroundColor: Color(0xfff4f2e9),
  211. body: Center(
  212. child: Padding(
  213. padding: EdgeInsets.all(24),
  214. child: Column(
  215. mainAxisAlignment: MainAxisAlignment.center,
  216. children: [
  217. Icon(Icons.error_outline, size: 64, color: Colors.red),
  218. SizedBox(height: 16),
  219. Text('Initialization Failed', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
  220. SizedBox(height: 8),
  221. Text(
  222. _initError!,
  223. textAlign: TextAlign.center,
  224. style: TextStyle(color: Colors.grey),
  225. ),
  226. SizedBox(height: 24),
  227. ElevatedButton(
  228. onPressed: () {
  229. setState(() {
  230. _isInitialized = false;
  231. _initError = null;
  232. });
  233. _initializeAsync();
  234. },
  235. child: Text('Retry'),
  236. ),
  237. ],
  238. ),
  239. ),
  240. ),
  241. ),
  242. );
  243. }
  244. // 正常启动应用
  245. cfg.Config config = cfg.Config(context, _baseDir);
  246. return AppLifecycleObserver(
  247. child: MultiProvider(
  248. providers: [
  249. Provider<Data>(lazy: false, create: (context) => Data(persistence: Persistence())..loadDataFromPersistence()),
  250. Provider<SettingsController>(lazy: false, create: (context) => SettingsController(persistence: Persistence())..loadStateFromPersistence()),
  251. ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, JcAudioController>(
  252. lazy: false,
  253. create: (context) => JcAudioController()..initialize(),
  254. update: (context, settings, lifecycleNotifier, audio) {
  255. if (audio == null) throw ArgumentError.notNull();
  256. audio.attachSettings(settings);
  257. audio.attachLifecycleNotifier(lifecycleNotifier);
  258. return audio;
  259. },
  260. dispose: (context, audio) => audio.dispose(),
  261. ),
  262. Provider<ApplovinAdsController>(create: (context) => ApplovinAdsController(context)),
  263. Provider<cfg.Config>(lazy: false, create: (context) => config),
  264. Provider<Device>(lazy: false, create: (context) => config.device),
  265. ],
  266. child: Prepare(
  267. child: MaterialApp(
  268. title: 'Jigsort Solitaire',
  269. initialRoute: '/',
  270. navigatorObservers: [routeObserver],
  271. routes: {
  272. '/': (context) => const HomeScreen(),
  273. '/play': (context) => BoardPlay(
  274. item: AssetItem(
  275. cfg.Config.firstId,
  276. '',
  277. 2000,
  278. 3000,
  279. 3,
  280. false,
  281. 'assets/builtin/${cfg.Config.firstId}.jpeg',
  282. 'assets/builtin/${cfg.Config.firstId}.jpeg',
  283. ),
  284. firstRun: true,
  285. ),
  286. },
  287. theme: ThemeData(brightness: Brightness.light, primaryColor: Colors.green, primarySwatch: Colors.blue),
  288. localizationsDelegates: AppLocalizations.localizationsDelegates,
  289. supportedLocales: AppLocalizations.supportedLocales,
  290. localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) {
  291. var result = supportedLocales.where((element) => element.languageCode == locale?.languageCode);
  292. if (result.isNotEmpty) {
  293. Device.locale = locale!;
  294. _log.info('当前语言(支持):${Device.locale!.countryCode}-${Device.locale!.languageCode}');
  295. return locale;
  296. }
  297. Device.locale = const Locale('en');
  298. _log.info('当前语言(默认):${Device.locale!.countryCode}-${Device.locale!.languageCode}');
  299. return Device.locale;
  300. },
  301. ),
  302. ),
  303. ),
  304. );
  305. }
  306. }
  307. class Prepare extends StatefulWidget {
  308. final Widget child;
  309. const Prepare({super.key, required this.child});
  310. @override
  311. State<Prepare> createState() => _PrepareState();
  312. }
  313. class _PrepareState extends State<Prepare> {
  314. @override
  315. void initState() {
  316. super.initState();
  317. // ✅ 保留:异步加载设备信息(不阻塞 UI)
  318. _loadDeviceInfo();
  319. }
  320. /// ✅ 优化:异步加载设备信息,不阻塞 UI
  321. Future<void> _loadDeviceInfo() async {
  322. if (!Platform.isAndroid) return;
  323. try {
  324. final deviceInfoPlugin = DeviceInfoPlugin();
  325. final androidInfo = await deviceInfoPlugin.androidInfo;
  326. if (mounted) {
  327. context.read<Device>().androidDeviceInfo = androidInfo;
  328. _log.info(
  329. 'Device info loaded: SDK ${androidInfo.version.sdkInt}, '
  330. 'LowRAM: ${androidInfo.isLowRamDevice}, '
  331. 'CPUs: ${Platform.numberOfProcessors}',
  332. );
  333. }
  334. } catch (e) {
  335. _log.warning('Failed to load device info: $e');
  336. // 失败不影响应用运行
  337. }
  338. }
  339. @override
  340. Widget build(BuildContext context) {
  341. return widget.child;
  342. }
  343. }