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 类确保每次请求返回相同的实例。
注意事项
- 不能访问实例成员:静态方法不能访问实例字段和方法。
- 生命周期:静态成员在应用程序的整个生命周期内存在。
- 线程安全:静态成员在多线程环境中需要注意线程安全问题,尤其是在修改静态字段时。
通过使用 static 关键字,可以在 Flutter 和 Dart 中创建类级别的字段、方法和常量,从而实现共享数据和类级别的功能。
var 、object 、dynimic
在 Dart 中,var、Object 和 dynamic 是三种不同的类型声明方式或类型,它们在不同的场景下有不同的用法和行为。下面详细解释它们的区别和用法:
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声明变量时,编译器会根据初始值推断变量的类型,一旦确定类型,变量的类型不能再改变。这提供了类型安全,但需要在声明时初始化变量。Object:Object是所有类的基类,可以持有任何类型的对象,提供了一种类型安全的方式来持有任何类型的值,但在使用时需要进行类型转换。dynamic:dynamic可以持有任何类型的对象,允许在运行时改变其类型,没有静态类型检查。这提供了极大的灵活性,但容易引发运行时错误。
使用场景
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 没有public和privite等关键字,默认就是公开的,私有变量使用 _ 开头。
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 的核心库(如 List、Set 和 Map)都是泛型的。
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,并且约束 T 为 num 类型。这样,add 函数可以处理 int 和 double 类型的参数。
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 提供了多种工具和关键字来实现异步编程,主要包括 Future、async、await 和 Stream。
1. Future 和 async/await
Future 表示一个异步操作的结果。async 和 await 关键字用于处理异步操作,使代码看起来像是同步执行的。
示例:基本的 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
示例:使用 async 和 await
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. Stream 和 StreamBuilder
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 提供了强大的异步编程工具,允许开发者处理耗时操作而不阻塞主线程。通过使用 Future、async/await、Stream 和相应的构建器小部件(如 FutureBuilder 和 StreamBuilder),可以轻松地管理异步操作并更新 UI。理解和掌握这些工具对于编写高效和响应迅速的 Flutter 应用程序至关重要。
异步 Future/async
异步任务更多还的是来自于 事件队列(event queue), Dart专门做了一层封装:Future
- Future异步任务的执行步骤
-
声明一个Future是,Dart会将异步任务的函数执行放入event queue, 然后立即返回,后续的代码继续同步执行
-
当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。
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保持页面状态,确保TabBarView、PageView 等组件切换时,保持其内容不被销毁和重建。
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
}
在这个例子中:
Animal是一个抽象类,包含一个抽象方法makeSound()和一个具体方法eat()。Dog和Cat类使用implements关键字实现了Animal接口,因此它们必须提供makeSound()和eat()方法的具体实现。- 可以创建
Dog和Cat的实例,并调用它们的方法。
implements 与 extends 和 with 的区别
extends
- 继承一个类,并且获得父类的所有方法和属性的实现。
- 子类可以重写父类的方法和属性。
with
- 用于混入一个或多个
mixin,以实现代码重用。 mixin不能有构造函数。
implements
- 接口实现:通过定义抽象类或普通类作为接口,并使用
implements关键字实现接口中的所有成员。 - 强制实现:确保某个类提供特定的方法和属性实现,而不依赖于继承。
总结
在 Dart 中,implements 关键字用于实现一个或多个接口。使用 implements 的类必须提供接口中所有成员的具体实现。这种机制使得代码更为模块化和可维护,适用于需要强制实现某些方法和属性的场景。与 extends 和 with 关键字相比,implements 更加强调实现接口规范,而不是继承实现或混入功能。
FFI
FFI(Foreign Function Interface)允许你调用 C 语言编写的本地代码。
Futter
三棵树
- Widget 树:描述 UI 结构和布局,是不可变的。
- Element 树:管理 Widget 树和 RenderObject 树之间的关系,处理 Widget 的生命周期。
- RenderObject 树:处理实际的布局和绘制。
Widget的声明生命周期
StatelessWidget
构造函数 -> build()
StatefulWidget
didChangeDependencies(): 会在配合使用InheritedWidget(用于共享数据给子Widget)触发
- 在
initState()之后调用 - 当依赖的
InheritedWidget发生变化时,也会调用,并且低调用多次
didUpdateWidget(): 当前widget配置有变化时,didUpdateWidget(),并且在调用之后,会调用build()
didUpdateWidget()方法之后会调用build()方法,所以在didUpdateWidget()方法中不要调用setState()
deactivate(): 在渲染树中被移除时会先调用deactivate()然后调用dispose()。 eg:组件移除,例如页面销毁的时候会依次执行:deactivate > disposedispose(): 在 widget 被废弃时被调用。 如果你需要执行一些清理操作(比如:listeners的移除),则重写该方法,并在此之后立即调用super.dispose()
更多请看 Flutter-widget生命周期
Widgets、RenderObjects 和 Elements
Flutter 如何与 Android iOS 通信
Flutter 与 Android iOS 原生的通信有以下三种方式
MethodChannel实现 Flutter 与 原生原生(Android 、iOS)双向通信BasicMessageChannel实现 Flutter 与 原生(Android 、iOS)双向通信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,然后才能使用。
- 注册为异步执行,需有一定的
时延才能使用调用方法。 - 所有回调都是异步的
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是implements了BuildContext这个抽象类 - 所以
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 是一种状态管理模式,主要用于构建可复用、可测试和可维护的应用程序。Cubit 是 Bloc 的简化版本。
示例
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 到高级的 Provider、Riverpod 和 Bloc。选择适合你的应用程序需求和复杂度的状态管理方法,对于构建高效和可维护的 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渲染过程
- 构建 Widget 树: 调用每个 Widget 的
build()方法。 - 创建 Element 树: 将 Widget 转换为 Element。
- 创建 RenderObject 树: 将 Element 转换为 RenderObject。
- 布局: 确定每个 RenderObject 的尺寸和位置。
- 绘制: 调用每个 RenderObject 的
paint()方法。 - 合成: 将各个绘制层合成为一个图像。
- 显示: 将最终图像显示到屏幕上。
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.对于不立即需要的内容,可以延迟构建和加载。
- 使用
FutureBuilder和StreamBuilder:延迟加载数据。FutureBuilder( future: some
6.对于长列表或网格视图
- 使用
ListView.builder和GridView.builder,这些方法只有在需要时才会构建子项,从而节省内存和提高性能。 - 缓存列表项:如果列表项复杂且不经常变化,可以缓存列表项。使用
ListView.builder和IndexedWidgetBuilder实现缓存。
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];
},
);
- 使用
AutomaticKeepAliveClientMixin在ListView的子项中使用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. 使用 debugPrint 和 print
虽然这是一个简单的方法,但在调试和性能监控中非常有效。通过在代码中添加 debugPrint 或 print 语句,可以记录和检查代码执行的时间和性能。
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应用中可以帮助监测和分析崩溃。
步骤:
-
添加Firebase依赖:
dependencies: firebase_core: latest_version firebase_crashlytics: latest_version -
初始化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); }); } -
自定义日志记录:
FirebaseCrashlytics.instance.log('custom log message');
Sentry
Sentry是另一个流行的崩溃报告和性能监测工具,可以轻松集成到Flutter应用中。
步骤:
-
添加Sentry依赖:
dependencies: sentry_flutter: latest_version -
初始化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); }); } -
自定义日志记录:
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应用中的崩溃和白屏问题,提升应用的稳定性和用户体验。