Flutter入門(mén)1——Dart語(yǔ)言基礎(chǔ)

Dart語(yǔ)言的某些特性可能會(huì)讓習(xí)慣使用Java或者Kotlin的開(kāi)發(fā)者看不懂或者感到疑惑翩肌,本文主要介紹Dart語(yǔ)言的一些和Java以及Kotlin不太一樣的地方,旨在讓Android開(kāi)發(fā)者可以快速掌握Dart語(yǔ)言缭乘。轉(zhuǎn)載請(qǐng)注明來(lái)源申國(guó)駿

1 變量&數(shù)據(jù)類(lèi)型

1.1 初始化

變量定義可以用 var value = 18;或者直接聲明類(lèi)型int value = 18;肋乍,大部分情況下使用var定義變量蒲祈,僅當(dāng)不太能通過(guò)代碼字面意義判斷類(lèi)型時(shí)谷婆,可以直接聲明類(lèi)型,例如String people = getPeople(true, 100);

對(duì)于非空類(lèi)型径玖,如果不能馬上進(jìn)行初始化痴脾,可以使用late關(guān)鍵字,例如:

// 這里使用List<String>不是用var是因?yàn)楸3址强招?// 相當(dāng)于Kotlin里面的 lateinit var names: List<String>
late List<String> names;
if (iWantFriends())
    names = friends.getNames();
else
    names = haters.getNames();

注意非空類(lèi)型在初始化之前訪(fǎng)問(wèn)會(huì)編譯出錯(cuò)梳星。

1.2 Final

finale關(guān)鍵字表示不可修改赞赖,可以不聲明類(lèi)型final name = "Alberto";

1.3 類(lèi)型轉(zhuǎn)換

// 1. If the string is not a number, val is null 
double? val = double.tryParse("12@.3x_"); // null 
double? val = double.tryParse("120.343"); // 120.343
// 2. The onError callback is called when parsing fails 
var a = int.parse("1_6", onError: (value) => 0); // 0 
var a = int.parse("16", onError: (value) => 0); // 16

1.4 String

// Very useful for SQL queries, for example
// 這種情況換行以及每行前面的空格不會(huì)刪除
var query = """
  SELECT name, surname, age
  FROM people
  WHERE age >= 18
  ORDER BY name DESC
  """;


// 這種情況不會(huì)有換行
var s = 'I am going to the'
                'second line';

1.5 空安全

在Dart中可以使用??來(lái)進(jìn)行空安全賦值,例如:

String? status; // This is null 
String isAlive = status ?? "RIP";

1.6 注釋

Dart中可以使用以下三種注釋?zhuān)?/p>

// for signle line comments

/*
 * 
 for multi-line comments
 */

/// for ducumentation comments [b] xxx
void a (int b) {
}

2 方法函數(shù)

2.1 具名參數(shù)(Named Parameters)

表示調(diào)用的時(shí)候必須聲明參數(shù)的名稱(chēng)冤灾,參數(shù)使用括號(hào){}包裹前域,例如方法:

void test({int a = 0, required int b}) {
  print("$a");
  print("$b");
}

調(diào)用的時(shí)候需要寫(xiě)明參數(shù)名稱(chēng),required表示這個(gè)參數(shù)必須填寫(xiě)

void main() {
  test(a: 5, b: 3); // Ok
  test(b: 3); // Ok
  test(a: 5); // Compilation error, 'b' is required
  test(5, 3); // Compilation error, name is required
}

2.2 位置參數(shù)(Positional parameters)

表示調(diào)用的時(shí)候這些參數(shù)是可選的韵吨。使用可選參數(shù)的時(shí)候匿垄,不能寫(xiě)參數(shù)的名稱(chēng),非空參數(shù)必須有默認(rèn)值归粉,例如:

// void test([int a = 0, int b])  Compilation error The parameter 'b' can't have a value of 'null' because of its type, but the implicit default value is 'null'
void test([int a = 0, int? b]) {
  print("$a");
  print("$b");
}

調(diào)用的時(shí)候不能寫(xiě)參數(shù)名

void main() {
  test(a: 5, b: 3); // Compilation error
  test(b: 3); // Compilation error
  test(a: 5); // Compilation error
  test(5, 3); // Ok 
  test();     // Ok
  test(5);    // Ok
}

目前暫時(shí)不允許方法的參數(shù)既有具名參數(shù)又有位置參數(shù)椿疗,例如以下這樣會(huì)編譯出錯(cuò):

void test({int a = 0, int b = 0}, [int c = 0, int d?]) {  // compile error
}

2.3 匿名方法

一行寫(xiě)法:

final isEven = (int value) => value % 2 == 0;

多行寫(xiě)法:

final anon = (String nickname) {
  var myName = "Alberto";
  myName += nickname;
  return myName;
};

2.4 擴(kuò)展方法

