Interview-Futter

366 阅读15分钟

flutter 进阶

Dart

static

static 关键字用于声明类级别的成员,包括字段、方法和常量。静态成员属于类本身,而不是类的实例。

静态字段(Static Fields)

静态字段是属于类的变量,而不是类的实例。所有类的实例共享同一个静态字段。

class MyClass {
  static int staticField = 0;
  
  void incrementField() {
    staticField++;
  }
}

void main() {
  MyClass obj1 = MyClass();
  MyClass obj2 = MyClass();
  
  obj1.incrementField();
  print(MyClass.staticField); // 输出: 1
  
  obj2.incrementField();
  print(MyClass.staticField); // 输出: 2
}

可以看到staticField在俩个对象里都有被++,即所有类的实例共享同一个静态字段。

静态方法(Static Methods)

静态方法是属于类的函数,而不是类的实例。它们不能访问实例成员(字段或方法),但可以访问静态成员。

class MyClass {
  static int staticField = 0;
  
  static void staticMethod() {
    print('Static field value: $staticField');
  }
}

void main() {
  MyClass.staticMethod(); // 输出: Static field value: 0
}

静态常量(Static Const)

静态常量是编译时常量,通常与 const 一起使用。

class MyClass {
  static const String staticConst = 'Hello, Flutter';
}

void main() {
  print(MyClass.staticConst); // 输出: Hello, Flutter
}

访问静态成员

静态成员可以通过类名直接访问,而无需创建类的实例。

void main() {
  print(MyClass.staticField); // 访问静态字段
  MyClass.staticMethod(); // 调用静态方法
}

示例:静态成员的应用

静态成员通常用于实现类级别的功能或共享数据。例如,你可以使用静态字段和方法来实现计数器或单例模式。

计数器示例

class Counter {
  static int _count = 0;
  
  static void increment() {
    _count++;
  }
  
  static int get count => _count;
}

void main() {
  Counter.increment();
  print(Counter.count); // 输出: 1
  
  Counter.increment();
  print(Counter.count); // 输出: 2
}

单例模式

class Singleton {
  static final Singleton _instance = Singleton._internal();
  
  factory Singleton() {
    return _instance;
  }
  
  Singleton._internal();
}

void main() {
  Singleton s1 = Singleton();
  Singleton s2 = Singleton();
  
  print(identical(s1, s2)); // 输出: true
}

在这个单例模式示例中,Singleton 类确保每次请求返回相同的实例。

注意事项

  1. 不能访问实例成员:静态方法不能访问实例字段和方法。
  2. 生命周期:静态成员在应用程序的整个生命周期内存在。
  3. 线程安全:静态成员在多线程环境中需要注意线程安全问题,尤其是在修改静态字段时。

通过使用 static 关键字,可以在 Flutter 和 Dart 中创建类级别的字段、方法和常量,从而实现共享数据和类级别的功能。

var 、object 、dynimic

在 Dart 中,varObjectdynamic 是三种不同的类型声明方式或类型,它们在不同的场景下有不同的用法和行为。下面详细解释它们的区别和用法:

var

  • 类型推断var 是一个声明变量时使用的关键字,Dart 编译器会根据变量的初始值自动推断变量的类型。
  • 静态类型:一旦推断出类型,变量的类型就不能再改变。
dart
复制代码
void main() {
  var name = "Alice"; // name 被推断为 String 类型
  // name = 123; // 错误: 不能将 int 类型赋值给 String 类型的变量
}

Object

  • 顶层类型Object 是 Dart 所有类的基类,是所有非空类型的超类型。任何类型的对象都可以赋值给 Object 类型的变量。
  • 静态类型检查:虽然 Object 类型的变量可以持有任何类型的对象,但在使用这些对象的成员时需要进行类型转换。
dart
复制代码
void main() {
  Object anything = "Hello";
  print(anything); // 输出: Hello

  anything = 123;
  print(anything); // 输出: 123
  
  // 需要类型转换
  if (anything is int) {
    print(anything + 1); // 输出: 124
  }
}

dynamic

  • 动态类型dynamic 是 Dart 中的一个特殊类型,可以持有任何类型的对象,并且在运行时可以改变其类型。
  • 跳过静态类型检查:使用 dynamic 类型的变量时,Dart 编译器不会进行静态类型检查,因此可以调用任何方法或访问任何成员,但这也意味着更容易出现运行时错误。
dart
复制代码
void main() {
  dynamic something = "Hello";
  print(something); // 输出: Hello

  something = 123;
  print(something); // 输出: 123

  print(something + 1); // 输出: 124

  something = true;
  print(something); // 输出: true
}

比较

  • var:使用 var 声明变量时,编译器会根据初始值推断变量的类型,一旦确定类型,变量的类型不能再改变。这提供了类型安全,但需要在声明时初始化变量。
  • ObjectObject 是所有类的基类,可以持有任何类型的对象,提供了一种类型安全的方式来持有任何类型的值,但在使用时需要进行类型转换。
  • dynamicdynamic 可以持有任何类型的对象,允许在运行时改变其类型,没有静态类型检查。这提供了极大的灵活性,但容易引发运行时错误。

