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,
          ),
        ),
      ],
    );
  }
}
相关推荐
前端拾光者20 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
Json_1817901448038 分钟前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子02041 小时前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing1 小时前
React核心功能详解(一)
前端·react.js·前端框架
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆1 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China1 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q1 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海1 小时前
Chrome离线安装包下载
前端·chrome