Flutter 性能优化汇总工作中用到的点

官方文档中比较浅显的一些
1.谨慎使用 saveLayer()

  • ShaderMask
  • ColorFilter
  • Chip— 当 disabledColorAlpha != 0xff 的时候,会调用 saveLayer()
  • Text— 当有 overflowShader 时,会调用saveLayer()

2.Opacity 别用 方案 :

Image.network(
'https://raw.githubusercontent.com/flutter/assets-for-api-docs/main/packages/diagrams/assets/blend_mode_destination.jpeg',

color: const Color.fromRGBO(255, 255, 255, 0.5),

colorBlendMode: BlendMode.modulate

)

要在图像中实现淡入淡出,请考虑使用 FadeInImage widget

  • Clipping 不会调用 saveLayer() (除非明确使用 Clip.antiAliasWithSaveLayer),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。
  • 加载动画使用骨骼动画? shadermask

https://docs.flutter.cn/cookbook/effects/shimmer-loading

3.animator 中千万别在vsync值监听中去setstate

4.如果大多数 children widget 在屏幕上不可见,请避免使用返回具体列表的构造函数(例如 Column() 或 ListView()),以避免构建成本。

避免在 Widget 对象上重写 operator ==。虽然这看起来有助于避免不必要的重建,但在实践中,它实际上损害了性能,因为这是 O(N²) 的行为。只有 leaf widget(没有子的 widget)是个例外,在这种特殊的情况下,比较 widget 的属性可能比重建 widget 更加有效,也能更少改变 widget 的配置。即使在这种情况下,最好还要缓存 widget,因为哪怕有一次对 operator == 进行覆盖也会导致全面性能的下降,编译器也会因此不再认为调用总是静态的。

5.监听的指标 Raster Build UI线程 jank 包含

要避免 Flutter 中的 Scroll Jank(滚动卡顿),可以从以下几个方面入手,结合具体场景进行优化:

1. 理解 Scroll Jank 的根源

  • 帧率不稳定: 滚动流畅需要稳定的 60 FPS(或更高的刷新率)。任何导致帧率下降的操作都可能引起卡顿[参考资料缺失]。

  • 复杂的构建/渲染: 在滚动过程中,如果 Flutter 需要进行大量的 Widget 构建或复杂的渲染计算,就会占用主线程时间,导致掉帧[参考资料缺失]。

  • 资源加载延迟: 滚动时加载图片、网络数据等资源,如果加载速度慢,会阻塞 UI 线程[参考资料缺失]。

2. 优化策略

  • a. 减少 Widget 重建

  • 使用 const 关键字: 对于静态的、不变的 Widget,使用 const 关键字可以避免不必要的重建[参考资料缺失]。


const Text('This is a static text');

  • 使用 const Widget 作为子节点: 如果 Widget 的子节点是 const 的,那么当父 Widget rebuild 时,子节点可以跳过 rebuild[参考资料缺失]。

  • 使用 Key 当列表中的 Widget 顺序发生变化时,Flutter 默认会尝试复用现有的 Widget。但如果 Widget 的内容也发生了变化,就会导致不必要的 rebuild。使用 Key 可以帮助 Flutter 正确地识别和复用 Widget[参考资料缺失]。

  • 使用 ListView.builderSliverList 这两种方式只构建可见区域的 Widget,避免一次性构建大量 Widget[参考资料缺失]。

  • 使用 AutomaticKeepAliveClientMixin 对于 PageViewTabBarView 中的页面,可以使用 AutomaticKeepAliveClientMixin 来保持页面的状态,避免每次切换时都重新构建[参考资料缺失]。

  • b. 优化图片加载

  • 使用 CachedNetworkImage CachedNetworkImage 库可以缓存网络图片,避免重复加载[参考资料缺失]。

  • 使用占位符: 在图片加载完成之前,显示一个占位符,避免 UI 闪烁[参考资料缺失]。

  • 预加载图片: 在滚动开始之前,预先加载一部分图片,减少滚动过程中的加载延迟[参考资料缺失]。

  • 调整图片大小: 加载适合屏幕尺寸的图片,避免加载过大的图片[参考资料缺失]。

  • c. 异步处理耗时操作

  • 使用 FutureBuilderStreamBuilder 将网络请求、数据库查询等耗时操作放在异步任务中执行,避免阻塞 UI 线程[参考资料缺失]。

  • 使用 compute 函数: 对于 CPU 密集型任务(例如复杂的数学计算),可以使用 compute 函数在后台线程中执行[参考资料缺失]。

  • d. 避免在 Build 方法中执行耗时操作

  • Build 方法应该只负责构建 UI,避免在其中执行任何耗时操作。耗时操作应该放在其他地方执行,例如 initStatedidChangeDependencies 或事件处理函数中[参考资料缺失]。

  • e. 减少 Overdraw

  • Overdraw 指的是在同一像素上绘制多次。过多的 Overdraw 会浪费 GPU 资源,导致性能下降。可以使用 Flutter DevTools 中的 Overdraw 可视化工具来检测 Overdraw 情况,并进行优化[参考资料缺失]。

  • f. 优化自定义绘制

  • 如果使用了 CustomPaint 进行自定义绘制,需要确保绘制逻辑是高效的。避免在 paint 方法中进行复杂的计算或资源加载[参考资料缺失]。

  • 可以使用 shouldRepaint 方法来控制是否需要重新绘制。只有当绘制内容发生变化时,才需要重新绘制[参考资料缺失]。

  • g. 使用 Profiler 进行性能分析

  • Flutter DevTools 提供了强大的 Profiler 工具,可以帮助你分析应用的性能瓶颈。通过 Profiler,你可以找到导致卡顿的具体原因,并进行针对性的优化[参考资料缺失]。

  • h. 启用 Impeller 渲染引擎

  • Impeller 是 Flutter 新的渲染引擎,它通过预编译着色器等技术,可以提高渲染性能,减少卡顿。目前 Impeller 在 iOS 上默认启用,Android 上还在开发中[参考资料缺失]。