使用场景

  • var:在大多数情况下,推荐使用 var,因为它提供了类型安全,同时避免了显式类型声明的冗长。
  • Object:适用于需要持有不同类型对象但不希望跳过静态类型检查的场景。
  • dynamic:适用于需要极大灵活性且无法确定类型的场景,但要谨慎使用,以避免运行时错误。

var 、final 、 const

  • var: 可以被赋值多次
  • final: 只可以被赋值一次。但是是运行时的。被使用到的时候才会进行初始化, 如果只是被定义, 而没有被使用到, 那么这个变量一直没有被初始化 (可以理解为'懒加载')
  • const: 只可以被赋值一次。一经定义就会在编译期间对其进行初始化。一般用于记录一些常量,例如宏

级联运算符 (..?..)

可以让你在同一个对象上连续调用多个对象的变量方法

var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

等价于
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

//?.. 代表这个对象有可能为空
querySelector('#confirm') // Get an object.
  ?..text = 'Confirm' //?.. 代表这个对象有可能为空
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

Dart作用域

Dart 没有publicprivite等关键字,默认就是公开的,私有变量使用 _ 开头。

Dart泛型

为 类 , 接口 , 方法 提供复用性 , 支持类型不确定的数据类型 ;

泛型类

  • 定义泛型类,使得类的成员可以使用不同类型。
class Box<T> {
  T content;

  Box(this.content);

  void display() {
    print(content);
  }
}

void main() {
  var intBox = Box<int>(123);
  intBox.display(); // 输出: 123

  var stringBox = Box<String>("Hello");
  stringBox.display(); // 输出: Hello
}

泛型方法

你也可以在方法中使用泛型。

T first<T>(List<T> items) {
  return items[0];
}

void main() {
  var numbers = [1, 2, 3];
  print(first(numbers)); // 输出: 1

  var words = ["Hello", "World"];
  print(first(words)); // 输出: Hello
}

在这个例子中,first 方法是一个泛型方法,它可以接受任何类型的 List 并返回该 List 的第一个元素。

泛型约束

有时你可能希望限制泛型参数的类型,这时可以使用泛型约束。

class Box<T extends num> {
  T content;

  Box(this.content);

  void display() {
    print(content);
  }
}

void main() {
  var intBox = Box<int>(123);
  intBox.display(); // 输出: 123

  // var stringBox = Box<String>("Hello"); // 错误: 类型 'String' 不满足 'num' 的约束
}

在这个例子中,Box 类使用 T extends num 对泛型参数进行约束,意味着 T 必须是 num 类型或其子类型。

泛型集合

Dart 的核心库(如 ListSetMap)都是泛型的。

void main() {
  List<int> numbers = [1, 2, 3];
  print(numbers); // 输出: [1, 2, 3]

  Map<String, int> ages = {
    'Alice': 30,
    'Bob': 25
  };
  print(ages); // 输出: {Alice: 30, Bob: 25}
}

使用泛型集合可以确保集合中的元素类型一致,从而提高代码的类型安全性。

泛型函数

你还可以在函数中使用泛型,以便函数能够处理不同类型的数据。

T add<T extends num>(T a, T b) {
  return a + b;
}

void main() {
  print(add(1, 2)); // 输出: 3
  print(add(1.5, 2.5)); // 输出: 4.0
}

在这个例子中,add 函数使用了泛型参数 T,并且约束 Tnum 类型。这样,add 函数可以处理 intdouble 类型的参数。

Dart自带泛型

  • State 就是泛型类,State 类中要求一个泛型 T , 该泛型类型必须继承 StatefulWidget 类 ;
class _MyHomePageState extends State<MyHomePage> { }

abstract class State<T extends StatefulWidget> extends Diagnosticable { }

结论

泛型使得代码更加灵活和可重用,同时保持了类型安全性。通过使用泛型类、泛型方法和泛型约束,你可以编写适用于各种类型的通用代码,这在处理集合或构建可重用组件时特别有用。泛型是 Dart 中一个重要的特性,掌握它将使你的代码更加高效和健壮。

Dart线程

  • dart是一门单线程的语音
  • dart支持异步(future、async)/多线程编程(isolate)

事件循环 Event loop

Dart中有两种队列:

  • 事件队列(event queue): 包含所有外来事件: I/O、mouse events、 drawing events、timers、isolate之间的信息传递
  • 微任务队列(microtask queue): 表示一个短时间内就会完成的异步任务。它优先级最高> event queue, 只要队列中有任务,就可以一直霸占事件循环, microtask queue添加的任务主要是由 Dart内部产生 在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。

dart/flutter 异步编程

Flutter 和 Dart 中,异步编程是一个重要的概念,用于处理耗时操作(如网络请求、文件读写等)而不阻塞主线程,从而保持应用的流畅性和响应性。Dart 提供了多种工具和关键字来实现异步编程,主要包括 FutureasyncawaitStream

1. Futureasync/await

Future 表示一个异步操作的结果。asyncawait 关键字用于处理异步操作,使代码看起来像是同步执行的。