與Kotlin擴(kuò)展方法類(lèi)似,語(yǔ)法稍微有點(diǎn)不同

extension FractionExt on String {
  bool isFraction() => ...
    
  // Converts a string into a fraction
  Fraction toFraction() => Fraction.fromString(this);
}

void main() {
  var str = "2/5";
  if (str.isFraction()) {
    final frac = str.toFraction();
  }
}

3 類(lèi)

Dart中不允許有類(lèi)方法重載糠悼,方法簽名不一樣也不行

class Example { 
  void test(int a) {}
  // Doesn't compile; you have to use different names
  void test(double x, double y) {}
}

3.1 Cascade 操作符

class Test {
  String val1 = "One";
  String val2 = "Two";
  int randomNumber() {
    print("Random!");
    return Random().nextInt(10);
  }
}

例如如果想多次調(diào)用randomNumber()可以這用寫(xiě)

Test()..randomNumber()
  ..randomNumber()
  ..randomNumber();

3.2 Library引入

Dart里面通過(guò) import 'package:path/to/file/library.dart';來(lái)引入一個(gè)文件届榄,當(dāng)兩個(gè)文件的類(lèi)名有沖突時(shí),可以采取兩種方式解決倔喂,一種是設(shè)置別名铝条,例如:

// Contains a class called 'MyClass'
import 'package:libraryOne.dart';
// Also contains a class called 'MyClass' 
import 'package:libraryTwo.dart' as second;

void main() {
  // Uses MyClass from libraryOne 
  var one = MyClass();
  //Uses MyClass from libraryTwo. 
  var two = second.MyClass();
}

另一種方法是選擇展示或隱藏

//Imports only MyClass and discards all the rest.
import 'package:libraryOne.dart' show MyClass; 
//Import severything except MyClass.
import 'package:libraryTwo.dart' hide MyClass; 

Dart for web中還支持library的延遲加載,library只會(huì)在需要的時(shí)候才會(huì)加載進(jìn)來(lái):

import 'package:greetings/hello.dart' deferred as hello;

// 使用時(shí):
Future<void> greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

Flutter也支持動(dòng)態(tài)加載席噩,具體見(jiàn):https://docs.flutter.dev/perf/deferred-components

3.3 可見(jiàn)性

Dart中沒(méi)有public班缰、privateprotected等聲明可見(jiàn)性的關(guān)鍵字班挖,只能通過(guò)下劃線(xiàn)_來(lái)表示變量或者方法私有(同一個(gè)文件里面加了下劃線(xiàn)也能訪(fǎng)問(wèn)鲁捏,私有僅相對(duì)于其他文件而言)。為什么這樣設(shè)計(jì)萧芙?跟Dart的dynamic類(lèi)型有關(guān)给梅,感興趣看看:https://github.com/dart-lang/sdk/issues/33383#issuecomment-396168900

 // === File: test.dart ===
class Test {
  String nickname = "";
  String _realName = "";
}

// === File: main.dart ===
import 'package:test.dart';

void main() {
  final obj = Test();
  
  // OK
  var name = obj.nickname; 
  // ERROR, doesn't compile 
  var real = obj._realName;
}

3.4 構(gòu)造函數(shù)

因?yàn)闃?gòu)造函數(shù)的執(zhí)行在變量初始化之后,因此類(lèi)里面的變量如果希望通過(guò)構(gòu)造函數(shù)來(lái)初始化并且希望是非空的話(huà)双揪,需要使用late來(lái)聲明:

class Fraction {
  late int _numerator;
  late int _denominator;
  
  Fraction(int numerator, int denominator) {
    _numerator = numerator;
    _denominator = denominator;\
  }
}

為了更好的可讀性动羽,還有個(gè)語(yǔ)法糖(優(yōu)先考慮):

class Fraction {
  int _numerator;
  int _denominator;
  
  Fraction(this._numerator, this._denominator);
}

如果不希望暴露內(nèi)部的私有變量名稱(chēng),可以使用Initializer list (優(yōu)先考慮)渔期,例如:

class Test {
  int _secret;
  double _superSecret;
  
  Test(int age, double wallet) : 
    _secret = age,
    _superSecret = wallet;
}

3.4.1 具名構(gòu)造函數(shù)

因?yàn)镈art沒(méi)有方法重載运吓,因此如果希望類(lèi)有多個(gè)構(gòu)造函數(shù)的話(huà)渴邦,需要使用具名構(gòu)造函數(shù),例如:

class Fraction {
  int _numerator;
  int _denominator;
  
  Fraction(this._numerator, this._denominator);
  
  // denominator cannot be 0 because 0/0 is not defined!
  Fraction.zero() :
    _numerator = 0,
    _denominator = 1;
}

void main() {
  // "Traditional" initialization
  final fraction1 = Fraction(0, 1);
  // Same thing but with a named constructor
  final fraction2 = Fraction.zero();
}

在具名構(gòu)造函數(shù)聲明中重定向到默認(rèn)構(gòu)造函數(shù)或者其他具名函數(shù):

Fraction(this._numerator, this._denominator); 
// Represents '1/2'
Fraction.oneHalf() : this(1, 2);
// Represents integers, like '3' which is '3/1' 
Fraction.whole(int val) : this(val, 1);
// Ok
Fraction.three() : this.whole(3)

3.4.2 工廠模式構(gòu)造函數(shù)

當(dāng)需要用到單例或者根據(jù)不同條件構(gòu)造子類(lèi)的時(shí)候拘哨,可以使用factory關(guān)鍵字修飾構(gòu)造函數(shù)谋梭。factory構(gòu)造函數(shù)與普通構(gòu)造函數(shù)的區(qū)別的是需要有return對(duì)象,并且可以根據(jù)不同的參數(shù)返回不同的對(duì)象倦青。

abstract class Animal {
  
  factory Animal(String name) {
    // 根據(jù)不同類(lèi)型生成不同子類(lèi)瓮床,工廠構(gòu)造函數(shù)可以使用return
    if (name == 'dog') return Dog(name);
    if (name == 'cat') return Cat(name);
    throw 'type error';
  }
  
  void talk();
}

class Dog implements Animal {
  String _name;
  // factory不會(huì)默認(rèn)生成單例,需要使用static等手段實(shí)現(xiàn)
  static int count = 0;
  
  factory Dog(String name) {
    // 可以調(diào)用普通構(gòu)造函數(shù)
    return Dog._default(name);
  }
  
  Dog._default(this._name);
  
  @override
  void talk() {
    count++;
    print('cout: $count,name: $_name');
  }
}

class Cat implements Animal {
  String name;
  // 不使用factory構(gòu)造函數(shù)产镐,同樣可以使用static實(shí)現(xiàn)cache功能
  static int count = 0;
  
  Cat(this.name);

  @override
  void talk() {
    count++;
    print('cout: $count,name: $name');
  }
}

void main() {
  // 調(diào)用的時(shí)候與普通構(gòu)造函數(shù)沒(méi)有區(qū)別
  Animal('dog').talk();
  Animal('dog').talk();
  Animal('cat').talk();
  Animal('cat').talk();
}

/*
 * 輸出:
cout: 1,name: dog
cout: 2,name: dog
cout: 1,name: cat
cout: 2,name: cat
 */

3.5 Getters & Setters

class Fraction {
  int _numerator;
  int _denominator;
  Fraction(this._numerator, this._denominator);
  
  // getters
  int get numerator => _numerator;
  int get denominator => _denominator;
  
  // setter
  set denominator(int value) {
    if (value == 0) {
      // Or better, throw an exception...
      _denominator = 1;
    } else {
      _denominator = value;
    }
  }
}

3.6 Callable類(lèi)

在類(lèi)里面隘庄,如果方法名字是call()的話(huà),那么這個(gè)類(lèi)被稱(chēng)為Callable類(lèi)癣亚。Callable類(lèi)對(duì)象可以像方法一樣調(diào)用:

// Create this inside 'my_test.dart' for example
class _Test {
  const _Test();
  void call(String something) {
    print(something);
  }
}

const test = _Test();

// Somewhere else, for example in main.dart
import 'package:myapp/my_test.dart';
void main() {
  test("Hello");
}

3.7 操作符重載

對(duì)比兩個(gè)對(duì)象是否相等丑掺,正常情況下是對(duì)比是否引用了同一個(gè)對(duì)象,假設(shè)我們希望根據(jù)類(lèi)中的某個(gè)字段判斷對(duì)象相等述雾,需要重載==號(hào)街州,例如:

class Example {
  int a;
  Example(this.a);
  
  @override
  bool operator== (Object other) {
    // 1. The function identical() is provided by the Dart code API 
    //    and checks if two objects have the same reference.
    if (identical(this, other))
      return true;

    // 2.
    if (other is Example) {
      final example = other;
      // 3.
      return runtimeType == example.runtimeType &&
        a == example.a;
    } else {
      return false;
    } 
  }
  // 4.
  @override
  int get hashCode => a.hashCode;
}
void main() {
    final ex1 = Example(2); 
  final ex2 = Example(2); 
  print(ex1 == ex2); //true
}

當(dāng)類(lèi)里面有很多變量的時(shí)候,手動(dòng)實(shí)現(xiàn)operator==以及hashCode比較復(fù)雜绰咽,我們可以借助于Equatable這個(gè)庫(kù)來(lái)實(shí)現(xiàn)菇肃,例如:

class Test extends Equatable {
  final int a;
  final int b;
  final String c;
  Test(this.a, this.b, this.c);
  