3. 代码示例


import 'package:flutter/material.dart';

import 'package:cached_network_image/cached_network_image.dart';

class MyList extends StatelessWidget {

 final List<String> imageUrls;

 MyList({Key? key, required this.imageUrls}) : super(key: key);

 @override

 Widget build(BuildContext context) {

 return ListView.builder(

 itemCount: imageUrls.length,

 itemBuilder: (context, index) {

 return Card(

 child: Row(

 children: [

 CachedNetworkImage(

 imageUrl: imageUrls[index],

 placeholder: (context, url) => CircularProgressIndicator(),

 errorWidget: (context, url, error) => Icon(Icons.error),

 width: 100,

 height: 100,

 fit: BoxFit.cover,

 ),

 SizedBox(width: 16),

 Expanded(

 child: Text('Image ${index + 1}'),

 ),

 ],

 ),

 );

 },

 );

 }

}

总结

避免 Scroll Jank 需要综合考虑多个因素,包括减少 Widget 重建、优化图片加载、异步处理耗时操作、减少 Overdraw 等。通过使用 Flutter DevTools 进行性能分析,可以帮助你找到具体的瓶颈,并进行针对性的优化。 记住,针对不同的场景,优化的侧重点也会有所不同。

一、构建优化(Build 阶段优化)

核心原则:减少 Widget 树重建范围和频次

  1. const 使用原则:

    // ✅ 正确:使用 const 构造函数
    const ListTile(title: const Text('Title'));
    
    // ❌ 错误:所有元素都不带 const
    ListTile(title: Text('Title'));
    

    原理const 组件会被编译为编译期常量,有效减少重建时的对象创建开销

  2. Widget 拆分标准:

    • 业务逻辑分离:逻辑操作与布局代码隔离
    • 频繁更新的部分独立成子Widget(如动画元素)
    • 示例:
    // 拆分前:整个列表项在父组件中构建
    itemBuilder: (ctx, i) => ListTile(
      leading: Image.network(data[i].url),
      title: Text(data[i].title),
      subtitle: _buildComplexContent(),
    )
    
    // 拆分后:独立组件优化
    itemBuilder: (ctx, i) => ListItem(data[i])
    
    class ListItem extends StatelessWidget {
      final Data data;
      const ListItem(this.data);
    
      @override
      Widget build(BuildContext context) {
        return ListTile(
          leading: OptimizedImage(data.url),
          title: Text(data.title),
          subtitle: ContentWidget(data.content),
        );
      }
    }
    

二、内存管理

核心原则:合理控制对象生命周期

  1. ListView 优化铁律:

    • 禁用 addAutomaticKeepAlives(分页加载场景)
    • 动态保持关键状态:
    // 自定义条件保留状态组件
    class ConditionalKeepAlive extends StatefulWidget {
      final bool keepAlive;
      final Widget child;
    
      const ConditionalKeepAlive({
        required this.keepAlive,
        required this.child
      });
    
      @override
      _ConditionalKeepAliveState createState() => _ConditionalKeepAliveState();
    }
    
    class _ConditionalKeepAliveState extends State<ConditionalKeepAlive> 
      with AutomaticKeepAliveClientMixin {
      
      @override
      bool get wantKeepAlive => widget.keepAlive;
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return widget.child;
      }
    }
    
  2. 图片缓存策略:

    CachedNetworkImage(
      imageUrl: url,
      memCacheWidth: (MediaQuery.of(context).size.width * 2).toInt(), // 物理像素适配
      maxWidthDiskCache: 1024, // 服务器端大图时限制本地存储尺寸
      filterQuality: FilterQuality.low, // 降低渲染质量节省GPU资源
    )
    

