Flutter真实项目中bug解决详解

问题1 dynamic是否是使用过多,使用dynamic还是object?

dynamic和object?能够赋值为任何类型的实例变量,同时也能复制为null。dynamic的类型在运行时进行评估,因此,为了正确使用,您需要进行检查和类型转换。dynamic的变量还能进行类型的改变,比如最开始值设置为123(int),然后设置为"123"(String),最后设置为null,这方面可以理解为dynamic是弱类型的。最大的问题是dynamic类型安全为零,因为它旨在用于运行时强制类型转换。由于强制类型转换必须安全,因此你必须为每个动态变量处理一长串if判断类型语句,编译器和IDE无法帮助你进行静态分析,这样就增加了运行时异常的可能性。但是dynamic在json解析方面和collection的泛型方面还是有比较多的使用的。示例代码如下所示:

ini 复制代码
void main() {
  dynamic a = 1;
  print(a);
  print("a is int : ${a is int}");
  a = '123';
  print(a);
  a = null;
  print(a);
  dynamic value = getValue();
  print('value is $value and runtime is ${value.runtimeType}');
}

int getValue() {
  return 124;
}

问题2 const为什么能够提高性能?

首先科普一下对象是否是同一个对象:对象的堆内存地址是相同的;对象的值是否相同:对象的属性值是否相同。Dart语言中用identical方法判断对象是否是同一个对象。常量规范化确保两个编译时常量表达式的结果创建具有相同状态的对象,也计算对同一规范实例的引用。const修饰的Widget或者其他类型的变量在编译时实例化,而不是运行时。Flutter在使用==或深度比较之前通过identical方法(指针相等性)比较 Widget。Flutter重用相同的Widget实例,避免不必要的重建和内存分配。这样就同时在时间和空间两方面进行了提升。示例代码如下所示:

csharp 复制代码
const zero = const Point(x: 0, y: 0);
const zero1 = const Point(x: 0, y: 0);
void main() {
   print(identical(zero, zero1));
}

class Point {
  final int x;
  final int y;
  const Point({required this.x, required this.y});
}

//stateless widiget
@override
Widget build(BuildContext context) {
  return const Text('Hello, World'); // ✅ Use const
}

问题3 集合判断中因为没有重写==和hashcode引起的异常

默认的哈希码实现提供了一个恒等哈希------通常只有两个对象完全相同时,它们的哈希码才会相同。同样,== 的默认行为也是恒等。如果您重写了==,则意味着您的类可能认为不同的对象"相等"。任何两个相等的对象都必须具有相同的哈希码。否则,Map和其他基于哈希的集合将无法识别这两个对象是等价的。任何两个对象==,那么他们的哈希码必须相同;如果两个对象的哈希码相同,那么他们两个不一定==。简单来说可以理解为判断对象是否==,判断的是对象的属性值是否相同,属性可能有多个,多个属性的组合个数比哈希码的范围要大。示例代码如下所示:

dart 复制代码
void main() {
  var set = <TaskItem>{};
  print(set.length);
  set.add(TaskItem(id: 1, name: "TaskItem1", isDone: false));
  print(set.length);
  set.add(TaskItem(id: 1, name: "TaskItem1", isDone: false));
  print(set.length);
}


class TaskItem {
  int id;
  String name;
  bool isDone;

  @override
  bool operator ==(Object other) => other is TaskItem && other.id == id
  && other.name == name;

  @override
  int get hashCode => id.hashCode;

  TaskItem({required this.id, required this.name, required this.isDone});

  void toggle() {
    this.isDone = !this.isDone;
  }
}

注释Set的默认实现是LinkedHashSet。 如果没有重写operator ==和hashCode,三次print输出的结果依次是0,1,2;重写operator ==和hashCode之后,三次print输出的结果依次是0,1,1,第二次新增TaskItem的时候判断hashCode和operator ==已经在Set之中,因为Set不能重复,所以第二次就不能添加成功了。Set这种集合在软件开发中比较常见,用来保存非重复的数据。比较常见的场景购物车的商品列表,日程软件中的日程任务等等。

问题3 NPE是否常见(Nullable and Non-nullable types)

NullPointerException就是大名鼎鼎的空指针异常,此异常会引起软件异常甚至崩溃。最热乎的空指针异常事件就是谷歌云在2025年6月12日发生全球大宕机。示例代码如下所示:

csharp 复制代码
void main() {
  int value;
  print("$value");
  value = 0;
}

运行结果如下图所示:

变量会被立即赋值或者变量的值在被访问之前就已经被赋值,这种情况都不会有问题。上面相同的情况同样发生在late修饰的变量。

可空类型强制转换的时候需要注意,请看下面示例,代码如下:

javascript 复制代码
void main() {
  String? name = null;
  print(name!.length);
}

运行结果如下:

问题4 巧妙使用async/await,提高性能

在Flutter开发过程中,异步async/await是非常常见的模式,但如果用得不当,可能会影响性能,尤其是在 UI构建、并发任务、启动流程等场景。如果任务之间没有依赖关系,避免串行等待,多任务并发执行,可以显著提高性能,尤其是多个异步I/O请求时。代码示例如下:

ini 复制代码
final futureA = loadA();
final futureB = loadB();
final futureC = loadC();

final results = await Future.wait([futureA, futureB, futureC]);

将计算密集操作移动到后台isolate,示例代码如下所示:

ini 复制代码
final result = await compute(expensiveFunction, input);

官方文档使用指南首先指出的是"优先使用 async/await,而不是使用原始的 Future"。由于异步代码可能难以阅读和调试,因此你应该优先使用 async 和 await,而不是使用 then() 和 catchError() 的循环。

再次感谢await,异步代码可以像同步代码一样编写。除了减少样板代码外,它还更易于阅读、维护和理解!同时也没有回调地狱的问题了,从n层回调减到1层。

参考资料

dart.dev/effective-d...

相关推荐
傅里叶1 小时前
Flutter项目使用 buf.build
flutter
恋猫de小郭2 小时前
iOS 26 开始强制 UIScene ,你的 Flutter 插件准备好迁移支持了吗?
android·前端·flutter
極光未晚3 小时前
乾坤微前端项目:前端处理后台分批次返回的 Markdown 流式数据
前端·vue.js·面试
地方地方3 小时前
event loop 事件循环
前端·javascript·面试
yuanlaile3 小时前
Flutter开发HarmonyOS鸿蒙App商业项目实战已出炉
flutter·华为·harmonyos
CodeCaptain4 小时前
可直接落地的「Flutter 桥接鸿蒙 WebSocket」端到端实施方案
websocket·flutter·harmonyos
stringwu4 小时前
Flutter 中的 MVVM 架构实现指南
前端·flutter
绝无仅有6 小时前
某银行大厂面试技术问题深度解析(一)
后端·面试·github
绝无仅有6 小时前
面试自述:从单体到微服务的实践之路
后端·面试·github