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对象被销毁时释放所有资源,例如取消定时器:

Dart 复制代码
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对象是否仍然有效:

Dart 复制代码
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中有定时器或其他异步操作,你可以按照以下方式修改代码:

示例代码:

Dart 复制代码
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;就行

完整代码如下:

Dart 复制代码
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,
          ),
        ),
      ],
    );
  }
}
相关推荐
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie1 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具3 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
清灵xmf3 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据3 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161773 小时前
防抖函数--应用场景及示例
前端·javascript
334554324 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel