flutter Timer报错
报错一:_timer没有被初始化
需求:按键之后才开始计时,如果一进来就初始化,就会立马计时,不符合要求。所以要按键完之后才初始化_timer。所以就需要用var 或者 dynamic这种不确定类型,来修饰_timer变量,late会报没有初始化
// late Timer _timer;//TODO 这样写会报:没有初始化
dynamic _timer;//or var 来修饰_timer;
报错二:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: setState() called after dispose(): _RecordExeWidgetState#1023f(lifecycle state: defunct, not mounted, ticker inactive) [ ]
E/flutter (17297): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. [ ]
E/flutter (17297): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree. [ ]
E/flutter (17297): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
原因:这个错误信息表明在Flutter应用中有一个setState()调用发生在了Widget被卸载之后,具体错误信息如下:
原因:通常这种情况发生在从定时器或动画回调中调用了setState()方法,而此时对应的State对象已经被销毁。
解决办法:
在dispose()方法中取消定时器或停止监听动画。
在调用setState()之前检查当前State对象是否仍然mounted。
如何避免
方法一:在dispose中清理资源
确保在State对象被销毁时释放所有资源,例如取消定时器:
class RecordExeWidget extends StatefulWidget {
@override
_RecordExeWidgetState createState() => _RecordExeWidgetState();
}
class _RecordExeWidgetState extends State<RecordExeWidget> {
Timer _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (mounted) {
// 更新UI
setState(() {
// 更新逻辑
});
}
});
}
@override
void dispose() {
_timer.cancel(); // 取消定时器
super.dispose();
}
}
方法二:检查mounted属性
在执行任何可能触发setState()操作的地方,先检查当前State对象是否仍然有效:
if (mounted) {
setState(() {
// 更新状态
});
}
报错三:
Unhandled Exception: 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4833 pos 12: '_lifecycleState != _ElementLifecycle.defunct': is not true.
原因:这个错误信息表明在Flutter框架内部检测到了一个断言失败,具体信息为 _lifecycleState != _ElementLifecycle.defunct 不成立。这意味着某个Widget的状态已经变为 defunct(即已被销毁),但仍尝试对其进行操作。
解决方案
检查生命周期状态 在调用 setState 之前,确保当前 State 对象仍然有效。
清理资源 确保在 dispose 方法中取消所有定时器和监听器。
示例代码
假设你有一个自定义的Widget,并且在这个Widget中有定时器或其他异步操作,你可以按照以下方式修改代码:
示例代码:
import 'package:flutter/material.dart';
class RecordExeWidget extends StatefulWidget {
@override
_RecordExeWidgetState createState() => _RecordExeWidgetState();
}
class _RecordExeWidgetState extends State<RecordExeWidget> {
Timer _timer;
int _counter = 0;
@override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (mounted) {
setState(() {
_counter++;
});
}
});
}
@override
Widget build(BuildContext context) {
return Text('Counter: $_counter');
}
@override
void dispose() {
_timer?.cancel(); // 取消定时器
super.dispose();
}
}
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Example')),
body: Center(child: RecordExeWidget()),
),
));
}
解释
1,检查 mounted 属性
在 Timer 的回调中,通过检查 mounted 属性来确保当前 State 对象仍然有效。如果 mounted 为 false,则说明 Widget 已经被销毁,不应再调用 setState。
2,清理定时器
在 dispose 方法中取消定时器,以防止内存泄漏和其他潜在问题。
通过这些步骤,可以有效地避免在 Widget 被销毁后仍尝试进行状态更新的情况。这样可以确保应用运行更加稳定,避免不必要的错误。
错误四:计时器停止后不会再启动
原因:
//如果这么写 // _timer ??= Timer.periodic(const Duration(seconds: 1), (Timer timer) { //TODO _timer?.cancel(); 执行后 timer里边就会不会再执行了,所以需要重新初始化 /// Example: /// ```dart /// final timer = /// Timer(const Duration(seconds: 5), () => print('Timer finished')); /// // Cancel timer, callback never called. /// timer.cancel(); /// ``` //所以应该每次都赋值 _timer = Timer.periodic(const Duration(seconds: 1), (Timer timer){} //TODO 页面关闭的时候记得 _timer?.cancel();_timer = null;就行
完整代码如下:
import 'dart:async';
import 'dart:developer';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:game_lib/common/common_page.dart';
import 'package:game_lib/common/my_tap.dart';
import 'package:lottie/lottie.dart';
class RecordExeWidget extends StatefulWidget {
const RecordExeWidget({super.key});
@override
State<StatefulWidget> createState() {
return _RecordExeWidgetState();
}
}
class _RecordExeWidgetState extends State<RecordExeWidget>
with RecordGuideVoice, SingleTickerProviderStateMixin {
bool recording = false;
late final AnimationController animationController =
AnimationController(vsync: this, duration: 1.seconds);
int _seconds = 0;
// late Timer _timer;//TODO 这样写会报:没有初始化
dynamic _timer;
void _startTimer() {
recording = true;
// _timer ??= Timer.periodic(const Duration(seconds: 1), (Timer timer) {
//TODO _timer?.cancel(); 执行后 timer里边就会不会再执行了,所以需要重新初始化
/// Example:
/// ```dart
/// final timer =
/// Timer(const Duration(seconds: 5), () => print('Timer finished'));
/// // Cancel timer, callback never called.
/// timer.cancel();
/// ```
//TODO 页面关闭的时候记得 _timer?.cancel();_timer = null;就行
_timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
log("===== record_ext_widget recording:$recording ,mounted:$mounted");
if (recording && mounted) {
log("===== record_ext_widget 开始计时");
setState(() {
_seconds += 1;
});
}
});
}
void _stopTimer() {
if (recording && mounted) {
log("===== record_ext_widget 停止计时");
setState(() {
_seconds = 0;
});
}
recording = false;
_timer?.cancel();
_timer = null;
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
_stopTimer();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//声音波浪
Image(
image: const AssetImage(
"assets/images/ic_sound_wave.png"),
width: 164.w,
height: 76.h,
fit: BoxFit.cover,
),
SizedBox(
width: 164.w,
height: 76.h,
child: Lottie.asset("assets/images/wave.zip",
package: "game_lib",
controller: animationController,
width: 164.w,
height: 76.h,
fit: BoxFit.fill)),
SizedBox(
height: 10.h,
),
//倒数秒
Row(
verticalDirection: VerticalDirection.down,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"$_seconds",
style: TextStyle(
color: const Color(0xff26AFC7),
fontFamily: "FZ-B",
fontSize: 40.sp,
),
),
Text(
"s",
style: TextStyle(
color: const Color(0xff26AFC7),
fontFamily: "FZ-B",
fontSize: 36.sp,
),
),
],
),
//麦克风图片,可以点击,点击有缩放效果
MyTap(
onTap: () {
//TODO 开始播放动画(波浪+麦克风),开始录音
//TODO 录音结束,提交后台,动画也要结束
if (!recording) {
_startTimer();
} else {
_stopTimer();
}
BotToast.showText(text: recording ? "开始录音了..." : "停止录音了...");
},
child: Image(
image: const AssetImage(
"assets/images/ic_microphone.png"),
width: 324.w,
height: 324.h,
fit: BoxFit.cover,
),
),
],
);
}
}