示例:基本的 Future 使用

Future<String> fetchUserOrder() {
  // 模拟一个耗时的操作,比如网络请求
  return Future.delayed(Duration(seconds: 2), () => 'Cappuccino');
}

void main() {
  print('Fetching user order...');
  fetchUserOrder().then((order) {
    print('User order: $order');
  });
  print('Order fetched');
}

上面fetchUserOrder是个Future,因为没有使用关键字async,所以使用了.then

示例:使用 asyncawait

Future<String> fetchUserOrder() async {
  // 模拟一个耗时的操作,比如网络请求
  await Future.delayed(Duration(seconds: 2));
  return 'Cappuccino';
}

void main() async {
  print('Fetching user order...');
  String order = await fetchUserOrder();
  print('User order: $order');
  print('Order fetched');
}

上面例子中,fetchUserOrder 是一个异步函数,使用 await 关键字等待异步操作完成,然后返回结果。在 main 函数中,也使用 await 等待 fetchUserOrder 的结果。

Future可以搭配FutureBuilder去实现UI

2. StreamStreamBuilder

Stream 用于处理一系列异步数据,比如事件流或数据流。StreamBuilder 是一个用于监听 Stream 并更新 UI 的小部件。

示例:基本的 Stream 使用

Stream<int> generateNumbers() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() {
  Stream<int> numberStream = generateNumbers();
  numberStream.listen((number) {
    print(number);
  });
}

Stream可以搭配 StreamBuilder取实现UI

4. 异步错误处理

在 Dart 中,可以使用 try-catch 块来处理异步操作中的错误。

示例:异步错误处理

Future<String> fetchUserOrder() async {
  await Future.delayed(Duration(seconds: 2));
  throw Exception('Failed to fetch order');
}

void main() async {
  try {
    String order = await fetchUserOrder();
    print('User order: $order');
  } catch (e) {
    print('Error: $e');
  }
}

在这个例子中,如果 fetchUserOrder 抛出异常,catch 块将捕获并处理该异常。

总结

Flutter 和 Dart 提供了强大的异步编程工具,允许开发者处理耗时操作而不阻塞主线程。通过使用 Futureasync/awaitStream 和相应的构建器小部件(如 FutureBuilderStreamBuilder),可以轻松地管理异步操作并更新 UI。理解和掌握这些工具对于编写高效和响应迅速的 Flutter 应用程序至关重要。

异步 Future/async

异步任务更多还的是来自于 事件队列(event queue), Dart专门做了一层封装:Future

  • Future异步任务的执行步骤
  1. 声明一个Future是,Dart会将异步任务的函数执行放入event queue, 然后立即返回,后续的代码继续同步执行

  2. 当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。

Flutter - Dart之异步编程

Stream

Flutter Dart之Stream

Mixin

使用 mixin 的场景

  • 当你希望在多个类中重用相同的方法或属性时。
  • 当你希望在一个类中混入多个功能时,而不想使用复杂的继承结构。

定义和使用 mixin

定义 mixin

一个 mixin 的定义与类的定义非常相似,但使用了 mixin 关键字:

mixin Logger {
  void log(String message) {
    print('Log: $message');
  }
}

/// 在类中使用 mixin
使用 `with` 关键字将 `mixin` 应用于一个类:
class MyClass with Logger {
  void doSomething() {
    log('Doing something');
  }
}

void main() {
  var myObject = MyClass();
  myObject.doSomething(); // 输出: Log: Doing something
}

///多个 mixin
///你可以在一个类中混入多个 `mixin`:     
mixin Printer {
  void printMessage(String message) {
    print('Message: $message');
  }
}

class MyClass with Logger, Printer {
  void doSomething() {
    log('Doing something');
    printMessage('This is a message');
  }
}

void main() {
  var myObject = MyClass();
  myObject.doSomething();
  // 输出:
  // Log: Doing something
  // Message: This is a message
}

mixin 的限制

  • mixin 不能有构造函数。
  • mixin 只能用于类,没有接口的功能。
  • 如果 mixin 需要访问某个类的成员(属性或方法),可以使用 on 关键字限制 mixin 的应用范围。
  • 混入相同方法的多个混入,最终会执行哪一个?:后面的类中的方法将前面的类中相同的方法覆盖

约束 mixin 的使用

你可以限制 mixin 只能应用于特定类型的类:

class Animal {
  String name;
  void eat() {
    print('Eating');
  }
}

///使用on可以限制Swimmer这个mixin只能混入 `Animal` 类或其子类中,否则会报错,
mixin Swimmer on Animal {
  void swim() {
    ///这个方法访问了Animal中的name属性
    print('$name Swimming');
  }
}

class Fish extends Animal with Swimmer {}

void main() {
  var fish = Fish();
  fish.eat();   // 输出: Eating
  fish.swim();  // 输出: Swimming
}

在这个例子中,Swimmer 只能被混入 Animal 类或其子类中,否则会报错。

flutter源代码中的使用mixin的例子