  @override
  List<Object> get props => [a, b, c];
}

或者使用with來(lái)引入EquatableMixin

 class Test extends SomeClass with EquatableMixin {
   final int a;
   final int b;
   final String c;
   Test(this.a, this.b, this.c);
   
   @override
   List<Object> get props => [a, b, c];
 }

4. 繼承

與Kotlin中類(lèi)和方法默認(rèn)都是final不同地粪,Dart中方法和類(lèi)默認(rèn)都是virtual取募,可以繼承和覆蓋的。而且Dart中暫時(shí)沒(méi)有辦法禁止類(lèi)被繼承蟆技。

4.1 covariant

當(dāng)繼承父類(lèi)的時(shí)候玩敏,默認(rèn)情況下覆蓋父類(lèi)方法需要使用父類(lèi)方法一樣的參數(shù),在某些特殊情況下质礼,子類(lèi)覆蓋方法如果希望方法參數(shù)也使用父類(lèi)方法參數(shù)的子類(lèi)旺聚,可以使用covariant關(guān)鍵字,例如:

abstract class Fruit {}
class Apple extends Fruit {}
class Grape extends Fruit {}
class Banana extends Fruit {}

abstract class Mammal {
  void eat(Fruit f);
}

class Human extends Mammal {
  // Ok
  void eat(Fruit f) => print("Fruit");
}

class Monkey extends Mammal { 
  // Error
  void eat(Banana f) => print("Banana");
  // Ok
  void eat(covariant Banana f) => print("Banana");
}

或者直接在父類(lèi)方法聲明:

abstract class Mammal {
  void eat(covariant Fruit f);
}

class Human extends Mammal {
  // Ok
  void eat(Fruit f) => print("Fruit");
}
class Monkey extends Mammal {
  // Ok
  void eat(Banana f) => print("Banana");
}

4.2 接口

在 Dart中沒(méi)有interface眶蕉,創(chuàng)建接口使用abstract class砰粹,例如:

abstract class MyInterface {
   void methodOne();
   void methodTwo();
}

class Example implements MyInterface {
  @override
  void methodOne() {}
  @override
  void methodTwo() {}
}

一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,不過(guò)只能繼承一個(gè)父類(lèi)造挽。

Dart中不支持接口有默認(rèn)實(shí)現(xiàn)碱璃,如果使用implements,則必須實(shí)現(xiàn)所有接口方法饭入,即使在接口中方法有實(shí)現(xiàn)嵌器,例如:

abstract class MyInterface {
   void methodOne();
   void methodTwo() {
     print('MyInterface');
   }
}

// Error Missing concrete implementation of 'MyInterface.methodTwo'
class Example implements MyInterface {
  @override
  void methodOne() {
    // Error The method 'methodTwo' is always abstract in the supertype.
    super.methodTwo();
  }
}

void main() {
  Example()..methodOne()
    ..methodTwo();
}

如果希望復(fù)用部分父類(lèi)的實(shí)現(xiàn),只能用extends谐丢。

4.3 Mixins

Mixins表示一個(gè)沒(méi)有構(gòu)造函數(shù)的類(lèi)爽航,這個(gè)類(lèi)的方法可以組合到其他類(lèi)中實(shí)現(xiàn)代碼復(fù)用蚓让,例如:

mixin Walking {
  void walk() => print("Walking");
}

class Human with Walking {
}

void main() {
  final me = Human();
  // prints "Walking"
  me.walk();
}

如果父類(lèi)通過(guò)with復(fù)用了Mixins類(lèi),則子類(lèi)繼承父類(lèi)后同樣擁有Mixins類(lèi)讥珍,例如:

mixin Walking {
  void walk() {}
}
mixin Breathing {
  void breath() {}
}
mixin Coding {
  void code() {}
}

// Human only has walk()
class Human with Walking {}

// Developer has walk() inherited from Human and also // breath() and code() from the two mixins
class Developer extends Human with Breathing, Coding {}

使用on關(guān)鍵字來(lái)限制使用Mixins的類(lèi)只能是某種類(lèi)型的子類(lèi)历极,例如:

// Constrain 'Coding' so that it can be attached only to
// subtypes of 'Human'
mixin Coding on Human {
  void code() {}
}

// All good
class Human {}
class Developer extends Human with Coding {}

// NO, 'Coding' can be used only on subclasses
class Human with Coding {}

// NO, 'Fish' is not a subclass of 'Human' so 
// you cannot attach the 'Coding' mixin
class Fish with Coding {}

Mixins不是一種繼承關(guān)系,沒(méi)有層級(jí)結(jié)構(gòu)衷佃,使用Mixins的類(lèi)不需要通過(guò)super調(diào)用Mixins里面的變量和方法执解。Mixins是一種組合的思想,類(lèi)似于把一部分通用的變量和方法放到一個(gè)公共的區(qū)域纲酗。

