本章是對Dart 基礎講解的最後一章:咱們講解餘下來的異步、泛型、異常,相對來講餘下來的這三章稍微有點難度,可是小夥伴只要用心跟着我一塊兒學習,應該問題不,若是有問題也能夠在下方留言或者私信我。好廢話很少說直接進入如主題:markdown
Dart目錄網絡
對於已經叱吒開發武林已久的開發人員來說,異步是一個很深的知識點。而咱們在接觸的每種語言裏面基本上都會提到異步,一樣地在使用Dart開發項目過程當中,也是會有異步操做的。app
咱們在Java , oc中可使用線程來實現異步操做,可是Dart是單線程模型,想要實現異步操做的話,咱們可使用事件隊列來處理。異步
重點:Dart是單線程模型語言,也就沒有了所謂的主線程/子線程之分。因此相對來講Dart線程理解起來和學習起來是相對容易的。async
再這裏首咱們要想理解Dart單線程模型,首先咱們理解什麼是Even-Looper,Event-Queueide
下面咱們先來看一張圖函數
從上圖咱們能夠看出:Event Loop就是從EventQueue中獲取Event、處理Event一直到EventQueue爲空爲止,而EventQueue小夥伴們能夠看做成一個管道,Events 就是管道中每個事件。可能說事件你們仍是有點模糊那咱們直接說是:用戶的輸入、文件IO、網絡請求、按鈕的點擊這些均可以說成Event。oop
下面咱們來講說單線程模型:性能
重點:只要Dart函數開始執行,它將會執行到這個函數結束,也就是說Dart的函數不會被其餘Dart代碼打斷。學習
因此在Dart中引入一個關鍵詞名叫:isolate 那什麼叫:isolate 首先從字面上來看是」隔離「每一個isolate都是隔離的,並不會共享內存。isolate是經過在通道上傳遞消息來通訊,這就標識了Dart從性能上大大全部提高。不像其它語言(包括Java、Kotlin、Objective-C和Swift)都使用搶佔式來切換線程。每一個線程都被分配一個時間分片來執行,若是超過了分配的時間,線程將被上下文切換搶佔。可是若是在線程間共享的資源(如內存)正在更新時發生搶佔,則會致使競態條件。 Flutter 對Dart情有獨鍾的那些事兒
import 'dart:core';
import 'dart:isolate';
int i;
void main() {
i = 10;
//建立一個消息接收器
ReceivePort receivePort = new ReceivePort();
//建立isolate
Isolate.spawn(isolateMain, receivePort.sendPort);
//接收其餘isolate發過來的消息
receivePort.listen((message) {
//發過來sendPort,則主isolate也能夠向建立的isolate發送消息
if (message is SendPort) {
message.send("好呀好呀!");
} else {
print("接到子isolate消息:" + message);
}
});
}
/// 新isolate的入口函數
void isolateMain(SendPort sendPort) {
// isolate是內存隔離的,i的值是在主isolate定義的因此這裏得到null
print(i);
ReceivePort receivePort = new ReceivePort();
sendPort.send(receivePort.sendPort);
// 向主isolate發送消息
sendPort.send("去大保健嗎?");
receivePort.listen((message) {
print("接到主isolate消息:" + message);
});
}
複製代碼
能夠看到代碼中,咱們接收消息使用了listene函數來監聽消息。假設咱們如今在main方法最後加入sleep休眠,會不會影響listene回調的時機?
import 'dart:io';
import 'dart:isolate';
int i;
void main() {
i = 10;
//建立一個消息接收器
ReceivePort receivePort = new ReceivePort();
//建立isolate
Isolate.spawn(isolateMain, receivePort.sendPort);
//接收其餘isolate發過來的消息
receivePort.listen((message) {
//發過來sendPort,則主isolate也能夠向建立的isolate發送消息
if (message is SendPort) {
message.send("好呀好呀!");
} else {
print("接到子isolate消息:" + message);
}
});
//增長休眠,是否會影響listen的時機?
sleep(Duration(seconds: 2));
print("休眠完成");
}
/// 新isolate的入口函數
void isolateMain(SendPort sendPort) {
// isolate是內存隔離的,i的值是在主isolate定義的因此這裏得到null
print(i);
ReceivePort receivePort = new ReceivePort();
sendPort.send(receivePort.sendPort);
// 向主isolate發送消息
sendPort.send("去大保健嗎?");
receivePort.listen((message) {
print("接到主isolate消息:" + message);
});
}
複製代碼
結果是會有延遲等待,而後咱們的listene纔打印出其餘isolate發過來的消息。到這個地方可能開發過Android 小夥伴會以爲如同Android Handler同樣。
在Dart運行環境中也是靠事件驅動的,經過event loop不停的從隊列中獲取消息或者事件來驅動整個應用的運行,isolate發過來的消息就是經過loop處理。可是不一樣的是在Android中每一個線程只有一個Looper所對應的MessageQueue,而Dart中有兩個隊列,一個叫作event queue(事件隊列),另外一個叫作microtask queue(微任務隊列)。
這裏有個疑問:其實Dart中的Main Isolate只有一個Event Looper。可是Dart中爲啥存在兩個列隊。
那microtask queue 存在的意義是啥:
其實這個裏面有巧妙的設計:microtask queue 存在是但願經過這個Queue來處理稍晚一些的事情,可是在下一個消息到來以前須要處理完的事情。當Event Looper正在處理Microtask Queue中的Event時候,Event Queue中的Event就中止了處理了,此時App不能繪製任何圖形,不能處理任何鼠標點擊,不能處理文件IO等等
在 Flutter 中有兩種處理異步操做的方式Future和Stream,Future用於處理單個異步操做,Stream用來處理連續的異步操做。啥意思呢?就比如:楊過練武功祕籍:練了打狗棒,並將打狗棒經過一段時間練會了這就是一個Future。
下面咱們先看看單個異步處理Future,其實在 Dart 庫中隨處可見 Future 對象:以下圖:
一般操做一個異步函數,並對其設置返回的對象,而這個對象就是一個 Future。 當一個 future 執行完後,他裏面的值 就可使用了,可使用 then() 來在 future 完成的時候執行其餘代碼。Future對象其實就表明了在事件隊列中的一個事件的結果。
///讀取文件
void readStringFromFile() {
File("/Users/Test/1.txt").readAsString().then((content) {
//任務執行完成會進入這裏,可以得到返回的執行結果。
print(content);
}).whenComplete(() {
//當任務中止時,最後會執行這裏。
print("楊過是武林高手");
}).catchError((e, s) {
//若是文件地址時會發生異常,這時候能夠利用catchError捕獲此異常。
print(s);
});
}
複製代碼
void moreTaskZips() {
//能夠等待多個異步任務執行完成後,再調用 then()。
//只有有一個執行失敗,就會進入 catchError()。
Future.wait([
// Future.delayed() 延遲執行一個延時任務。
// 2秒後返回結果
Future.delayed(new Duration(seconds: 2), () {
return "楊過";
}),
// 4秒後返回結果
Future.delayed(new Duration(seconds: 4), () {
return "我喜歡小龍女";
})
]).then((v) {
//執行成功會走到這裏
print(v[0] + v[1]);
}).catchError((v) {
//執行失敗會走到這裏
print("我是尹志平");
}).whenComplete(() {
//不管成功或失敗都會走到這裏
print("我就要和個人過兒回古墓");
});
}
複製代碼
Stream是一個抽象類,用於表示一序列異步數據的源。它是一種產生連續事件的方式,能夠生成數據事件或者錯誤事件,以及流結束時的完成事件。
Stream 的好處是處理過程當中內存佔用較小。舉個例子:在讀取file文件數據的時候, Future 只能一次獲取異步數據。而 Stream 能屢次異步得到的數據。若是當文件比較大,明顯Futrue佔用的時間更久,這樣子就會導內存佔用過大。
void readFile(){
// 說明:這裏的listen 其實就是一個訂閱了Stream 咱們經過查看源碼發現會返回一個 StreamSubscription 訂閱者
File("/Users/Test/app-release.apk").openRead().listen((List<int> bytes) {
print("Stream我被執行"); //執行屢次
});
//經過查詢源碼:readAsBytes 返回的是一個Future
File("/Users/Test/app-release.apk").readAsBytes().then((_){
print("future我被執行"); //執行1次
});
}
複製代碼
Stream 可經過listen進行數據監聽(listen其實就是訂閱當前Stream,會返回一個StreamSubscription訂閱者,訂閱者提供了取消訂閱的cancel(),去掉後咱們的listen中就接不到任何信息了。除了cancel()取消方法以外還有pause()暫停),經過error接收失敗狀態,經過done來接收結束狀態;
Dart中提供了多種建立Stream方法:
void main() {
// 第一種:建立方法Stream.fromFuture(Future<T> future)
_createStreamFromFuture();
}
_createStreamFromFuture() {
Future<String> getTimeOne() async {
await Future.delayed(Duration(seconds: 3));
return '當前時間爲:${DateTime.now()}';
}
Stream.fromFuture(getTimeOne())
.listen((event) => print('測試經過Stream.fromFuture建立Stream -> $event'))
.onDone(() => print('測試經過Stream.fromFuture建立Stream -> done 結束'));
//輸出結果
//測試經過Stream.fromFuture建立Stream -> 當前時間爲:2020-07-20 12:01:40.280591
//測試經過Stream.fromFuture建立Stream -> done 結束
}
複製代碼
void main() {
// 第二種建立方法: Stream.fromFutures(Iterable<Future<T>> futures)
_createStreamFromFuture();
}
_createStreamFromFuture() {
Future<String> getTimeOne() async {
await Future.delayed(Duration(seconds: 3));
return '當前時間爲:${DateTime.now()}';
}
Future<String> getTimeTwo() async {
await Future.delayed(Duration(seconds: 3));
return '當前時間爲:${DateTime.now()}';
}
Stream.fromFutures([getTimeOne(),getTimeTwo()])
.listen((event) => print('測試經過Stream.fromFutures建立Stream -> $event'))
.onDone(() => print('測試經過Stream.fromFutures建立Stream -> done 結束'));
//輸出結果
//測試經過Stream.fromFuture建立Stream -> 當前時間爲:2020-07-20 12:01:40.280591
//測試經過Stream.fromFuture建立Stream -> done 結束
}
複製代碼
Stream提供的:fromFutures裏面能夠塞多個Future, 經過一系列的 Future 建立新的單訂閱流,每一個 Future 都會有自身的 data / error 事件,當這一系列的 Future 均完成時,Stream 以 done 事件結束;若 Futures 爲空,實則是沒有意義的。則 Stream 會馬上關閉。
void main() {
// 第三種建立方法: Stream.fromIterable(Iterable<T> elements)
_createStreamFromFuture();
}
_createStreamFromFuture() {
var data = ['黃藥師', '郭靖', '楊過', false];
Stream.fromIterable(data)
.listen((event) => print('測試經過Stream.fromFuture建立Stream -> $event'))
.onDone(() => print('測試經過Stream.fromFuture建立Stream -> done 結束'));
// 測試經過Stream.fromFuture建立Stream -> 黃藥師
// 測試經過Stream.fromFuture建立Stream -> 郭靖
// 測試經過Stream.fromFuture建立Stream -> 楊過
// 測試經過Stream.fromFuture建立Stream -> false
// 測試經過Stream.fromFuture建立Stream -> done 結束
}
複製代碼
Stream有兩種訂閱模式:單訂閱和多訂閱。單訂閱就是隻能有一個訂閱者,上面的使用咱們都是單訂閱模式,而廣播是能夠有多個訂閱者。經過 Stream.asBroadcastStream() 能夠將一個單訂閱模式的 Stream 轉換成一個多訂閱模式的 Stream,isBroadcast 屬性能夠判斷當前 Stream 所處的模式
import 'dart:async';
import 'dart:io';
void main() {
_createStreamBroadcast();
}
_createStreamBroadcast() {
var stream = new File("/Users/Test/app-release.apk").openRead();
stream.listen((event) => print(' $event'));
//因爲是單訂閱,因此這個地方只能有一個,因此下面這種寫法是錯誤
//stream.listen((event) => print(' 我再添加一個訂閱$event'));
//一個單訂閱模式的 Stream 轉換成一個多訂閱模式的 Stream可使用Stream.asBroadcastStream
var broadcastStream =
new File("/Users/Test/app-release.apk").openRead().asBroadcastStream();
broadcastStream.listen((_) {
print("我是黃藥師1");
});
broadcastStream.listen((_) {
print("我是黃藥師2");
});
}
複製代碼
這裏咱們要注意一下多訂閱模式若是沒有及時添加訂閱者則可能丟數據。
import 'dart:async';
import 'dart:io';
void main() {
_createStreamBroadcast();
}
_createStreamBroadcast() {
//默認是單訂閱
var stream = Stream.fromIterable(["黃藥師", "郭靖", "楊過"]);
//3s後添加訂閱者 不會丟失數據
Timer(Duration(seconds: 3), () => stream.listen(print));
//建立一個流管理器 對一個stream進行管理
var streamController = StreamController.broadcast();
///我再這個地方添加一個流數據
streamController.add("小龍女");
///先發出事件再訂閱 沒法接到通知
streamController.stream.listen((i) {
print("broadcast:$i");
});
//記得關閉
streamController.close();
//這裏沒有丟失,由於stream經過asBroadcastStream轉爲了多訂閱,可是本質是單訂閱流,並不改變原始 stream 的實現特性
var broadcastStream =
Stream.fromIterable(["黃藥師-1", "郭靖-2", "楊過-3"]).asBroadcastStream();
Timer(Duration(seconds: 3), () => broadcastStream.listen(print));
}
複製代碼
使用async和await的代碼是異步的,可是看起來很像同步代碼。有了這兩個關鍵字,咱們能夠更簡潔的編寫異步代碼,而不須要調用Future相關的API
import 'dart:async';
import 'dart:io';
void main() {
_readData().then((v){
print("你的名字$v");//輸出:你的名字[黃藥師, 郭靖, 楊過]
});
}
List<String> _testAsyncAndAwait() {
return ["黃藥師", "郭靖", "楊過"];
}
_readData() async {
return _testAsyncAndAwait();
}
複製代碼
import 'dart:async';
import 'dart:io';
void main() {
_startMethod();
_methodC();
}
_startMethod() async {
_methodA();
await _methodB();
print("start結束");
}
_methodA() {
print("A開始執行這個方法~");
}
_methodB() async {
print("B開始執行這個方法~");
await print("後面執行這句話~");
print("繼續執行這句哈11111~");
}
_methodC() {
print("我是黃藥師!!!");
}
//A開始執行這個方法~
//B開始執行這個方法~
//後面執行這句話~
//我是黃藥師!!!
//繼續執行這句哈11111~
//start結束
複製代碼
Dart是一種可選的類型語言,因此Dart像其餘語言同樣也支持泛型,泛型的做用就是解決 類 接口 方法的複用性、以及對不特定數據類型的支持(類型校驗)。更直接的理解是傳入什麼,返回什麼,同時支持類型校驗。
語法以下:
Collection_name <data_type> identifier= new Collection_name<data_type>
複製代碼
下面咱們來聊聊,Dart中泛型方法,泛型類,泛型接口
import 'dart:async';
import 'dart:io';
void main() {
print(_setUser(User().name)); //輸出:黃藥師
}
_setUser<T>(T user) {
return user;
}
class User {
var name = "黃藥師";
}
複製代碼
import 'dart:async';
import 'dart:io';
void main() {
User<String>()
..addName("黃藥師")
..addName("楊過")
..addName("小龍女")
..addName("黃蓉")
..printInfo();
}
class User<T> {
List<T> names = List<T>();
void addName(T name) {
names.add(name);
}
void printInfo() {
names.forEach((v) {
print("我是:$v");
//輸出 我是:黃藥師
//我是:楊過
//我是:小龍女
//我是:黃蓉
});
}
}
複製代碼
import 'dart:async';
import 'dart:io';
void main() {
Student<String>()
..addName("黃藥師")
..addName("小龍女")
..printInfo();
}
class Student<T> implements User<T> {
List<T> names = List<T>();
@override
void addName(T name) {
names.add(name);
}
@override
void printInfo() {
names.forEach((v) {
print("你是:$v");
//你是:黃藥師
//你是:小龍女
});
}
}
abstract class User<T> {
void addName(T name);
void printInfo();
}
複製代碼
和 Java 不一樣的是,全部的 Dart 異常是非檢查異常。 方法不必定聲明瞭他們所拋出的異常, 而且不要求你捕獲任何異常。
Dart 提供了 Exception和Error 類型, 以及一些子類型。你還 能夠定義本身的異常類型。可是, Dart 代碼能夠 拋出任何非 null 對象爲異常,不只僅是實現了 Exception 或者Error 的對象。
throw new Exception('這是一個異常');
throw '這是一個異常';
throw 123;
複製代碼
與Java不一樣之處在於捕獲異常部分,Dart中捕獲異常一樣是使用catch語句,可是Dart中的catch沒法指定異常類型。須要結合on來使用,基本語法以下:
try {
throw 123;
} on int catch(e){
//使用 on 指定捕獲int類型的異常對象
} catch(e,s){
//函數 catch() 能夠帶有一個或者兩個參數,
//第一個參數爲拋出的異常對象,
//第二個爲堆棧信息 ( StackTrace 對象)
rethrow; //使用 `rethrow` 關鍵字能夠 把捕獲的異常給 從新拋出
} finally{
//finally內部的語句,不管是否有異常,都會執行。
print("this is finally");
}
複製代碼
總結: 歷經三週終於把這三篇文章完成,時間上拉距有點長,文章中針對Dar講解t相對簡單。好比:第一章裏面講解的final、const知識點,和本章異步講解的都不夠細,後期會出兩篇單獨針對「Dart異常」和final、const知識的講解。
最後經過在寫三篇文章的同時查詢了須要關於Flutter的資料。同時也遇到了比較好的博客以下:梁飛宇博客
本人知乎: 如何快速掌握Dart這門語言並進階Flutter變成大神
最後感謝小夥伴認真閱讀《40分鐘快速入門Dart基礎》三篇文章,若是有喜歡能夠點贊關注,也能夠私信本人,後期我會持續輸出關於Flutter知識和一些開發的技巧。