1.flutter AnimationController中就使用TickerProviderStateMixin来控制动画 2.AutomaticKeepAliveClientMixin保持页面状态,确保TabBarViewPageView 等组件切换时,保持其内容不被销毁和重建。 3.WidgetsBindingObserver 用于监听应用程序的生命周期事件、窗口大小变化

abstract关键字

  • abstract 关键字用于定义抽象类和抽象方法。
  • 抽象类不能被实例化,它们主要用于定义接口或基类,以便其他类继承和实现。抽象方法是没有方法体的方法,必须在子类中实现
abstract class PaymentMethod {
  void pay(double amount);
}

class CreditCardPayment extends PaymentMethod {
  @override
  void pay(double amount) {
    print('Paying $amount with credit card.');
  }
}

class PayPalPayment extends PaymentMethod {
  @override
  void pay(double amount) {
    print('Paying $amount with PayPal.');
  }
}

void main() {
  PaymentMethod payment1 = CreditCardPayment();
  payment1.pay(100.0); // 输出: Paying $100.0 with credit card.

  PaymentMethod payment2 = PayPalPayment();
  payment2.pay(200.0); // 输出: Paying $200.0 with PayPal.
}

implements关键字

  • 在 Dart 中,implements 关键字用于实现一个类(通常是接口类)中的所有成员。
  • 使用 implements 关键字的类必须提供所实现接口中所有成员的具体实现。
  • 这与继承(extends)不同,继承会继承父类的实现,而实现接口则需要完全自己实现接口中的所有成员。

implements 关键字示例

定义接口

在 Dart 中,接口是通过抽象类或普通类定义的。接口类可以包含抽象方法(没有方法体)和非抽象方法(有方法体)。任何类都可以作为接口被实现。

abstract class Animal {
  void makeSound();
  void eat() {
    print('Eating');
  }
}

实现接口

使用 implements 关键字来实现接口。实现接口的类必须提供接口中所有成员的具体实现,即使这些成员已经有默认实现。

class Dog implements Animal {
  @override
  void makeSound() {
    print('Bark');
  }

  @override
  void eat() {
    print('Dog is eating');
  }
}

class Cat implements Animal {
  @override
  void makeSound() {
    print('Meow');
  }

  @override
  void eat() {
    print('Cat is eating');
  }
}

void main() {
  Animal dog = Dog();
  dog.makeSound(); // 输出: Bark
  dog.eat();       // 输出: Dog is eating

  Animal cat = Cat();
  cat.makeSound(); // 输出: Meow
  cat.eat();       // 输出: Cat is eating
}

在这个例子中:

  1. Animal 是一个抽象类,包含一个抽象方法 makeSound() 和一个具体方法 eat()
  2. DogCat 类使用 implements 关键字实现了 Animal 接口,因此它们必须提供 makeSound()eat() 方法的具体实现。
  3. 可以创建 DogCat 的实例,并调用它们的方法。

implementsextendswith 的区别

extends

  • 继承一个类,并且获得父类的所有方法和属性的实现。
  • 子类可以重写父类的方法和属性。

with

  • 用于混入一个或多个 mixin,以实现代码重用。
  • mixin 不能有构造函数。

implements

  • 接口实现:通过定义抽象类或普通类作为接口,并使用 implements 关键字实现接口中的所有成员。
  • 强制实现:确保某个类提供特定的方法和属性实现,而不依赖于继承。

总结

在 Dart 中,implements 关键字用于实现一个或多个接口。使用 implements 的类必须提供接口中所有成员的具体实现。这种机制使得代码更为模块化和可维护,适用于需要强制实现某些方法和属性的场景。与 extendswith 关键字相比,implements 更加强调实现接口规范,而不是继承实现或混入功能。

FFI

FFI(Foreign Function Interface)允许你调用 C 语言编写的本地代码。

Futter

三棵树

  • Widget 树:描述 UI 结构和布局,是不可变的。
  • Element 树:管理 Widget 树和 RenderObject 树之间的关系,处理 Widget 的生命周期。
  • RenderObject 树:处理实际的布局和绘制。

Widget的声明生命周期

StatelessWidget

构造函数 -> build()

StatefulWidget

  • didChangeDependencies(): 会在配合使用InheritedWidget(用于共享数据给子Widget)触发
  1. initState()之后调用
  2. 当依赖的InheritedWidget发生变化时,也会调用,并且低调用多次
  • didUpdateWidget(): 当前widget配置有变化时,didUpdateWidget(),并且在调用之后,会调用build()

didUpdateWidget()方法之后会调用build()方法,所以在didUpdateWidget()方法中不要调用setState()

  • deactivate(): 在渲染树中被移除时会先调用deactivate()然后调用dispose()。 eg:组件移除,例如页面销毁的时候会依次执行:deactivate > dispose
  • dispose(): 在 widget 被废弃时被调用。 如果你需要执行一些清理操作(比如:listeners的移除),则重写该方法,并在此之后立即调用 super.dispose()

更多请看 Flutter-widget生命周期

Widgets、RenderObjects 和 Elements

Flutter-可选参数 Key

Flutter 如何与 Android iOS 通信

