main.dart 14 KB

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