5. 異常處理

異常處理與Kotlin類(lèi)似衰腌。捕捉特定異常使用關(guān)鍵字on,例如:

void main() {
  try {
    final f = Fraction(1, 0);
  } on IntegerDivisionByZeroException {
    print("Division by zero!");
  } on FormatException {
    print("Invalid format!");
  } on Exception catch (e) {
    // You arrive here if the thrown exception is neither 
    // IntegerDivisionByZeroException or FormatException 
    print("General exception: $e");
  } catch(e) {
    print("General error: $e");
  } finally {
    print("Always here");
  }
}

5.1 rethrow

如果希望在try中重新拋出一樣的異常觅赊,使用rethrow關(guān)鍵字右蕊,例如:

try {
    try {
    throw FormatException();
  } on Exception catch (e) {
    print("$e");
    // same as `throw e;`
    rethrow; 
  }
} catch (e2) {
  print("$e2");
}

6. Collections操作

6.1 List

因?yàn)镈art中所有列表都是List對(duì)象,因此可以添加元素吮螺,也可以使用...操作符來(lái)添加另一個(gè)列表饶囚,例如:

void main() {
  List<int>? list1 = [1, 2, 3];
  list1?.add(4);
  var list3 = [-2, -1, 0, ...?list1]; // All good
  print('$list3');
}

列表初始化也可以存在if或者for表達(dá)式,例如:

const hasCoffee = true;

final jobs = const [
  "Welder",
  "Race driver",
  "Journalist",
  if (hasCoffee) "Developer"
];

final numbers = [
  0, 1, 2,
  for(var i = 3; i < 100; ++i) i
];

除了直接定義列表鸠补,還有其他列表的構(gòu)造函數(shù)萝风,例如:

// Now example has this content: [1, 1, 1, 1, 1]
final example = List<int>.filled(5, 1, growable: true); 

var example = List<int>.unmodifiable([1,2,3]);
// same as `var example = const <int>[1, 2, 3];`
example.add(4); // Runtime error

// Now example has this content: [0, 1, 4, 9, 16]
var example = List<int>.generate(5, (int i) => i*i);

6.2 Set

有幾種方式聲明set變量:

// 1. Direct type annotation
Set<int> example = {};
// 2. Type inference with diamonds 
final example = <int>{};
// 3. Initialize with objects
final example = {1, 2, 3};
// 4. This is a Map, not a set!!
final example = {};

6.3 Map

定義一個(gè)map

 final example = <int, String> {
   0: "A",
   1: "B",
 };

添加元素:

// The key '0' is already present, "C" not added
example.putIfAbsent(0, () => "C");
// The key '6' is not present, "C" successfully added 
example.putIfAbsent(6, () => "C");

// "A" has '0' as key and it's replaced with "C". 
// Now the map contains {0: "C", 1: "B"} 
example[0] = "C";
// The key '6' is not present, "C" gets added 
example[6] = "C";

6.4 Transform方法

void main() {
    // Generate a list of 20 items using a factory 
  final list = List<int>.generate(20, (i) => i);
  // Return a new list of even numbers
  final List<String> other = list
    .where((int value) => value % 2 == 0) // Intermediate
    .map((int value) => value.toString()) // Intermediate
    .toList(); // Terminal
}

Intermediates

  • where():相當(dāng)于Kotlin中的filter,用于過(guò)濾
  • map():1對(duì)1轉(zhuǎn)換成另外元素
  • skip():跳過(guò)前n個(gè)元素
  • followedBy():拼接元素

Terminals

  • toList()/toSet()/toMap():聚合元素組成新的Collections

  • every():判斷是否所有元素都滿(mǎn)足某個(gè)條件

  • contains():是否包含某個(gè)元素

  • reduce():將所有元素歸集到一個(gè)元素

  • fold():與reduce()類(lèi)似紫岩,擁有初始值规惰,且最后歸集元素類(lèi)型可以不一樣,例如:

    final list = ['hello', 'Dart', '!'];
    final value = list.fold(0, (int count, String item) => count + item.length);
    print(value); // 10
    

7. 異步

所有的Dart代碼都是運(yùn)行在isolate中的泉蝌,每個(gè)isolate只有一個(gè)線(xiàn)程歇万,isolate之間不會(huì)共享內(nèi)存。

在單個(gè)isolate中勋陪,如果因?yàn)橄到y(tǒng)I/O贪磺、或者等待HTTP請(qǐng)求、或者與瀏覽器通信诅愚、或者等待另一個(gè)isolate處理返回寒锚、或者等待timer計(jì)時(shí)觸發(fā)等,需要等待在非當(dāng)前isloate處理的事情(這些事情要不就在不同的線(xiàn)程中運(yùn)行违孝,要不就是由操作系統(tǒng)或者Dart運(yùn)行時(shí)處理刹前,允許與當(dāng)前的isolate同時(shí)執(zhí)行),可以使用Futrue或者Stream進(jìn)行異步操作等浊。