Flutter 与 Android iOS 原生的通信有以下三种方式

  1. MethodChannel 实现 Flutter 与 原生原生(Android 、iOS)双向通信
  2. BasicMessageChannel 实现 Flutter 与 原生(Android 、iOS)双向通信
  3. EventChannel实现 原生原生(Android 、iOS)向Flutter 发送消息 -->单向

MethodChannel 使用 以iOSOC版为标准

  • flutter端
const K_channel_name = 'com.test.fluttertonative';
//定义一个_channel
MethodChannel _channel = MethodChannel(channel_name)
  ..setMethodCallHandler((call)  { //设置处理原生返回的callback; ..是级联用法
     /// 逻辑处理...
     print('收到原生调用flutter:'+call.method+'参数:'+call.arguments);
     if (call.method == 'getUserInfo') {
        return call.arguments;
     } else {
        return Future.value();
     }
  });
  
//invokeMethod 调用原生 
_channel.invokeMethod('getDeviceInfo');  

//在main文件的main方法中注册channel
void main() {
  runApp(MyApp());
  channel.register();
}
  • OC端
@interface Plugin()
@property (nonatomic, strong) FlutterMethodChannel *methodChannel;

@end

@implementation Plugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    FlutterMethodChannel* channel = [FlutterMethodChannel                                     methodChannelWithName:@"com.test.fluttertonative"                                     binaryMessenger:[registrar messenger]];
    
    Plugin* instance = [[Plugin alloc] init];
    //plugin持有methodChannel 用于native call flutter
    instance.methodChannel = channel;
    //增加native/flutter之间的方法消息delegate
    [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
   if ([call.method isEqualToString:@"getDeviceInfo"]) {
        //拿到设备信息逻辑
        id deviceInfo = ...
        //回调给flutter
        result(deviceInfo);        
   } else if ([call.method isEqualToString:@"callback_getUserInfo"]){
        id userInfo = call.arguments[@"userInfo"]
   } else {
        result(FlutterMethodNotImplemented); 
   }
}

//主动去调用flutter
- (void)nativeCallFutter {
   [self.methodChannel invokeMethod:@"getUserInfo" arguments:nil];
}

//appdelete中注册
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
        
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
  • flutter和原生,都必须先注册channel,然后才能使用。
  • 注册为异步执行,需有一定的时延才能使用调用方法。
  • 所有回调都是异步的

参考 Flutter与原生数据交互MethodChannel

BuildContext 到底是个什么

///BuildContext的定义
abstract class BuildContext {
  /// The current configuration of the [Element] that is this [BuildContext].
  Widget get widget;

  /// The [BuildOwner] for this context. The [BuildOwner] is in charge of
  /// managing the rendering pipeline for this context.
  BuildOwner? get owner;
  ...

///Elements的定义 
abstract class Element extends DiagnosticableTree implements BuildContext {
  Element(Widget widget)
    : _widget = widget {
    if (kFlutterMemoryAllocationsEnabled) {
      FlutterMemoryAllocations.instance.dispatchObjectCreated(
        library: _flutterWidgetsLibrary,
        className: '$Element',
        object: this,
      );
    }
  }
  ...
}  
  • BuildContext是一个抽象类
  • Element implementsBuildContext 这个抽象类
  • 所以BuildContext对象实际上是Element对象。之所以这么设计是为了用于防止开发者直接操作 Element 对象。

什么是状态管理,为什么需要它

Provider:

  • 适用场景: 适用于中小型应用,需要在多个层级共享状态的场景。
  • 特点: 轻量级,使用InheritedWidget来共享状态,支持各种类型的状态,易于上手。
  • 优势: 简单易用,不需要大量的额外代码,具有高性能,适用于简单的状态共享。
  • 劣势: 在大型应用中可能难以管理复杂的状态。

Riverpod: Provider的升级和优化

  • 适用场景: 适用于需要更强大、更简单的状态管理和依赖注入的场景。
  • 特点: 基于Provider的升级版本,提供更简单、更强大的API,支持多种状态管理模式。
  • 优势: 代码清晰,性能高效,支持多种状态管理模式,适用于各种规模的项目。
  • 劣势: 相对较新的库,社区可能还在成长

BLoC: 是一种利用reactive programming方式构建应用的方法,这是一个由流构成的完全异步的世界。

  • 适用场景: 适用于复杂的应用,需要分离业务逻辑和UI的场景。
  • 特点: 通过Streams管理状态和业务逻辑,将界面层与业务逻辑层分开,适合中大型应用。
  • 优势: 适合处理复杂的状态变化和异步操作,便于测试和维护。
  • 劣势: 在简单应用中可能显得过于复杂。

Redux:

  • 适用场景: 适用于需要管理大量复杂状态的应用。
  • 特点: 基于单一状态源和不可变状态,通过Actions和Reducers来管理状态变化。
  • 优势: 严格的状态管理,适用于大型应用,具有强大的开发工具和中间件。
  • 劣势: 在小型应用中可能过于繁琐,学习曲线较陡。

GetX:

