main.dart 13 KB

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