一個(gè)例子是讀取文件的內(nèi)容:

[圖片上傳失敗...(image-a62817-1662032328576)]

如果是進(jìn)行復(fù)雜耗CPU的計(jì)算任務(wù)腮郊,需要在獨(dú)立的isolate中執(zhí)行。

7.1 Futures

對(duì)于耗時(shí)I/0等任務(wù)筹燕,可以使用Future類(lèi)來(lái)避免主線(xiàn)程阻塞轧飞,例如:

Future<int> processData(int param1, double param2) {
    // function that takes 4 or 5 seconds to execute...
  final res = httpGetRequest(value);
  return Future<int>.value(res);
}

void main() {
  final process = processData(1, 2.5);
  process.then((data) => print("result = $data"))
    .catchError((e) => print(e.message));
  print("Future is bright");
}

// output:
// Future is bright
// result = 10; // <-- printed after 4 or 5 seconds

如果等待多個(gè)Futures衅鹿,可以使用wait()方法:

Future<int> one = exampleOne();
Future<int> two = exampleTwo();
Future<int> three = exampleThree();
Future.wait<int>([
  one,
  two,
  three
]).then(...).catchError(...);

Future類(lèi)的一些具名構(gòu)造函數(shù):

  • Future<T>.delayed():延遲一段時(shí)間執(zhí)行
  • Future<T>.error():一般用于結(jié)束表示異步方法錯(cuò)誤結(jié)束
  • Future<T>.value():包裹異步方法返回結(jié)果

7.1.1 async & await

asyncawait是簡(jiǎn)化Future寫(xiě)法的語(yǔ)法糖,可以解決多個(gè)Futture嵌套等待的回調(diào)地獄过咬,與Kotlin協(xié)程寫(xiě)法有點(diǎn)像大渤,以下兩段代碼是一樣效果:

void main() {
  final process = processData(1, 2.5);
  process.then((data) => print("result = $data"));
}
void main() async {
  final data = await processData(1, 2.5);
  print("result = $data")
}

異步方法也可以使用async返回結(jié)果,例如以下兩段代碼等價(jià):

// Use the named constructor
Future<int> example() => Future<int>.value(3);
// Use async and the compiler wraps the value in a Future
Future<int> example() async => 3;

7.2 Streams

Stream類(lèi)型也是表示未來(lái)返回的結(jié)果掸绞,與Future不同的是Stream表示的不是單一個(gè)結(jié)果泵三,而是一連串的結(jié)果。

stream.png

Stream中有幾個(gè)概念:

  • Generator:負(fù)責(zé)生產(chǎn)數(shù)據(jù)并通過(guò)stream發(fā)送
  • Stream:生產(chǎn)數(shù)據(jù)放置的地方衔掸,可以通過(guò)對(duì)Stream進(jìn)行訂閱獲取Generator生產(chǎn)的數(shù)據(jù)
  • Subscriber:訂閱者烫幕,通過(guò)訂閱監(jiān)聽(tīng)獲取數(shù)據(jù)

7.2.1 Streams & Generator

Stream<int> randomNumbers() async* {                        // 1. 
  final random = Random();
  for(var i = 0; i < 100; ++i) {                                // 2. 
    await Future.delayed(Duration(seconds: 1)); // 3.
    yield random.nextInt(50) + 1;                           // 4.
    }
}                                                                                   // 5.
  1. 返回類(lèi)型是Stream<int>async*表示可以使用yield來(lái)發(fā)送數(shù)據(jù)
  2. 循環(huán)產(chǎn)生100個(gè)隨機(jī)數(shù)
  3. await等待Future延遲1秒
  4. 使用yield來(lái)發(fā)送數(shù)據(jù)
  5. 如果方法被async*修飾的話(huà)敞映,方法不能有return返回较曼,因?yàn)閿?shù)據(jù)是通過(guò)yield發(fā)送的

Stream的產(chǎn)生是on demand的,意味著僅當(dāng)有觀察者訂閱之后才會(huì)執(zhí)行Stream的生產(chǎn)邏輯振愿。