  • 适用场景: 适用于快速开发和中小型应用,需要轻量级状态管理和依赖注入的场景。
  • 特点: 简单易用,提供状态管理、依赖注入和路由导航的综合解决方案。
  • 优势: 低学习曲线,高性能,适用于快速迭代的小型项目。
  • 劣势: 对于大型复杂应用,可能需要更复杂的状态管理方案。

MobX:

  • 适用场景: 适用于需要响应式编程和可观察对象的场景。
  • 特点: 通过可观察对象和反应式编程来管理状态,支持多种数据变化方式。
  • 优势: 简化了状态管理,具有响应式编程的特点,易于学习和使用。
  • 劣势: 相对较新的库,可能在一些大型项目中缺乏一些高级功能。

状态分:

  • 局部状态: 比如说一个控件中输入的信息
  • 全局状态: 比如是登陆后从后台请求回来的 userId

当状态越来越多,多个页面共享一个状态时,就需要管理这个状态。

1. setState

setState 是最基本的状态管理方法。它适用于简单的状态更新和小规模的状态管理。

2. InheritedWidget

InheritedWidget 是 Flutter 内置的一个高效的状态管理工具,适用于在 Widget 树中向下传递数据和状态。

3. Provider

Provider 是 Flutter 社区广泛使用的状态管理解决方案,它是一个基于 InheritedWidget 的包装,提供了一种更简洁的方式来进行状态管理。

示例

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(
  ChangeNotifierProvider(
    create: (context) => Counter(),
    child: MyApp(),
  ),
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class Counter with ChangeNotifier {
  int _value = 0;

  int get value => _value;

  void increment() {
    _value++;
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

4. Riverpod

Riverpod 是一个改进的状态管理库,与 Provider 类似,但解决了一些 Provider 的限制。它提供了更好的编译时安全性和测试支持。

示例

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() => runApp(
  ProviderScope(
    child: MyApp(),
  ),
);

final counterProvider = StateNotifierProvider<Counter, int>((ref) {
  return Counter();
});

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class Counter extends StateNotifier<int> {
  Counter() : super(0);

  void increment() {
    state++;
  }
}

class MyHomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final counter = watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Riverpod Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text(
              '$counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: context.read(counterProvider.notifier).increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

5. Bloc/Cubit

Bloc 是一种状态管理模式,主要用于构建可复用、可测试和可维护的应用程序。CubitBloc 的简化版本。

示例

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(
  BlocProvider(
    create: (context) => CounterCubit(),
    child: MyApp(),
  ),
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            BlocBuilder<CounterCubit, int>(
              builder: (context, count) {
                return Text(
                  '$count',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterCubit>().increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

总结

Flutter 提供了多种状态管理解决方案,从最基本的 setState 到高级的 ProviderRiverpodBloc。选择适合你的应用程序需求和复杂度的状态管理方法,对于构建高效和可维护的 Flutter 应用程序至关重要。

常用的状态管理

  • Futter内置支持 setState

最重要的方式 setState,支持规模较小的程序足够了,所有其它方式最终都需要调用 setState。

  • Function callback Dart Function 足够灵活, 单向变更通知,可以和ObserverList结合支持多个订阅者。
typedef FooChanged = void Function(int);

typedef ValueChanged<T> = void Function(T value);
  • pkg: provider
  • pkg: flutter_bloc

BLoC 模式

BLoC是一种利用reactive programming方式构建应用的方法,这是一个由流构成的完全异步的世界。 flutter_bloc使用解析---骚年,你还在手搭bloc吗!

Flutter渲染过程

  1. 构建 Widget 树: 调用每个 Widget 的 build() 方法。
  2. 创建 Element 树: 将 Widget 转换为 Element。
  3. 创建 RenderObject 树: 将 Element 转换为 RenderObject。
  4. 布局: 确定每个 RenderObject 的尺寸和位置。
  5. 绘制: 调用每个 RenderObject 的 paint() 方法。
  6. 合成: 将各个绘制层合成为一个图像。
  7. 显示: 将最终图像显示到屏幕上。

FLutter跨平台原理

1. 单一代码库

Flutter 允许开发者使用单一代码库编写应用程序,然后通过编译生成适用于不同平台的应用。开发者主要使用 Dart 编程语言进行开发,Dart 的 AOT(Ahead Of Time)编译器可以将代码编译成高效的本机代码。

2. Flutter 框架

Flutter 框架提供了丰富的 Widget 库,这些 Widget 是跨平台的,可以在不同平台上具有相同的外观和行为。Flutter 的 Widget 系统是高度可定制的,允许开发者创建复杂的用户界面。

3. Flutter 引擎

Flutter 引擎是用 C++ 编写的,它提供了高性能的底层渲染和操作系统接口。Flutter 引擎包含以下关键组件:

  • Skia 图形库:一个高性能的 2D 图形库,用于绘制 Flutter 的 UI。
  • Dart 虚拟机:支持 Dart 语言的 JIT(Just In Time)和 AOT 编译。
  • 平台通道:用于 Dart 代码与本机代码之间的通信。

4. 渲染引擎 (Skia)

Flutter 使用 Skia 作为其图形渲染引擎。Skia 是一个开源的 2D 图形库,被许多知名应用和系统使用,如 Google Chrome 和 Android。通过 Skia,Flutter 可以实现高性能的图形绘制,无论是在移动设备还是桌面系统上。

5. 平台通道

MethodChannel 平台通道是 Flutter 框架和本机代码之间的通信桥梁。通过平台通道,Flutter 应用可以调用本机 API,例如访问相机、传感器等设备功能。开发者可以使用平台通道在 Dart 代码中编写自定义平台插件,以扩展 Flutter 的功能。

6. 编译和打包

Flutter 提供了工具链,用于将 Dart 代码编译成本机代码。对于移动平台,Flutter 使用 Gradle (Android) 和 Xcode (iOS) 进行编译和打包。对于 Web 平台,Flutter 使用 Dart2js 将 Dart 代码编译成 JavaScript。对于桌面平台,Flutter 使用不同的工具链进行编译,例如使用 CMake 生成本机可执行文件。

7. 热重载和热重启

Flutter 支持热重载(Hot Reload)和热重启(Hot Restart),这使得开发者可以快速地查看代码变更的效果,极大地提高了开发效率。

  • 热重载:只重载更改部分的代码,不重启应用,保持应用状态。
  • 热重启:重启应用,丢失当前状态。

如何统一管理错误页面

Flutter 程序中 Dart 代码运行时意外发生的错误事件。我们可以通过与 Java 类似的 try-catch 机制来捕获它 如果在 Flutter 当中出错的话,那就是一片红。

可以使用 ErrorWidget.builder 来自定义一个 Widget 就 ok 了。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
   //管理错误
   ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) {
    return Scaffold(
    body: Center(
        child: Column(children: [
            Icon( Icons.error, color: Colors.red, size: 100,),
            Text(flutterErrorDetails.exceptionAsString())
        ]),
    ));
   };
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

常用第三方库

  • flutter_screenutil: 尺寸适配

  • intl: 国际化

  • fluro: 路由

  • go_router: 路由跳转

  • flutter_easyloading: loading动画加载

  • connectivity: 网络检测

  • shared_preferences: 本地存储

  • camera: 调用相机

  • image_picker: 系统相册拾取

  • cupertino_icons: iOS风格图标

  • daydart: 日期

  • dio: 网络库

  • retrofit:网络封装

  • dio_cookie_manager: dio之cookie管理

  • cookie_jar: cookie管理

  • webview_flutter: webview

  • json_annotation: json解析 /FlutterJsonBeanFactory json解析

  • logger: 日志管理

  • path: 系统文档路径

  • path_provider: 系统文档路径

  • fluwx: 微信第三方插件

  • url_launcher: 启动URL的Flutter插件,适用于IOS和Android平台。他可以打开网页,发送邮件,还可以拨打电话

  • build_runner:
  • flutter_gen_runner:
  • retrofit_generator: ^7.0.8

性能优化

1.避免不必要的重建

  • 使用 const 构造函数:对于不变的Widget,使用const构造函数,这样Flutter可以在编译时对其进行优化。

2.避免布局过度复杂

3.优化动画

  • 使用 AnimatedBuilder:用于高效重建只需部分更新的动画。

4.减少重绘区域

尽量减少需要重绘的区域,避免整个屏幕的重绘。

  • 使用 RepaintBoundary:将需要频繁重绘的Widget包裹在 RepaintBoundary 中,可以将重绘限制在这个区域内。
    RepaintBoundary(
      child: SomeCustomWidget(),
    );
    

5.对于不立即需要的内容,可以延迟构建和加载。

  • 使用 FutureBuilderStreamBuilder:延迟加载数据。
    FutureBuilder(
      future: some
    

6.对于长列表或网格视图

  • 使用 ListView.builderGridView.builder,这些方法只有在需要时才会构建子项,从而节省内存和提高性能。
  • 缓存列表项:如果列表项复杂且不经常变化,可以缓存列表项。使用 ListView.builderIndexedWidgetBuilder 实现缓存。
final List<Widget> _cachedItems = List.generate(
  items.length,
  (index) => ListTile(
    title: Text(items[index]),
  ),
);

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return _cachedItems[index];
  },
);
  • 使用 AutomaticKeepAliveClientMixinListView 的子项中使用 AutomaticKeepAliveClientMixin 可以保持子项的状态,避免不必要的重建。
class MyListItem extends StatefulWidget {
  final String title;
  MyListItem({required this.title});

  @override
  _MyListItemState createState() => _MyListItemState();
}

class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListTile(
      title: Text(widget.title),
    );
  }

  @override
  bool get wantKeepAlive => true;
}
  • 分页加载大量数据的情况

性能监控

1. Flutter DevTools

Flutter DevTools 是一个强大的调试和性能分析工具,包含了许多有用的功能来帮助开发者监控应用的性能。使用DevTools的各个面板来监控和分析性能。

  • 性能分析器 (Performance) :监控帧率、Jank(卡顿)和时间轴事件。
  • 内存分析器 (Memory) :监控应用的内存使用情况,发现内存泄漏。
  • CPU分析器 (CPU Profiler) :监控CPU使用情况,分析代码执行的性能瓶颈。

2. Dart DevTools

Dart DevTools 是一个独立的工具,主要用于Dart代码的性能分析和调试。它可以与Flutter DevTools结合使用,提供更多的性能监控选项。

3. Flutter Inspector

Flutter Inspector 是一个用于检查和调试Flutter应用布局和渲染的工具。

  • Widget树:查看和导航应用的Widget树。
  • 布局边界:检查Widget的布局边界,识别布局问题。
  • 重绘边界:识别需要重绘的区域,发现性能瓶颈。

4. Timeline

Timeline 是一个详细的时间轴视图,显示应用的帧率和性能事件。它可以帮助开发者发现和分析性能问题,如Jank和长时间运行的任务。

5. Performance Overlay

Performance Overlay 是一个内置的工具,可以直接在应用界面上显示帧率和渲染信息。

  • 启用 Performance Overlay
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          showPerformanceOverlay: true, // 启用Performance Overlay
          home: Scaffold(
            appBar: AppBar(title: Text('Flutter Performance Overlay')),
            body: Center(child: Text('Hello, Flutter!')),
          ),
        );
      }
    }
    

6. 使用 debugPrintprint

虽然这是一个简单的方法,但在调试和性能监控中非常有效。通过在代码中添加 debugPrintprint 语句,可以记录和检查代码执行的时间和性能。

void someFunction() {
  final start = DateTime.now();
  // 需要监控的代码
  final end = DateTime.now();
  debugPrint('someFunction执行时间: ${end.difference(start)}');
}

崩溃白屏监测

1. 使用Flutter框架内置工具

捕获未处理的异常--FlutterError、runZonedGuarded

可以在应用入口处捕获未处理的异常,记录日志并进行适当的处理。

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterError.dumpErrorToConsole(details);
    // 记录异常信息到日志文件或远程服务器
    logError(details);
  };