三、滚动性能优化(List/Grid 核心优化点)

核心指标:滚动帧率稳定在 60fps

  1. 基准优化方案:

    ListView.builder(
      itemCount: 1000,
      itemExtent: 80, // ✅ 显式设定行高(避免动态计算)
      cacheExtent: 500, // ✅ 预加载区域调整
      addAutomaticKeepAlives: false, // ❗️禁用自动保持
      addRepaintBoundaries: true, // ✅ 智能添加重绘隔离
      physics: const BouncingScrollPhysics(), // ❗️低端机用ClampingScrollPhysics
    )
    
  2. 分页加载优化公式:

    加载公式:触发阈值 = 最大滚动距离 - 当前偏移
    公式实现:
    if (scrollOffset >= maxScrollExtent - triggerThreshold) {
      loadNextPage();
    }
    

    代码实现:

    void _scrollListener() {
      final max = _controller.position.maxScrollExtent;
      final current = _controller.position.pixels;
      if (current >= max - 200) { // 提前200像素加载
        _loadData();
      }
    }
    

四、渲染管线优化

核心原理:减少 GPU 合成层数量

  1. 重绘隔离技巧:

    • 动态判断是否添加隔离层:
    Widget build(BuildContext context) {
      final needsBoundary = hasAnimations || hasFrequentUpdates;
      return needsBoundary 
          ? RepaintBoundary(child: _realContent())
          : _realContent();
    }
    
  2. 图层合并优化:

    // ✅ 正确:层级扁平化
    Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [/*...*/],
      ),
    )
    
    // ❌ 错误:过度嵌套
    Container(
      color: Colors.white,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(8),
        child: Container(
          decoration: BoxShadow(...),
        ),
      ),
    )
    

五、关键性能指标及调优工具

诊断指标对照表:

指标 健康范围 危险信号 优化措施
FPS (帧率) ≥58 fps <45 fps 检查渲染管线
UI 线程耗时 <16ms >30ms 优化build方法
GPU 线程耗时 <8ms >12ms 减少图层合成
对象创建数/帧 <1000 >2000 检查对象复用
Scroll jank 0-2次/滚动 >5次/滚动 列表项优化

工具链使用指南:

# 性能分析专用构建命令
flutter run --profile --purge-persistent-cache

# 调试包分析
flutter build apk --analyze-size
flutter build appbundle --target-platform android-arm64 --analyze-size

六、进阶性能模式(针对复杂场景)

  1. 按需渲染模式

    class SmartListView extends StatefulWidget {
      @override
      _SmartListViewState createState() => _SmartListViewState();
    }
    
    class _SmartListViewState extends State<SmartListView> {
      final _visibleIndexes = <int>{};
    
      void _handleScroll(ScrollMetrics metrics) {
        final first = metrics.minScrollExtent;
        final last = metrics.maxScrollExtent;
        final newIndexes = calculateVisibleItems(first, last);
        if (newIndexes != _visibleIndexes) {
          setState(() => _visibleIndexes = newIndexes);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return ValueListenableBuilder(
          valueListenable: DataModel.itemsNotifier,
          builder: (ctx, items, _) {
            return ListView.builder(
              itemCount: items.length,
              itemBuilder: (ctx, index) {
                return VisibilityTracker(
                  visible: _visibleIndexes.contains(index),
                  child: ListItem(item: items[index]),
                );
              },
            );
          },
        );
      }
    }
    
  2. 内存压缩方案

    // 图片内存优化组件
    class CompressedImage extends StatelessWidget {
      final String url;
    
      const CompressedImage(this.url);
    
      @override
      Widget build(BuildContext context) {
        return Image.network(
          url,
          cacheWidth: MediaQuery.of(context).size.width.toInt() * 2,
          filterQuality: FilterQuality.low,
          loadingBuilder: (ctx, child, progress) {
            return progress == null 
                ? child 
                : Shimmer.fromColors(...); // 内存友好的加载动画
          },
        );
      }
    }
    

结语:性能优化四步法则

  1. :每次修改后用 devtools 性能面板验证
  2. :通过时间线定位具体问题(构建、布局、绘制、合成)
  3. :针对性应用上述优化方案
  4. :权衡优化效果与复杂度,避免过度优化

记住:用真机测不要用假机测原因是模拟器有些很大的差距 ❗️

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容