From c272c662bd42c755c8ebf73b721a3a64bac78bed Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Mon, 29 Sep 2025 00:11:22 +0800 Subject: [PATCH] fix: 崩溃修复 --- lib/app/splash_page.dart | 17 ++++++++++------- lib/common/permission/permissionRequestPage.dart | 74 +++++++++++++++++++++++++++++++++++++++++++++++--------------------------- lib/common/permission/permissionRequester.dart | 3 ++- lib/common/request/api_response/api_response_entity.dart | 4 +++- lib/pages/home/widgets/ShakeImage.dart | 58 +++++++++++++++++++++++++++++++++++++++++++++++----------- lib/pages/reading/bloc/reading_bloc.dart | 14 +++++++++----- lib/pages/section/section_page.dart | 50 ++++++++++++++++++++++++++++++++------------------ lib/pages/unit/view.dart | 2 +- lib/pages/video/lookvideo/widgets/video_widget.dart | 4 +++- lib/utils/audio_player_util.dart | 63 +++++++++++++++++++++++++++++++++++++++++++++------------------ 10 files changed, 199 insertions(+), 90 deletions(-) diff --git a/lib/app/splash_page.dart b/lib/app/splash_page.dart index 52f880d..c92662a 100644 --- a/lib/app/splash_page.dart +++ b/lib/app/splash_page.dart @@ -140,19 +140,22 @@ class _TransitionViewState extends State { actions: [ TextButton( child: const Text('退出'), - onPressed: () => { - // 关闭对话框并重试 - Navigator.of(context).pop(), - AppConfigHelper.exitApp(), - completer?.complete(), - }), + onPressed: () { + // 关闭对话框并退出应用 + Navigator.of(context).pop(); + AppConfigHelper.exitApp(); + // 只有在 completer 还没有完成时才完成它 + if (completer != null && !completer.isCompleted) { + completer.complete(); + } + }), TextButton( child: const Text('重试'), onPressed: () async { // 关闭对话框并重试 Navigator.of(context).pop(); await fetchNecessaryData(userToken, completer: completer); - completer?.complete(); + // 不需要再次调用 completer.complete(),因为 fetchNecessaryData 内部已经处理了 }, ), ], diff --git a/lib/common/permission/permissionRequestPage.dart b/lib/common/permission/permissionRequestPage.dart index 3e53263..32b74f1 100644 --- a/lib/common/permission/permissionRequestPage.dart +++ b/lib/common/permission/permissionRequestPage.dart @@ -30,6 +30,7 @@ class _PermissionRequestPageState extends State bool _isGoSetting = false; late final List msgList; bool _isDialogShowing = false; + bool _isRequestingPermission = false; @override void initState() { @@ -107,6 +108,11 @@ class _PermissionRequestPageState extends State /// 校验权限 void _handlePermission(List permissions) async { + if (_isRequestingPermission) { + Log.d('_handlePermission already in progress, skipping'); + return; + } + ///一个新待申请权限列表 List intentPermissionList = []; @@ -129,37 +135,51 @@ class _PermissionRequestPageState extends State ///实际触发请求权限 Future _requestPermission(List permissions) async { - Log.d('_requestPermission permissions=$permissions'); - MapEntry? statusEntry = - await requestPermissionsInner(permissions); - if (statusEntry == null) { - ///都手动同意授予了 - _popPage(true); + if (_isRequestingPermission) { + Log.d('_requestPermission already in progress, skipping'); return; } + + _isRequestingPermission = true; + Log.d('_requestPermission permissions=$permissions'); + + try { + MapEntry? statusEntry = + await requestPermissionsInner(permissions); + if (statusEntry == null) { + ///都手动同意授予了 + _popPage(true); + return; + } - Permission permission = statusEntry.key; - PermissionStatus status = statusEntry.value; + Permission permission = statusEntry.key; + PermissionStatus status = statusEntry.value; - // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) - if (status.isDenied) { - showAlert( - permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); - } - // 权限已被永久拒绝 - /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 - /// 在 iOS 上:如果用户拒绝访问所请求的功能。 - if (status.isPermanentlyDenied) { - _isGoSetting = true; - showAlert( - permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); - } - // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) - // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) - if (status.isLimited || status.isRestricted) { - if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; - showAlert( - permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); + // 还未申请权限或之前拒绝了权限(在 iOS 上为首次申请权限,拒绝后将变为 `永久拒绝权限`) + if (status.isDenied) { + showAlert( + permission, msgList[0], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); + } + // 权限已被永久拒绝 + /// 在 Android 上:Android 11+ (API 30+):用户是否第二次拒绝权限。低于 Android 11 (API 30):用户是否拒绝访问请求的功能,并选择不再显示请求。 + /// 在 iOS 上:如果用户拒绝访问所请求的功能。 + if (status.isPermanentlyDenied) { + _isGoSetting = true; + showAlert( + permission, msgList[2], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); + } + // isLimited:拥有部分权限(受限,仅在 iOS (iOS14+) 上受支持) + // isRestricted:拥有部分权限,活动限制(例如,设置了家长///控件,仅在iOS以上受支持。(仅限 iOS) + if (status.isLimited || status.isRestricted) { + if (Platform.isIOS || Platform.isMacOS) _isGoSetting = true; + showAlert( + permission, msgList[1], msgList[3], _isGoSetting ? "前往系统设置" : "继续"); + } + } catch (e) { + Log.d('_requestPermission error: $e'); + _popPage(false); + } finally { + _isRequestingPermission = false; } } diff --git a/lib/common/permission/permissionRequester.dart b/lib/common/permission/permissionRequester.dart index e65c7f1..08ede98 100644 --- a/lib/common/permission/permissionRequester.dart +++ b/lib/common/permission/permissionRequester.dart @@ -35,12 +35,13 @@ Future requestPermissions( if (allGranted) { return true; } else { - return await Navigator.of(context).push(PageRouteBuilder( + final result = await Navigator.of(context).push(PageRouteBuilder( opaque: false, pageBuilder: ((context, animation, secondaryAnimation) { return PermissionRequestPage(permissions, permissionNames, permissionDesc, isRequiredPermission: isRequiredPermission); }))); + return result ?? false; } } diff --git a/lib/common/request/api_response/api_response_entity.dart b/lib/common/request/api_response/api_response_entity.dart index f427b96..b4d96b1 100644 --- a/lib/common/request/api_response/api_response_entity.dart +++ b/lib/common/request/api_response/api_response_entity.dart @@ -1,8 +1,10 @@ import 'dart:convert'; -import 'api_response_entity.g.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'api_response_entity.g.dart'; +@JsonSerializable(genericArgumentFactories: true) class ApiResponse { int? code; String? msg; diff --git a/lib/pages/home/widgets/ShakeImage.dart b/lib/pages/home/widgets/ShakeImage.dart index cd1fa13..19dd97b 100644 --- a/lib/pages/home/widgets/ShakeImage.dart +++ b/lib/pages/home/widgets/ShakeImage.dart @@ -20,7 +20,7 @@ class _ShakeImageState extends State with SingleTickerProviderStateMixin, WidgetsBindingObserver, RouteAware { late AnimationController _controller; late Animation _animation; - late Timer _timer; + Timer? _timer; late final AppLifecycleListener appLifecycleListener; static const TAG = 'ShakeImage'; @@ -43,7 +43,11 @@ class _ShakeImageState extends State ); super.initState(); - WidgetsBinding.instance.addObserver(this); + try { + WidgetsBinding.instance.addObserver(this); + } catch (e) { + printLog('Add observer error: $e'); + } WidgetsBinding.instance.addPostFrameCallback((_) { final route = ModalRoute.of(context); if (route is PageRoute) { @@ -100,16 +104,32 @@ class _ShakeImageState extends State void _startAnimation() { Timer(const Duration(seconds: 1), () { - _controller.forward(from: 0.0); - _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) { - _controller.forward(from: 0.0); - }); + // 检查widget是否还存在且controller是否有效 + if (mounted) { + try { + _controller.forward(from: 0.0); + _timer = Timer.periodic(const Duration(seconds: 4), (Timer timer) { + // 每次执行前都检查widget状态和controller状态 + if (mounted) { + try { + _controller.forward(from: 0.0); + } catch (e) { + printLog('Timer callback error: $e'); + timer.cancel(); + } + } + }); + } catch (e) { + printLog('Initial animation error: $e'); + } + } }); printLog('_startAnimation'); } void _stopAnimation() { - _timer.cancel(); + _timer?.cancel(); + _timer = null; printLog('_stopAnimation'); } @@ -162,9 +182,21 @@ class _ShakeImageState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { - _controller.stop(); + if (mounted) { + try { + _controller.stop(); + } catch (e) { + printLog('Stop animation error: $e'); + } + } } else if (state == AppLifecycleState.resumed) { - _controller.forward(); + if (mounted) { + try { + _controller.forward(); + } catch (e) { + printLog('Resume animation error: $e'); + } + } } } @@ -172,9 +204,13 @@ class _ShakeImageState extends State void dispose() { printLog('dispose'); customerRouteObserver.unsubscribe(this); - WidgetsBinding.instance.removeObserver(this); + try { + WidgetsBinding.instance.removeObserver(this); + } catch (e) { + printLog('Remove observer error: $e'); + } _controller.dispose(); - _timer.cancel(); + _timer?.cancel(); super.dispose(); } diff --git a/lib/pages/reading/bloc/reading_bloc.dart b/lib/pages/reading/bloc/reading_bloc.dart index dafe15f..1b2a3e2 100644 --- a/lib/pages/reading/bloc/reading_bloc.dart +++ b/lib/pages/reading/bloc/reading_bloc.dart @@ -293,9 +293,13 @@ class ReadingPageBloc } Future _playAudio(String? audioUrl) async { - if (audioUrl!.isNotEmpty) { - await audioPlayer.play(UrlSource(audioUrl), - balance: 0.0, ctx: AudioContext()); + if (audioUrl != null && audioUrl.isNotEmpty) { + try { + await audioPlayer.play(UrlSource(audioUrl), + balance: 0.0, ctx: AudioContext()); + } catch (e) { + Log.d('_playAudio error: $e'); + } } } @@ -321,10 +325,10 @@ class ReadingPageBloc } List resultDetails; if (currentPageData()?.resultDetails != null) { - resultDetails = currentPageData()!.resultDetails!; + resultDetails = currentPageData()?.resultDetails ?? []; } else { List? wordList = currentPageData()?.word?.split(RegExp(r'\s+')); - resultDetails = wordList! + resultDetails = (wordList ?? []) .map( (word) => SingsoundResultDetailEntity.withCharAndScore(word, 0)) .toList(); diff --git a/lib/pages/section/section_page.dart b/lib/pages/section/section_page.dart index 19df9be..512f140 100644 --- a/lib/pages/section/section_page.dart +++ b/lib/pages/section/section_page.dart @@ -85,13 +85,17 @@ class _SectionPageView extends StatelessWidget { 'courseLessonId': courseLessonId, 'isTopic': true }).then((value) { - if (value != null) { + if (value != null && !bloc.isClosed) { Map dataMap = value as Map; - bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, dataMap['isCompleted'], - currentTime: dataMap['currentTime'], - autoNextSection: dataMap['nextSection'])); + try { + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, dataMap['isCompleted'], + currentTime: dataMap['currentTime'], + autoNextSection: dataMap['nextSection'])); + } catch (e) { + print('BLoC add event error: $e'); + } } AudioPlayerUtil.getInstance() .playAudio(AudioPlayerUtilType.countWithMe); @@ -111,15 +115,19 @@ class _SectionPageView extends StatelessWidget { 'courseLessonId': courseLessonId, 'moduleColor': ModuleCache.instance.getCurrentThemeColor() }).then((value) { - if (value != null) { + if (value != null && !bloc.isClosed) { Map dataMap = value as Map; - bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, - dataMap['isCompleted'], - currentStep: dataMap['currentStep'], - autoNextSection: dataMap['nextSection'], - )); + try { + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, + dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'], + )); + } catch (e) { + print('BLoC add event error: $e'); + } AudioPlayerUtil.getInstance() .playAudio(AudioPlayerUtilType.countWithMe); } @@ -138,13 +146,17 @@ class _SectionPageView extends StatelessWidget { 'courseLessonId': courseLessonId, 'moduleColor': ModuleCache.instance.getCurrentThemeColor() }).then((value) { - if (value != null) { + if (value != null && !bloc.isClosed) { Map dataMap = value as Map; - bloc.add(RequestEndClassEvent( - dataMap['courseLessonId']!, dataMap['isCompleted'], - currentStep: dataMap['currentStep'], - autoNextSection: dataMap['nextSection'])); + try { + bloc.add(RequestEndClassEvent( + dataMap['courseLessonId']!, dataMap['isCompleted'], + currentStep: dataMap['currentStep'], + autoNextSection: dataMap['nextSection'])); + } catch (e) { + print('BLoC add event error: $e'); + } } AudioPlayerUtil.getInstance() .playAudio(AudioPlayerUtilType.countWithMe); @@ -183,7 +195,9 @@ class _SectionPageView extends StatelessWidget { itemCount: bloc.unlockPageCount(), controller: bloc.pageController, onPageChanged: (int index) { - bloc.add(CurrentUnitIndexChangeEvent(index)); + if (!bloc.isClosed) { + bloc.add(CurrentUnitIndexChangeEvent(index)); + } }, itemBuilder: (context, index) { return ScrollConfiguration( diff --git a/lib/pages/unit/view.dart b/lib/pages/unit/view.dart index 78148c7..54657b1 100644 --- a/lib/pages/unit/view.dart +++ b/lib/pages/unit/view.dart @@ -78,7 +78,7 @@ class UnitPage extends StatelessWidget { if (value != null) { Map dataMap = value as Map; - bool needRefresh = dataMap['needRefresh']; + bool needRefresh = dataMap['needRefresh'] ?? false; if (needRefresh) { bloc.add(RequestUnitDataEvent( courseModuleEntity?.id)); diff --git a/lib/pages/video/lookvideo/widgets/video_widget.dart b/lib/pages/video/lookvideo/widgets/video_widget.dart index 7987b4d..4e29028 100644 --- a/lib/pages/video/lookvideo/widgets/video_widget.dart +++ b/lib/pages/video/lookvideo/widgets/video_widget.dart @@ -81,7 +81,9 @@ class _VideoWidgetState extends State { 'nextSection': widget.isTopic }); } as VoidCallback, againSectionTap: (() { - lookVideoBloc.add(SectionAgainEvent()); + if (mounted && !lookVideoBloc.isClosed) { + lookVideoBloc.add(SectionAgainEvent()); + } }), context: context); } } diff --git a/lib/utils/audio_player_util.dart b/lib/utils/audio_player_util.dart index a0cfaaa..e3abdf9 100644 --- a/lib/utils/audio_player_util.dart +++ b/lib/utils/audio_player_util.dart @@ -30,7 +30,7 @@ enum AudioPlayerUtilType { class AudioPlayerUtil extends WidgetsBindingObserver { static AudioPlayerUtil? _instance; - late AudioPlayer _audioPlayer; + AudioPlayer? _audioPlayer; late AudioPlayerUtilType currentType; bool _wasPlaying = false; static const TAG = "AudioPlayerUtil"; @@ -43,7 +43,7 @@ class AudioPlayerUtil extends WidgetsBindingObserver { if (!BasicConfig.isEnvProd()) { AudioLogger.logLevel = AudioLogLevel.info; } - _audioPlayer.onPlayerStateChanged.listen((event) async { + _audioPlayer?.onPlayerStateChanged.listen((event) async { Log.d("$TAG onPlayerStateChanged $event _wasPlaying=$_wasPlaying"); if (event == PlayerState.completed) { // 播放结束再次播放 @@ -70,42 +70,66 @@ class AudioPlayerUtil extends WidgetsBindingObserver { // 播放音频 Future playAudio(AudioPlayerUtilType type) async { Log.d('$TAG playAudio begin $type'); + if (_audioPlayer == null) { + Log.d('$TAG playAudio _audioPlayer is null, creating new one'); + _audioPlayer = AudioPlayer(); + } currentType = type; String path = type.path; - await _audioPlayer.play(AssetSource(path.assetMp3), volume: 0.5); - await _audioPlayer.onPlayerComplete.first; + try { + await _audioPlayer!.play(AssetSource(path.assetMp3), volume: 0.5); + await _audioPlayer!.onPlayerComplete.first; + } catch (e) { + Log.d('$TAG playAudio error: $e'); + } Log.d('$TAG playAudio end $type'); } // stop Future stop() async { - Log.d("$TAG stop _audioPlayer.state=${_audioPlayer.state}"); - if (_audioPlayer.state == PlayerState.playing) { - await _audioPlayer.stop(); + if (_audioPlayer == null) return; + Log.d("$TAG stop _audioPlayer.state=${_audioPlayer!.state}"); + try { + if (_audioPlayer!.state == PlayerState.playing) { + await _audioPlayer!.stop(); + } + } catch (e) { + Log.d("$TAG stop error: $e"); } } // pause Future pause() async { - Log.d("$TAG pause _audioPlayer.state=${_audioPlayer.state}"); - if (_audioPlayer.state == PlayerState.playing) { - await _audioPlayer.pause(); + if (_audioPlayer == null) return; + Log.d("$TAG pause _audioPlayer.state=${_audioPlayer!.state}"); + try { + if (_audioPlayer!.state == PlayerState.playing) { + await _audioPlayer!.pause(); + } + } catch (e) { + Log.d("$TAG pause error: $e"); } } // resume Future resume() async { - Log.d("$TAG resume _audioPlayer.state=${_audioPlayer.state}"); - if (_audioPlayer.state == PlayerState.paused) { - await _audioPlayer.resume(); + if (_audioPlayer == null) return; + Log.d("$TAG resume _audioPlayer.state=${_audioPlayer!.state}"); + try { + if (_audioPlayer!.state == PlayerState.paused) { + await _audioPlayer!.resume(); + } + } catch (e) { + Log.d("$TAG resume error: $e"); } } @override void didChangeAppLifecycleState(AppLifecycleState state) async { - Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer.state}"); + if (_audioPlayer == null) return; + Log.d("$TAG didChangeAppLifecycleState appState=$state _wasPlaying=$_wasPlaying _audioPlayer.state=${_audioPlayer!.state}"); if (state == AppLifecycleState.paused) { - if (_audioPlayer.state == PlayerState.playing) { + if (_audioPlayer!.state == PlayerState.playing) { _wasPlaying = true; await pause(); }; @@ -118,12 +142,15 @@ class AudioPlayerUtil extends WidgetsBindingObserver { } bool isPlaying() { - return _audioPlayer.state == PlayerState.playing; + return _audioPlayer?.state == PlayerState.playing; } void dispose() { - Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer.state}"); - _audioPlayer.dispose(); + if (_audioPlayer != null) { + Log.d("$TAG dispose _audioPlayer.state=${_audioPlayer!.state}"); + _audioPlayer!.dispose(); + _audioPlayer = null; + } WidgetsBinding.instance.removeObserver(this); } } -- libgit2 0.22.2