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
班缰、private
、protected
等聲明可見(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()
:聚合元素組成新的Collectionsevery()
:判斷是否所有元素都滿(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
async
和await
是簡(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中有幾個(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.
- 返回類(lèi)型是
Stream<int>
,async*
表示可以使用yield
來(lái)發(fā)送數(shù)據(jù) - 循環(huán)產(chǎn)生100個(gè)隨機(jī)數(shù)
-
await
等待Future延遲1秒 - 使用
yield
來(lái)發(fā)送數(shù)據(jù) - 如果方法被
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.
}
- 處理異步Stream需要聲明方法為
async
- 通過(guò)調(diào)用
randomNumbers
來(lái)訂閱Stream销凑,在訂閱的時(shí)候開(kāi)始執(zhí)行數(shù)據(jù)產(chǎn)生丛晌,因?yàn)镾tream是on-demand的 - 通過(guò)
await for
捕獲yield
發(fā)送的數(shù)據(jù) - 在最后打印結(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大致如下:
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通信律胀。
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);
}