Stream類(lèi)的一些具名構(gòu)造函數(shù):

  • Stream<T>.periodic():不斷地間隔產(chǎn)生數(shù)據(jù)捷犹,例如:

    final random = Random();
    final stream = Stream<int>.periodic(
      const Duration(seconds: 2),
      (count) => random.nextInt(10)
    );
    
  • Stream<T>.value():產(chǎn)生一個(gè)簡(jiǎn)單的事件,例如:

    final stream = Stream<String>.value("Hello");
    
  • Stream<T>.error():產(chǎn)生一個(gè)錯(cuò)誤的事件冕末,例如:

    Future<void> something(Stream<int> source) async {
      try {
        await for (final event in source) { ... }
      } on SomeException catch (e) {
        print("An error occurred: $e");
      }
    }
    // Pass the error object
    something(Stream<int>.error("Whoops"));
    
  • Stream<T>.fromIterable():產(chǎn)生一個(gè)從列表發(fā)送數(shù)據(jù)的Stream萍歉,例如:

     final stream = Stream<double>.fromIterable(const <double>[
       1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
    ]);
    
  • Stream<T>.fromFuture():將Future轉(zhuǎn)換成Stream,包含兩個(gè)事件档桃,一個(gè)是Future的結(jié)果枪孩,另一個(gè)是Stream的結(jié)束,例如:

    final stream = Stream<double>.fromFuture(
      Future<double>.value(15.10)
    );
    
  • Stream<T>.empty():表示發(fā)送一個(gè)結(jié)束的事件

Stream的一些方法:

  • drain(...):忽略所有事件胳蛮,僅在完成或者錯(cuò)誤時(shí)通知
  • map(...):改變事件
  • skip(int count):跳過(guò)前幾個(gè)事件

7.2.2 Subscribers

import 'dart:math';
Stream<int> randomNumbers() async* { 
  final random = Random();
  for(var i = 0; i < 10; ++i) {                                 
    await Future.delayed(Duration(seconds: 1)); 
    yield random.nextInt(50) + 1;                           
    }
}
void main() async {                                 // 1.
  final stream = randomNumbers();       // 2.
  await for (var value in stream) { // 3.
    print(value);
  }
  // 最后打印
  print("Async stream!");                       // 4.
}
  1. 處理異步Stream需要聲明方法為async
  2. 通過(guò)調(diào)用randomNumbers來(lái)訂閱Stream销凑,在訂閱的時(shí)候開(kāi)始執(zhí)行數(shù)據(jù)產(chǎn)生丛晌,因?yàn)镾tream是on-demand的
  3. 通過(guò)await for捕獲yield發(fā)送的數(shù)據(jù)
  4. 在最后打印結(jié)果

如果不希望打印"Async stream!"不希望被await阻塞仅炊,可以使用listen來(lái)監(jiān)聽(tīng)Stream結(jié)果:

void main() async {
  final stream = randomNumbers();

  stream.listen((value) {
    print(value);
  });
  // 最先打印
  print("Async stream!");
}

如果需要被多個(gè)Subscribers訂閱的話(huà),可以使用asBroadcastStream()方法澎蛛,詳見(jiàn):https://api.flutter.dev/flutter/dart-async/Stream/asBroadcastStream.html

在Flutter中大部分情況下只需要訂閱抚垄,不需要寫(xiě)Stream的生產(chǎn)者,因?yàn)榇蟛糠稚a(chǎn)者都是來(lái)自于library谋逻。

7.2.3 Controller

我們可以使用StreamController<T>來(lái)更精細(xì)地控制和管理Stream呆馁,例如下面代碼:

/// Exposes a stream that continuously generates random numbers
class RandomStream {
    /// The maximum random number to be generated final 
  int maxValue;
    static final _random = Random();
  
  Timer? _timer;
  late int _currentCount;
  late StreamController<int> _controller;
  
  /// Handles a stream that continuously generates random numbers. Use 
  /// [maxValue] to set the maximum random value to be generated. 
  RandomStream({this.maxValue = 100}) {
    _currentCount = 0;
    _controller = StreamController<int>(
      onListen: _startStream,
      onResume: _startStream,
      onPause: _stopTimer,
      onCancel: _stopTimer
    ); 
  }
  
    /// A reference to the random number stream
    Stream<int> get stream => _controller.stream; 
  
  void _startStream() {
    _timer = Timer.periodic(const Duration(seconds: 1), _runStream);
    _currentCount = 0;
  }
  
  void _stopTimer() {
    _timer?.cancel();
    _controller.close();
  }
  
  void _runStream(Timer timer) {
    _currentCount++;
    _controller.add(_random.nextInt(maxValue));
    if (_currentCount == maxValue) {
      _stopTimer();
    }
  }
}

調(diào)用代碼:

void main() async {
  final stream = RandomStream().stream;
  await Future.delayed(const Duration(seconds: 2));
  
  // The timer inside our 'RandomStream' is started
  final subscription = stream.listen((int random) {
    print(random);
  });
  
  await Future.delayed(const Duration(milliseconds: 3200));
  subscription.cancel();
}

StreamController<T>使用比較復(fù)雜,不過(guò)比較強(qiáng)大而且擴(kuò)展性較好毁兆,在Dart和Flutter中優(yōu)先使用StreamController<T>對(duì)Stream進(jìn)行處理浙滤。

7.3 Isolates