  runZonedGuarded(() {
    runApp(MyApp());
  }, (error, stackTrace) {
    // 捕获ZonedGuarded区域内的异常
    logError(error, stackTrace);
  });
}

void logError(dynamic error, [dynamic stackTrace]) {
  // 将错误信息记录到日志文件或发送到远程服务器
  print('Caught error: $error');
  if (stackTrace != null) {
    print('Stack trace: $stackTrace');
  }
}

2. 使用第三方崩溃监测服务

Firebase Crashlytics

Firebase Crashlytics是一个强大的实时崩溃报告工具,集成到Flutter应用中可以帮助监测和分析崩溃。

步骤:

  1. 添加Firebase依赖

      dependencies:
      firebase_core: latest_version
      firebase_crashlytics: latest_version
    
  2. 初始化Firebase

    import 'package:firebase_core/firebase_core.dart';
    import 'package:firebase_crashlytics/firebase_crashlytics.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
    
      FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
    
      runZonedGuarded(() {
        runApp(MyApp());
      }, (error, stackTrace) {
        FirebaseCrashlytics.instance.recordError(error, stackTrace);
      });
    }
    
  3. 自定义日志记录

    FirebaseCrashlytics.instance.log('custom log message');
    

Sentry

Sentry是另一个流行的崩溃报告和性能监测工具,可以轻松集成到Flutter应用中。

