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,
          ),
        ),
      ],
    );
  }
}
相关推荐
掘金安东尼9 小时前
让 JavaScript 更容易「善后」的新能力
前端·javascript·面试
掘金安东尼9 小时前
用 HTMX 为 React Data Grid 加速实时更新
前端·javascript·面试
灵感__idea11 小时前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
yinuo11 小时前
轻松接入大语言模型API -04
前端
袋鼠云数栈UED团队12 小时前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
cipher12 小时前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
UrbanJazzerati12 小时前
非常友好的Vue 3 生命周期详解
前端·面试
AAA阿giao12 小时前
从零构建一个现代登录页:深入解析 Tailwind CSS + Vite + Lucide React 的完整技术栈
前端·css·react.js
亦妤12 小时前
JS执行机制、作用域及作用域链
javascript
兆子龙13 小时前
像 React Hook 一样「自动触发」:用 Git Hook 拦住忘删的测试代码与其它翻车现场
前端·架构