版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/a4a9c89b85ca
转载请标明出处:
https://www.jianshu.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');
}