版權聲明:本文為博主原創文章,未經博主允許不得轉載。http://www.lxweimin.com/p/a4a9c89b85ca
轉載請標明出處:
http://www.lxweimin.com/p/a4a9c89b85ca
本文出自 AWeiLoveAndroid的博客
本文首發在公眾號Flutter那些事,未經允許,嚴禁轉載。
前言
Flutter1.0穩定版昨晚的終于發布了。我們為此感到高興。對于開發者來說,有了穩定版相當于一個定心丸。本文主要介紹Fllutter1.0的一些功能和相關工具。
Flutter系列博文鏈接 ↓:
工具安裝:
Flutter基礎篇:
- 谷歌Flutter1.0正式版發布
- Flutter基礎篇(1)-- 跨平臺開發框架和工具集錦
- Flutter基礎篇(2)-- 老司機用一篇博客帶你快速熟悉Dart語法
- Flutter基礎篇(3)-- Flutter基礎全面詳解
- Flutter基礎篇(4)-- Flutter填坑全面總結
- Flutter基礎篇(5)-- Flutter代碼模板,解放雙手,提高開發效率必備
- Flutter基礎篇(6)-- 水平和垂直布局詳解
- Flutter基礎篇(7)-- Flutter更新錯誤全面解決方案(圖文+視頻講解)
- Flutter基礎篇(8)-- Flutter for Web詳細介紹
- Flutter基礎篇(9)-- 手把手教你用Flutter實現Web頁面編寫
- Flutter1.9升級體驗總結(Flutter Web 1.9最新版本填坑指南)
Flutter進階篇:
Dart語法系列博文鏈接 ↓:
Dart語法基礎篇:
Dart語法進階篇:
本文代碼同步發布在Github: https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo
上一篇主要講了Dart的類與函數,由于內容有太多,我就把剩下的內容分開寫一篇文章。
這一篇我們講Dart的泛型、異步、庫等有關詳解,內容較多,希望大家可以耐心看完。我也是花了很長時間研究的。喜歡的就點個贊,打個賞吧。
感謝大家支持。
九、泛型(Generics)
如果您查看基本數組類型的API文檔 List,您會看到該類型實際上是List<E>。<...>表示法將List標記為 泛型(或參數化)類型 - 具有正式類型參數的類型。按照慣例,大多數類型變量都有單字母名稱,例如E,T,S,K和V.
(一)為什么使用泛型?
類型安全通常需要泛型,但它們比僅允許代碼運行有更多好處:
1).正確指定泛型類型可以生成更好的代碼。
如果您希望列表只包含字符串,則可以將其聲明為List<String>(將其讀作“字符串列表”)。這樣一來,工具可以檢測到將非字符串分配給列表可能是一個錯誤。
例子:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// 報錯 The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42);
2).您可以使用泛型來減少代碼重復。
泛型允許您在多種類型之間共享單個接口和實現,同時仍然利用靜態分析。
例如:創建了一個用于緩存對象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
您發現需要此接口針對字符串的做一個緩存,因此您需要創建另一個接口:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
如果還有其他更改,就要寫很多接口。
泛型可以省去創建所有這些接口的麻煩。你可以創建一個帶有類型參數的接口。
示例如下:T是一個占位符,您可以將其視為開發人員稍后定義的類型。
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
(二)使用集合文字
list和map文字可以參數化。參數化文字就像你已經看到的文字一樣,除了你在開始括號之前添加 <type>(對于list)或 <keyType, valueType>(對于map)。
以下是使用類型文字(typed literals)的示例:
var numbers = <String>['11', '22', '33'];
var pages = <String, String>{
'index.html': 'Homepage',
'store.html': 'Store',
'mine.html': 'Mine'
};
(三)使用帶有構造函數的參數化類型
要在使用構造函數時指定一個或多個類型,請將類型放在類名稱后面的尖括號<...>中。例如:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);
以下代碼創建一個具有整數的key和View類型的value的map:
var views = Map<int, View>();
(四)泛型集合及其包含的類型
Dart的泛型類型是具體的
。也就說,它們在運行時會會攜帶類型信息。示例如下:(相反,Java中的泛型使用擦除
,這意味著在運行時刪除泛型類型參數。在Java中,您可以測試對象是否為List,但您無法測試它是否是List<String>。)
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
print(names.runtimeType); // List<String>
(五)限制參數類型
實現泛型類型時,您可能希望限制其參數的類型。你可以在<>里面使用extends。
例如:
abstract class SomeBaseClass {
// 其他操作
}
class Foo<T extends SomeBaseClass> {
String toString() {
return "Instance of Foo<$T>";
}
}
class Extender extends SomeBaseClass {
//其他操作
}
現在可以使用SomeBaseClass或它的任何子類作為泛型參數。
例如:
void main() {
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
print(someBaseClassFoo.toString());// Instance of Foo<SomeBaseClass>
print(extenderFoo.toString());// Instance of Foo<SomeBaseClass>
}
也可以不指定泛型參數。
例如:
var foo = Foo();
//等同于print(foo.toString());
print(foo);// Instance of Foo<SomeBaseClass>
如果指定任何非SomeBaseClass類型會導致錯誤。
例如:var foo = Foo<Object>;
(六)使用泛型方法
新版本的Dart的泛型方法,允許在方法和函數上使用類型參數。(但它同樣適用于實例方法,靜態方法,頂級函數,本地函數甚至lambda表達式。)
例如:
T first<T>(List<T> data) {
// 做一些初始工作或錯誤檢查...
T tmp = data[0];
// 做一些額外的檢查或處理...
return tmp;
}
在first(<T>)上的的泛型類型參數,允許你在以下幾個地方使用類型參數T:
- 1). 在函數的返回類型(T)中
- 2). 在參數類型(List<T>)中
- 3). 在局部變量的類型(T tmp)
泛型方法可以聲明類方法(實例和靜態)以相同的方式獲取泛型參數。
class C {
static int f < S,T > (int x) => 3 ;
int m < S,T > (int x) => 3 ;
}
泛型方法也適用于函數類型參數,本地函數和函數表達式。
1.將泛型方法作為參數[callback]。
void functionTypedParameter(T callback <T>(T thing)){}
2.聲明一個本地泛型函數本身
。
void localFunction(){
T itself<T>(T thing) => thing;
}
3.將泛型函數表達式綁定到局部變量。
void functionExpression(){
var lambda = <T>(T thing) => thing;
}
十、庫和可見性
該import和library指令可以幫助您創建一個模塊化的,可共享的代碼庫。庫不僅提供API,還是隱私單元(以下劃線(_)
開頭的標識符僅在庫內可見)。每個Dart應用程序都是一個庫,即使它不使用library指令。可以使用包來分發庫。
(一)使用庫
使用import指定一個庫中的命名空間如何在另一個庫匯總使用。
例如,Dart Web應用程序通常使用dart:html 庫,它們可以像這樣導入:
import 'dart:html';
對于內置庫,URI具有特殊dart: 方案(scheme)。對于其他庫,您可以使用文件系統路徑或package: 方案(scheme),這個是由包管理器(如pub工具)提供的庫。
例如:
import 'libs/mylib.dart';
(二)指定庫前綴
如果導入兩個具有沖突標識符的庫,則可以為一個或兩個庫指定前綴。例如,如果test2.dart和test3.dart都有一個hello()函數,那么直接導入這兩個文件會有沖突,這種情況下我們可以使用as關鍵字給庫指定一個前綴:
test2.dart代碼如下:
void hello() {
print('test2.dart : hello()函數');
}
test3.dart代碼如下:
void hello(){
print('test3.dart : hello()函數');
}
現在要在test1.dart中導入這兩個文件:
// 這樣寫會報錯
// import 'test2.dart';
// import 'test3.dart';
// 正確寫法:
import 'test2.dart';
// 給導入的庫指定一個前綴 方便識別
import 'test3.dart' as test3;
調用方式:
void main(){
hello();//test2.dart : hello()函數
test3.hello();//test3.dart : hello()函數
}
導入庫也可以使用相對路徑。例如:lib/demo1/a.dart
, lib/demo2/b.dart
這兩個文件。現在b.dart
這個文件需要引用a.dart
,可以使用import '../demo1/a.dart'
導入。
(三)僅導入庫的一部分
如果只想使用庫的一部分,則可以有選擇地導入庫,可以使用show
或者hide
關鍵字。例如:show表示僅導入當前庫,hide表示除了當前庫之外全部導入。
// 僅導入mylib.dart里面的test2函數
// import 'libs/mylib.dart' show test2;
// 剛好和show相反 除了test2函數之外 其它的都導入
import 'libs/mylib.dart' hide test2;
//我們想導入mylib庫,但是不想用里面的otherLib這個庫 可以這樣寫
// import 'libs/mylib.dart' hide otherLib;
(四)懶加載一個庫
延遲加載(也稱為延遲加載)
允許應用程序根據需要加載庫,如果需要的話。以下是您可能使用延遲加載的一些情況:
- 1).減少應用程序的初始啟動時間。
- 2).例如,執行A/B測試 - 嘗試算法的替代實現。
- 3).加載很少使用的功能,例如可選的屏幕和對話框。
要延遲加載庫,必須先使用deferred as
它導入一個庫。當我們import一個庫的時候,如果使用了as 不能同時使用deferred as
例如:
// import 'libs/mylib.dart'; // 不能同時使用
import 'libs/mylib.dart' deferred as tests;
當您需要庫時,使用庫的標識符調用loadLibrary()。
例如(注意導包:import 'dart:async';):
Future hello() async {
await tests.loadLibrary();
tests.test2();
}
// 然后再去使用:
void main(){
hello(); // 結果是: mylib.dart:test2()函數
}
在上述的代碼中,await關鍵字暫停執行,直到庫被加載。
您可以在一個庫上調用loadLibrary()多次,而不會出現問題。該庫只加載一次。
使用延遲加載時請記住以下內容:
- 1).延遲庫的常量不是導入文件中的常量。請記住,在加載延遲庫之前,這些常量不存在。
- 2).您不能在導入文件中使用延遲庫中的類型。相反,請考慮將接口類型移動到由延遲庫和導入文件導入的*庫。
- 3).Dart隱式插入loadLibrary()到你使用deferred as namespace定義的命名空間。loadLibrary()函數返回Future。
(五)庫的拆分
【說明】dart官網不推薦使用part ,這個僅作為了解。
使用part指令,可以將庫拆分為多個Dart文件。part of表示隸屬于某個庫的一部分。
注意事項:
- 1.不能同時使用library和part of,它們都用于指定屬于庫的內容。
// library testlib2; 這個不能和part of同時使用 會報錯
// part of 表示這個庫是testlib庫的一部分
part of testlib1;
- B庫是A庫的一部分,在B庫里面聲明:part of A庫名稱
例如:在testlib2.dart里面聲明part of testlib1
; 表示testlib2這個庫是testlib庫的yi部分。
- B庫是A庫的一部分,在B庫里面聲明:part of A庫名稱
- 如果B庫聲明A庫的一部分,同時A庫也想聲明它的一部分是B庫,正確寫法:B庫聲明part of A庫名稱,然后A庫聲明part 'B庫的路徑' , 同時,如果B庫沒有聲明,那么在A庫里面使用part指令會報錯。
testlib1.dart內容:
- 如果B庫聲明A庫的一部分,同時A庫也想聲明它的一部分是B庫,正確寫法:B庫聲明part of A庫名稱,然后A庫聲明part 'B庫的路徑' , 同時,如果B庫沒有聲明,那么在A庫里面使用part指令會報錯。
// 第1個庫:
library testlib1;
// 可以不寫
part 'testlib2.dart';
void run() {
print('testlib1庫 : run()函數');
}
testlib2.dart內容:
part of testlib1;
class testLib2 {}
void start() {
print('testlib2庫 : start()函數');
}
- B庫聲明了part of A庫名稱,A庫可以省去聲明part 'B庫的路徑'
// 第1個庫:
library testlib1;
// 可以不寫
part 'testlib2.dart';
(六)庫的自動導入
在A庫中使用export關鍵字引入B庫,當我們使用A庫的時候,會自動引入B庫,也就是說我們導入了A庫,就可以使用B庫了。
mylib.dart內容為:
// 這是一個庫 命名為mylib
library mylib;
// 希望使用mylib的時候 自動使用otherlib.dart 可以使用export關鍵字引入其他庫
export 'otherlib.dart';
// 導入otherlib2.dart
export 'otherlib2.dart';
class MyLib {
void test() {
print('mylib.dart: MyLib : test()函數');
}
}
void test2() {
print('mylib.dart: test2()函數');
}
otherlib.dart庫內容為:
// otherlib庫
library otherlib;
class otherLib {}
void test() {
print('otherLib庫 : test()函數');
}
otherlib2.dart庫內容為:
// otherlib2庫
library otherlib2;
class otherLib2 {}
void test2() {
print('otherLib2庫 : test2()函數');
}
(七)庫的組成結構
庫的最低要求是:pubspec.yaml
文件和lib
目錄。
庫的pubspec.yaml文件與普通應用程序包的文件格式相同。
lib目錄:庫代碼位于lib 目錄下,并且對其他包是公共的。您可以根據需要在lib下創建任何層次結構。
聲明一個庫的關鍵字是library。
例如在文件test.dart文件首行加上:library mylib; 表示這個庫的名稱是mylib
十一、異步支持
Dart庫中包含許多返回Future或Stream對象的函數。這些函數是異步的:它們在設置可能耗時的操作(例如I / O)后返回,而不等待該操作完成。
Dart官網有關于異步的教學:
使用Future完成異步任務:https://www.dartlang.org/tutorials/language/futures
使用Streams(流)管理序列化數據:https://www.dartlang.org/tutorials/language/streams
async
和await
關鍵字支持異步編程,讓你寫異步代碼看起來類似于同步代碼。
(一)處理Future
當您需要完成Future的結果時,您有兩個選擇:
- 1).使用async和await。
- 2).使用Future API,如 庫瀏覽 中所述。
(二)使用async和await
使用async
和await
異步的代碼,但它看起來很像同步代碼。例如,這里有一些代碼await 用于等待異步函數的結果。例如:await lookUpVersion();
要使用async,代碼必須在async函數中(標記為async的函數)。
例如:
Future checkVersion() async {
var version = await lookUpVersion();
// 其他操作
}
注意: 雖然async函數可能執行耗時的操作,但它不會等待這些操作。async函數只在遇到第一個await表達式時執行。然后它返回一個Future對象,僅在await表達式完成后才恢復執行。
使用try,catch,finally在使用await的代碼中處理錯誤和清理代碼。
try {
var version = await lookUpVersion();
} catch (e) {
// 這里可以看到是什么錯誤。
}finally{
// 正確的解決方式寫在這里
}
您可以在異步功能中多次使用await。例如,以下代碼等待三次函數結果:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在await表達式中,表達式的值通常是Future; 如果不是,那么該值將自動包含在Future中。 這個Future對象表示返回一個對象的promise。 await表達式的值是返回的對象。 await表達式使執行暫停,直到該對象可用。
如果在使用await時遇到編譯時錯誤,請確保await在async函數中。
例如,要在應用程序的main()函數中使用await
,main()方法必須標記為async
:以下是一個完整的示例代碼:
import 'dart:async';
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
Future checkVersion() async {
print('checkVersion()');
var version = await lookUpVersion();
}
Future<String> lookUpVersion() async{
print('lookUpVersion()');
return '版本號:v1.0';
}
結果:
// checkVersion()
// lookUpVersion()
// lookUpVersion()
// In main: version is 版本號:v1.0
(三)定義一個異步函數
方法被async修飾的函數是異步函數。給一個函數添加async關鍵字,使得返回值是一個Future。
void main(){
lookUpVersion(); //輸出結果:lookUpVersion()同步方法 返回值是:1.0.0
lookUpVersion2(); // 輸出結果:lookUpVersion2()異步方法 返回值是:1.0.0
lookUpVersion3(); // 輸出結果:lookUpVersion3()異步方法 沒有返回值
}
例如,看下面這個返回值是String的同步函數:
String lookUpVersion() {
print('lookUpVersion()同步方法 返回值是:1.0.0');
return '1.0.0';
}
如果將其更改為異步函數 - 例如,因為Future的實現將非常耗時 - 返回的值是Future:
Future<String> lookUpVersion2() async{
print('lookUpVersion2()異步方法 返回值是:1.0.0');
return '1.0.0';
}
如果您的函數沒有返回有用的值,請設置其返回類型Future<void>
例如:
Future<void> lookUpVersion3() async {
print('lookUpVersion3()異步方法 沒有返回值');
}
(四)處理Stream
當您需要完成Future的結果時,您有兩個選擇:
- 1).使用async和異步for循環(await for)。
注意:在使用await for之前,請確保它使代碼更清晰,并且您確實希望等待所有Stream的結果。 例如,通常情況,不應該使用await for UI事件偵聽器,因為UI框架會發送無窮無盡的事件流(streams of events)。 - 2).使用Stream API(主要是IO操作。)
異步for循環的格式:await for(var或具體類型 標識符 in 表達式){}
例如:我們讀取本地的一個文件內容,實例代碼如下:
import 'dart:io';
import 'dart:convert';
void main() {
test();
}
// await for循環的使用示例
// 這里是讀取本地文件的內容
Future test() async {
var config=File('d:\\test.txt');
// 打開io流進行文件讀取
Stream<List<int>> inputStream = config.openRead();
var lines = inputStream
// 設置編碼格式為utf-8
.transform(utf8.decoder)
.transform(LineSplitter());
try {
await for (var line in lines) {
print('從Stream中獲取到的內容是: ${line} \r文本內容長度為:'+ '${line.length}\r=======');
}
print('文件現在沒有關閉。。。');
} catch (e) {
print(e);
}
}
表達式的值必須有Stream類型,執行過程如下:
- 1).等待,知道Stream發出一個數值。
- 2).執行for循環的主體,講變量設置為這個發出的數值。
- 3).重復1和2,知道關閉Stream。
要停止監聽Stream,你可以使用break或者return語句跳出for循環B并且從Stream中取消訂閱。
如果在實現異步for循環時遇到編譯時錯誤,確保await for在一個async函數中。
例如,要在應用程序的main()函數中使用await for循環,main()方法必須標記為async:以下是一個完整的示例代碼:
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
有關異步編程的更多信息,請查看庫瀏覽的 Dart庫之旅 --- dart:async 部分,另請參閱文章 Dart語言異步支持:階段2(該頁面可能過期了) 和 Dart語言規范。
十二、Isolates
大多數計算機,甚至在移動平臺上,都有多核CPU。為了利用所有這些核心,開發人員傳統上使用并發運行的共享內存線程。但是,共享狀態并發容易出錯,并且可能導致代碼復雜化。
所有Dart代碼都在隔離區內運行,而不是線程。每個隔離區都有自己的內存堆,確保不會從任何其他隔離區訪問隔離區的狀態。
Dart是單線程模型,但是使用Isolates可以用于多線程。
這個庫主要用于服務端的開發。如果你不使用Dart做服務端開發,僅作為了解即可。
源碼可以看Github:https://github.com/dart-lang/isolate
官方API文檔: https://api.dartlang.org/stable/2.1.0/dart-isolate/dart-isolate-library.html
使用isolate 需要先導入包:import 'dart:isolate';
下面來一個簡單的示例代碼:
// 在另一個隔離區()中同步讀取“D://file.json”
// 結果是{msg: [{title: 你好1, contents: yes}, {title: 你好2, contents: NO}]}
main() async {
// 在其他隔離(isolate)中同步讀取文件,然后對其進行解碼。
print(await readIsolate());
}
// 同步讀取'D//file.json'(在同一個線程中)
Map readSync() {
JsonCodec().decode(new File('D://file.json').readAsStringSync());
}
// 在另一個隔離區()中同步讀取“D://file.json”
Future readIsolate() async {
final response = new ReceivePort();
await Isolate.spawn(_isolate, response.sendPort);
return response.first;
}
/// 期望通過[Isolate.spawn]創建
void _isolate(SendPort sendPort) {
sendPort.send(readSync());
}
下面是file.json文件的內容:
{
"msg": [
{
"title": "你好1",
"contents": "yes"
},
{
"title": "你好2",
"contents": "NO"
}
]
}
十三、生成器(Generators)
當您需要懶惰地生成一系列值時,請考慮使用生成器函數。Dart支持兩種生成器功能。
(一)同步生成器,返回一個Iterable對象。
要實現同步生成器函數,請將函數體標記為sync*,并使用yield語句來傳遞值。
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
如果您的生成器是遞歸的,您可以使用yield*以下方法來提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
(二)異步生成器,返回一個Stream對象。
要實現異步生成器函數,請將函數體標記為async*,并使用yield語句來傳遞值。
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
十四、類型定義
在Dart中,函數是對象,就像字符串一樣,數字是對象。一個類型定義,或功能型的別名,給出了一個函數類型聲明字段時,您可以使用和返回類型的名稱。當函數類型分配給變量時,typedef會保留類型信息。
以下代碼,它不使用typedef:我們可以看到compare是一個函數,但它是哪一種類型的函數?不是很清楚。
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// compare是一個函數,但它是哪一種類型的函數?
assert(coll.compare is Function);
}
接下來使用typedef改造一下:
我們將代碼更改為使用顯式名稱并保留類型信息,開發人員和工具都可以使用該信息。
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
目前:typedef僅限于函數類型。
因為typedef只是別名,Dart提供了一種檢查任何函數類型的方法。
例如:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True
}
十五、元數據Metadata
使用元數據提供有關代碼的其他信息。元數據注解以字符開頭@,后跟對編譯時常量(如deprecated)的引用或對常量構造函數的調用。
元數據可以出現在庫,類,typedef,類型參數,構造函數,工廠,函數,字段,參數或變量聲明之前以及導入或導出指令之前。您可以使用反射在運行時檢索元數據。
所有Dart代碼都有兩個注解:@deprecated
和 @override
。
以下是使用@deprecated 注解的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
// Turns the TV's power on.
void turnOn() {
//...
}
}
您可以定義自己的元數據注釋。
這是一個定義帶有兩個參數的@todo注釋的示例:
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
以下是使用@todo注釋的示例:
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}