回顾 24年 Flutter 骨架屏没有释放 CurvedAnimation 导致内存泄漏的血案

📋 问题概述

在 Flutter 应用开发中,骨架屏是一个常用的 UI 组件,用于在数据加载时提供视觉反馈。然而,如果不正确处理动画对象的生命周期,会导致严重的内存泄漏问题,最终引发应用卡顿。而我们踩这个坑是发生在去年,我这里做了记录回顾一下案情过程~

🚨 血案回顾

1. 问题现象

  • App 使用一段时间后页面变得卡顿
  • 内存占用持续增长
  • 骨架屏动画越多,问题越严重
  • 用户反馈应用越来越慢

2. 排查过程

  • 1、团队排查很久没有发现问题 & 有伙伴提出可能是骨架屏动画较多导致? 我们的封装是参考了此篇文章的 link 因此也不觉得有什么不对。
  • 2、后来在我给官方 Flutter 提交一个修复 PR 合并后才被检测出内存泄漏。后来官方人员也提了 Pr 解决了问题!
  • 3、这说明是很容易被大家忽视的,即使是官方人员的审核~
  • 4、此时联系起应用中的卡顿问题 😂😂😂

🔍 根本原因分析

问题代码示例

dart 复制代码
// ❌ 错误实现 - 存在内存泄漏
gradientPosition = Tween<double>(
  begin: -3,
  end: 10,
).animate(
  CurvedAnimation(parent: controller!, curve: Curves.linear), // 没有保存引用
)..addListener(() {
    if (mounted) {
      setState(() {});
    }
  });

内存泄漏原因

  1. CurvedAnimation 没有保存引用:创建后无法手动释放
  2. Animation 对象持有强引用:对 AnimationController 形成强引用链
  3. Widget 销毁时未释放:CurvedAnimation 没有被正确释放
  4. 循环引用:Animation 和 Controller 之间形成循环引用

🛠️ 修复方案

正确实现

dart 复制代码
class TWSkeletonViewState extends State<TWSkeletonView>
    with SingleTickerProviderStateMixin {
  
  AnimationController? controller;
  Animation? gradientPosition;
  // ✅ 添加 CurvedAnimation 引用
  CurvedAnimation? curvedAnimation;

  @override
  void initState() {
    super.initState();
    // ... 其他初始化代码
    
    if (controller != null) {
      // ✅ 保存 CurvedAnimation 引用
      curvedAnimation = CurvedAnimation(
        parent: controller!, 
        curve: Curves.linear
      );
      
      gradientPosition = Tween<double>(
        begin: -3,
        end: 10,
      ).animate(curvedAnimation!)
        ..addListener(() {
          if (mounted) {
            setState(() {});
          }
        });
    }
  }

  @override
  void dispose() {
    // ✅ 手动释放 CurvedAnimation
    curvedAnimation?.dispose();
    curvedAnimation = null;
    controller?.dispose();
    controller = null;
    super.dispose();
  }
}

📚 相关资源

Flutter 官方的 issue 说明他们一直在优化框架

阶段1 Clean up notDisposed leaks in Flutter Framework, phase 1.

传送门

阶段2 Clean up memory leaks in Flutter Framework, phase 2.

传送门

Classes to instrument

  • RouteEntry
  • AnimationController
  • AnimationEagerListenerMixin
  • BannerPainter
  • ChangeNotifier
  • CurvedAnimation
  • DisposableBuildContext
  • Element
  • GestureRecognizer
  • Image
  • ImageInfo
  • ImageStreamCompleterHandle
  • Layer
  • MultiDragPointerState
  • OverlayEntry
  • PerformanceModeRequestHandle
  • Picture
  • PipelineOwner
  • RenderObject
  • RestorationBucket
  • Route
  • ScrollDragController
  • SelectionOverlay
  • SemanticOwner
  • SnapshotPainter
  • State
  • TextPainter
  • TextSelectionOverlay

阶段3 Clean up 'not disposed' memory leaks in FF, phase 3

传送门

  • AnimationEagerListenerMixin
  • CurvedAnimation
  • TrainHoppingAnimation
  • ImageInfo
  • BoxPainter
  • ScrollDragController
  • SelectionOverlay
  • SnapshotPainter
  • TextSelectionOverlay

后来我们也陆续参与的其他开源组件修复

等等...这说明我们一不留意就忽视了手动释放

🎯 最佳实践

1. 引用管理

dart 复制代码
// ✅ 保存所有需要释放的引用
late AnimationController controller;
CurvedAnimation? curvedAnimation;
StreamSubscription? subscription;
Timer? timer;

2. 释放顺序

dart 复制代码
@override
void dispose() {
  timer?.cancel();
  timer = null;
  
  subscription?.cancel();
  subscription = null;
  
  curvedAnimation?.dispose();
  curvedAnimation = null;
  
  controller.dispose();
  
  super.dispose();
}

3. 安全检查

dart 复制代码
// ✅ 使用 mounted 检查
if (mounted) {
  setState(() {});
}

// ✅ 使用 ?. 操作符
curvedAnimation?.dispose();

4. 内存监控

dart 复制代码
// ✅ 在开发阶段监控内存使用
import 'dart:developer' as developer;

void logMemoryUsage() {
  developer.log('Memory usage: ${ProcessInfo.currentRss}');
}

🧪 测试方法

1. 内存泄漏测试

dart 复制代码
// 创建大量组件
List<TWSkeletonView> skeletons = List.generate(
  1000, 
  (index) => TWSkeletonView()
);

// 重复创建和销毁
for (int i = 0; i < 100; i++) {
  setState(() {
    skeletons = List.generate(1000, (index) => TWSkeletonView());
  });
}

2. 性能监控

  • 使用 Flutter Inspector 监控内存使用
  • 观察内存增长趋势
  • 检查是否有内存泄漏警告

📈 影响评估

内存泄漏的影响

  1. 内存占用持续增长:每次创建组件都会泄漏内存
  2. 应用性能下降:内存不足时触发垃圾回收
  3. 用户体验恶化:页面卡顿、响应缓慢
  4. 系统资源浪费:影响其他应用性能

修复后的效果

  1. 内存使用稳定:不再持续增长
  2. 应用性能提升:减少垃圾回收频率
  3. 用户体验改善:页面流畅度提升
  4. 系统资源优化:更好的资源利用

🔧 预防措施

1. 代码审查

  • 检查所有 Animation 对象的生命周期管理
  • 确保 dispose() 方法正确实现
  • 验证引用是否正确释放

2. 自动化测试

  • 添加内存泄漏检测测试
  • 集成性能监控工具
  • 定期进行压力测试

3. 开发规范

  • 建立组件生命周期管理规范
  • 使用 lint 规则检查常见问题
  • 定期进行代码质量审查

📝 总结

CurvedAnimation 内存泄漏是一个容易被忽视但影响严重的问题。通过正确的引用管理和生命周期控制,可以有效避免这类问题。关键是要记住:

  1. 所有 Animation 对象都要保存引用
  2. 在 dispose() 中按正确顺序释放
  3. 释放后可以将引用设置为 null
  4. 使用 mounted 检查避免在已销毁的 Widget 上操作

写这篇文章的目的是提醒我们在 Flutter 开发中要特别注意特殊的对象内存管理,避免因为疏忽导致的内存泄漏问题。另外推荐下另外一篇做法 Flutter应用使用leak_tracker监控内存泄露 确实能有效的监控泄漏

相关推荐
mldong5 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排5 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60616 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角7 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!7 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰7 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi9 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen9 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马10 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒10 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端