與Java等其他語(yǔ)言不同,Dart不支持直接開(kāi)啟多線(xiàn)程來(lái)處理后臺(tái)復(fù)雜計(jì)算任務(wù)气堕,也沒(méi)有線(xiàn)程安全的例如AtomicInteger的類(lèi)型纺腊,也不支持信號(hào)量畔咧、互斥鎖等避免數(shù)據(jù)競(jìng)爭(zhēng)和多線(xiàn)程編程問(wèn)題的手段。

Dart代碼都是運(yùn)行在isolates中揖膜,每個(gè)isolate中只有一個(gè)線(xiàn)程誓沸,isolate之間不會(huì)共享內(nèi)存,isolate之間通過(guò)message進(jìn)行通信壹粟,因此Dart中不存在數(shù)據(jù)競(jìng)爭(zhēng)拜隧。

如何在一個(gè)線(xiàn)程實(shí)現(xiàn)異步處理呢?每個(gè)isolate中都包含一個(gè)Event loop趁仙,異步方法會(huì)被切分成多個(gè)事件放到Event loop中洪添,從而達(dá)到不阻塞的效果。

[圖片上傳失敗...(image-71dfe8-1662032328576)]

例如下面代碼:

void requestAsync() async {
  print("event in async but not future");
  final String result = await getRequest();
  print(result);
}

void main() {
  requestAsync();
  print("event in main");
}

Future<String> getRequest() async {
  return Future<String>.value("")
    .then((value) {
      print("event in future");
      return Future<String>.value("future result");
    });
}

// output:
// event in async but not future
// event in main
// event in future
// future result

isolate中的Event loop大致如下:

isolate_event_loop.drawio.png

7.3.1 多個(gè)isolates

Dart application可以有多個(gè)isolate雀费,可以使用Isolate.spawn()創(chuàng)建薇组。Isolates有各自的內(nèi)存空間以及event loop,不同isolate不會(huì)共享內(nèi)存坐儿,isolate之間通過(guò)message通信律胀。

multi_isolate.png

Isolate有ReceivePort以及SendPort用于接受和發(fā)送message,例如:

void main() async {
  // Read some data.
  final jsonData = await _parseInBackground();

  // Use that data
  print('Number of JSON keys: ${jsonData.length}');
}

// Spawns an isolate and waits for the first message
Future<Map<String, dynamic>> _parseInBackground() async {
  final p = ReceivePort();
  await Isolate.spawn(_readAndParseJson, p.sendPort);
  return await p.first as Map<String, dynamic>;
}

Future<void> _readAndParseJson(SendPort p) async {
  final fileData = await File(filename).readAsString();
  final jsonData = jsonDecode(fileData);
  Isolate.exit(p, jsonData);
}

[圖片上傳失敗...(image-fdcb74-1662032328576)]

通常情況下不會(huì)直接調(diào)用Isolate.spawn()貌矿,而是調(diào)用Future<T> compute(...)炭菌,例如:

 // Model class
class PrimeParams {
  final int limit;
  final double another;
  const PrimeParams(this.limit, this.another);
}

// Use the model as parameter
int sumOfPrimes(PrimeParams data) {
  final limit = data.limit;
  final another = data.another;
  ...
}

// Function to be called in Flutter
Future<int> heavyCalculations() {
  final params = PrimeParams(50000, 10.5);
  return compute<PrimeParams, int>(sumOfPrimes, params);
}

參考

  1. Flutter Complete Reference
  2. A tour of the Dart language
  3. Concurrency in Dart
  4. Asynchronous programming: Streams
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逛漫,隨后出現(xiàn)的幾起案子黑低,更是在濱河造成了極大的恐慌,老刑警劉巖酌毡,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件克握,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡枷踏,警方通過(guò)查閱死者的電腦和手機(jī)菩暗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)竹揍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缰猴,“玉大人,你說(shuō)我怎么就攤上這事袭艺√桶荆” “怎么了佑稠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)旗芬。 經(jīng)常有香客問(wèn)我舌胶,道長(zhǎng),這世上最難降的妖魔是什么疮丛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任幔嫂,我火速辦了婚禮漱办,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婉烟。我一直安慰自己娩井,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布似袁。 她就那樣靜靜地躺著洞辣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昙衅。 梳的紋絲不亂的頭發(fā)上扬霜,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音而涉,去河邊找鬼著瓶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛啼县,可吹牛的內(nèi)容都是我干的材原。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼季眷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼余蟹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起子刮,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤威酒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挺峡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體葵孤,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年橱赠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尤仍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡病线,死狀恐怖吓著,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情送挑,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布暖眼,位于F島的核電站惕耕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诫肠。R本人自食惡果不足惜司澎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一欺缘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挤安,春花似錦谚殊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至围肥,卻和暖如春剿干,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背穆刻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工置尔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人氢伟。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓榜轿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朵锣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子差导,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容