步骤:

  1. 添加Sentry依赖

    dependencies:
      sentry_flutter: latest_version
    
  2. 初始化Sentry

    import 'package:sentry_flutter/sentry_flutter.dart';
    
    void main() async {
      await SentryFlutter.init(
        (options) {
          options.dsn = 'YOUR_SENTRY_DSN';
        },
        appRunner: () => runApp(MyApp()),
      );
    
      FlutterError.onError = Sentry.captureException;
    
      runZonedGuarded(() {
        runApp(MyApp());
      }, (error, stackTrace) {
        Sentry.captureException(error, stackTrace: stackTrace);
      });
    }
    
  3. 自定义日志记录

    Sentry.captureMessage('custom log message');
    

3. 检测和处理白屏

白屏通常是由于Widget树构建或渲染问题导致的。可以通过以下方法进行监测和处理:

使用 WidgetsBindingObserver

实现 WidgetsBindingObserver 来监听应用状态变化,当应用进入后台或前台时进行适当处理。

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // 应用回到前台,检查并恢复UI状态
      checkAndRecoverUIState();
    }
  }

  void checkAndRecoverUIState() {
    // 检查UI状态并进行恢复
    print('Recovering UI state');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Flutter White Screen Detection')),
        body: Center(child: Text('Hello, Flutter!')),
      ),
    );
  }
}

延迟加载和加载指示器

在加载数据或进行耗时操作时,显示加载指示器以避免白屏。

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    // 模拟耗时操作
    await Future.delayed(Duration(seconds: 2));
    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Loading Example')),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : Center(child: Text('Data Loaded')),
    );
  }
}

总结

通过使用Flutter框架内置工具、第三方崩溃监测服务(如Firebase Crashlytics和Sentry)、实现 WidgetsBindingObserver 和显示加载指示器等方法,可以有效监测和处理Flutter应用中的崩溃和白屏问题,提升应用的稳定性和用户体验。