├── 45分钟快速入门(上).md ├── 45分钟快速入门(下).md ├── Dart 异步编程详解.md ├── Dart 网络编程.md ├── Dart 语言Stream详解.md ├── Dart 语言标准流与文件操作.md ├── Dart 调用C语言混合编程.md └── README.md /45分钟快速入门(上).md: -------------------------------------------------------------------------------- 1 | 2 | # 前言 3 | 谷歌推出Flutter跨平台UI框架后,对移动端的开发又产生了新的影响,Flutter采用Dart语言开发,而Flutter为什么选择Dart语言作为唯一的开发语言呢?总的来说,其拥有如下优势 4 | 5 | - Dart可基于AOT(Ahead Of Time)编译,即编译成平台的本地代码,运行性能高。 6 | - Dart也可基于JIT(Just In Time)编译,编译快速,可热加载,使开发周期加倍提升(Flutter亚秒级有状态热重载) 7 | - Dart可以更轻松地创建以60fps运行的流畅动画和转场。Dart在没有锁的情况下进行对象分配和垃圾回收 8 | - Dart语法结合Java与JavaScript语法特点,几乎没有令人不适的怪异语法,使Java程序员倍感亲切,快速上手 9 | 10 | 通常来说一门语言要么使用AOT编译,编译慢,开发效率低,或者使用JIT编译,在运行时编译,虽然可以热重载,但是执行效率低,而Dart在这两种之间做出了完美平衡,当开发时使用JIT编译,调试快,所见即所得,开发效率高,当发布时,使用AOT编译,编译成目标平台的本地代码,执行效率高。 11 | 12 | 13 | 14 | # 环境准备 15 | ## 安装Dart SDK 16 | [官方下载地址](https://www.dartlang.org/install) 17 | 共有三种SDK版本选择 18 | - Flutter 19 | - Web 20 | - Server 21 | 22 | 本章仅作为Dart编程语法学习,这里建议安装**Server**版的SDK,然后选择Windows版本进行下载。 23 | 24 | ## 配置环境变量 25 | 在Windows上,通过点击下一步即可安装,安装完成后,需将`dart-sdk`下的的`bin`目录添加到系统Path环境变量中。这一步骤是通常的命令配置步骤。 26 | 27 | 28 | ## 配置 VSCode 编辑器 29 | 作为Dart语言的学习,不建议下载笨重的IDE,官方提供支持VSCode 编辑器插件,建议使用VSCode 学习。 30 | 31 | 从 [官网下载](https://code.visualstudio.com/ "官网下载") VSCode编辑器,安装完成后,启动VSCode并在插件商店中搜索Dart进行插件安装。 32 | 33 | 34 | ## 测试环境 35 | 在VSCode中新建一个`test.dart`文件,编写如下代码 36 | ```dart 37 | void main(){ 38 | print("hello world!"); 39 | } 40 | ``` 41 | 运行后成功在控制台输出`hello world!` 42 | 43 | # 基础语法 44 | 45 | ## 代码注释 46 | Dart中的代码注释基本与Java语言相同 47 | ```dart 48 | // 单行注释 49 | 50 | /* 51 | * 多行注释 52 | */ 53 | 54 | /** 55 | * 文档注释 56 | */ 57 | 58 | /// 使用三个斜杠开头 59 | /// 这是Dart特有的文档注释 60 | ``` 61 | 62 | ## 内置数据类型 63 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190323004151710.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 64 | > 在Dart中,所有能够使用变量引用的都是对象,每个对象都是一个类的实例。数字、函数和 `null` 也都是对象。所有的对象都继承于Object类。 65 | 66 | 要注意,没有初始化的变量默认值为 `null`。数值类型变量的默认值也是 `null`。 67 | 68 | 数值类型`num`有两个具体子类,分别为`int`和`double`,其中`int`为整数值,范围是`-2^53`至`2^53`之间;`double`则是64位的双精度浮点数。 69 | 70 | 71 | ## 变量与常量 72 | ### 定义变量 73 | Dart中定义变量有两种方式,一种是静态类型语言常用的方式,显式指定变量类型,另一种则是动态语言的常用方式,不指定类型,由vm自动推断。 74 | 75 | ```dart 76 | // 1.通过显式指定类型来定义变量 77 | String name = "张三"; 78 | num age = 18; 79 | 80 | // 2.使用关键字var,不指定类型 81 | var address = "深南大道"; 82 | var id = 100; 83 | 84 | /* 使用var定义变量,即使未显式指定类型,一旦赋值后类型就被固定 85 | * 因此使用var定义的变量不能改变数据类型 86 | */ 87 | var number = 19; 88 | // 以下代码错误,无法运行,number变量已确定为int类型 89 | number = "2019"; 90 | ``` 91 | 92 | 如想动态改变变量的数据类型,应当使用`dynamic`或`Object`来定义变量。 93 | ```dart 94 | // dynamic声明变量 95 | dynamic var1 = "hello"; 96 | var1 = 19; 97 | print(var1); // 19 98 | 99 | // Object声明变量 100 | Object var2 = 20; 101 | var2 = "Alice"; 102 | print(var2); // Alice 103 | ``` 104 | 105 | 建议在编写代码时,尽可能显式指定变量类型,这样可以提升代码可读性与调试的便利性。 106 | 107 | ### 定义常量 108 | Dart中定义常量也有两种方式,一种使用`final`关键字,同Java中的用法, 一个 final 变量只能赋值一次;另一种是Dart的方式,使用`const`关键字定义。 109 | ```dart 110 | // 1.使用final关键字定义常量 111 | final height = 10; 112 | 113 | // 2.使用const关键字定义常量 114 | const pi = 3.14; 115 | ``` 116 | 需要注意,`final`定义的常量是运行时常量,而`const`常量则是编译时常量,也就是说`final`定义常量时,其值可以是一个变量,而`const`定义的常量,其值必须是一个字面常量值。 117 | 118 | ```dart 119 | final time = new DateTime.now(); // 正确 120 | const time = new DateTime.now(); // 错误 121 | 122 | 123 | const list = const[1,2,3]; // 正确 124 | const list = [1,2,3]; // 错误 125 | ``` 126 | 127 | ## 内置类型的常用操作 128 | ### 数值类型 129 | 130 | ```dart 131 | // String 转 int 132 | var one = int.parse('1'); 133 | 134 | // String 转 double 135 | var onePointOne = double.parse('1.1'); 136 | 137 | // int 转 String 138 | String oneAsStr = 1.toString(); 139 | 140 | // double 转 String 141 | String piAsStr = 3.14159.toStringAsFixed(2); // 保留两位 '3.14' 142 | 143 | // Dart也支持整数位操作,<<、 >>、&、| 144 | print((3 << 1) == 6); // 0011 << 1 == 0110 145 | print((3 >> 1) == 1); // 0011 >> 1 == 0001 146 | print((3 | 4) == 7); // 0011 | 0100 == 0111 147 | ``` 148 | 149 | ### 字符串 150 | > 值得一提的是,Dart中提供的字符串`插值表达式`使字符串格式化变得异常方便。 151 | 152 | ```dart 153 | // 1.Dart可以使用单引号或双引号来创建字符串 154 | var s1 = "hello"; 155 | var s2 = 'world'; 156 | 157 | // 2.类似Python,Dart可以使用三引号来创建包含多行的字符串 158 | var multiLine1 = """你可以像这样,创建一个 159 | 包含了多行的字符串内容 160 | """; 161 | 162 | var multiLine2 = '''你也可以使用三个单引号,创建一个 163 | 包含了多行的字符串内容 164 | '''; 165 | 166 | // 3.类似Python,还可以在字符串字面值的前面加上`r`来创建原始字符串,则该字符串中特殊字符可以不用转义 167 | var path = r'D:\workspace\code'; 168 | 169 | // 4.Dart支持使用"+"操作符拼接字符串 170 | var greet = "hello" + " world"; 171 | 172 | // 5.Dart提供了插值表达式"${}",也可以用于拼接字符串 173 | var name = "王五"; 174 | var aStr = "hello,${name}"; 175 | print(aStr); // hello,王五 176 | 177 | // 当仅取变量值时,可以省略花括号 178 | var aStr2 = "hello,$name"; // hello,王五 179 | 180 | // 当拼接的是一个表达式时,则不能省略花括号 181 | var str1 = "link"; 182 | var str2 = "click ${str1.toUpperCase()}"; 183 | print(str2); // click LINK 184 | 185 | // 6. 与Java不同,Dart使用"=="来比较字符串的内容 186 | print("hello" == "world"); 187 | ``` 188 | 189 | ### 布尔类型 190 | > Dart中的布尔类型用法同Java,仅有`false`、`true`两个值,不能使用0、非0或者`null`、非`null`来表达`false`和`true`。与Java不同的是,布尔类型的默认值为`null` 191 | 192 | ```dart 193 | bool flags; 194 | print(flags); // null 195 | ``` 196 | 197 | ### 列表 198 | Dart中列表操作与JavaScript中的数组相似。 199 | ```dart 200 | // 创建列表 201 | var list = [1, 2, 3]; 202 | // 下标从0开始。使用length可以访问list的长度 203 | print(list[0]); 204 | print(list.length); 205 | 206 | // 可以使用add添加元素 207 | list.add(5); 208 | 209 | // 可在list字面量前添加const关键字,定义一个不可改变的 列表(编译时常量) 210 | var constantList = const [1, 2, 3]; 211 | constantList[1] = 1; // 报错 212 | ``` 213 | 214 | ### 映射 215 | 又称为关联数组,相当于Java中的`HashMap` 216 | 217 | ```dart 218 | // 1.通过字面量创建Map 219 | var gifts = { 220 | 'first' : 'partridge', 221 | 'second': 'turtledoves', 222 | 'fifth' : 'golden rings' 223 | }; 224 | 225 | // 2.使用Map类的构造函数创建对象 226 | var pic = new Map(); 227 | // 往Map中添加键值对 228 | pic['first'] = 'partridge'; 229 | pic['second'] = 'turtledoves'; 230 | pic['fifth'] = 'golden rings'; 231 | 232 | // 3.获取Map的长度 233 | print(pic.length); 234 | 235 | // 4.查找Map 236 | pirnt(pic["first"]); 237 | print(pic["four"]); // 键不存在则返回 null 238 | ``` 239 | 240 | ## 函数 241 | > 在Dart中,函数(或方法) 也是对象,它的类型是 `Function`。 这意味着,函数可以赋值给变量,也可以当做其他函数的参数。 242 | 243 | ### 定义函数 244 | Dart中定义函数,基本上与Java类似 245 | ```dart 246 | String greet(String name){ 247 | return "hello,$name"; 248 | } 249 | ``` 250 | 251 | 在Dart中,类型是可选,可以省略显式的类型,但仍然建议显式指定类型。 252 | ```dart 253 | greet(name){ 254 | return "hello,$name"; 255 | } 256 | ``` 257 | 258 | 要注意,函数也是对象,所有函数都有返回值。当没有指定返回值的时候,函数会返回`null`。当然,如果你强行使用`void`来修饰函数,则函数真的没有返回值,这种情况就另当别论了。 259 | 260 | ### 函数的参数 261 | Dart中支持两种可选参数 262 | - 命名可选参数 263 | - 位置可选参数 264 | 265 | 在Java中通常使用方法重载来实现同名方法的不同参数调用,Dart中则可以通过可选参数来实现相同效果。 266 | 267 | #### 命名可选参数 268 | 先来看一下`命名参数`,它使用花括号来定义参数列表 269 | 270 | ```dart 271 | // 定义一个函数,参数列表用花括号包裹 272 | enableFlags({bool bold, bool hidden}) { 273 | // do something 274 | } 275 | 276 | // 调用方式,传参时使用"参数名:值"的形式 277 | enableFlags(hidden:true,bold:false); 278 | ``` 279 | 280 | 如果在定义函数时,给参数列表中的参数设置默认值,则该参数就是可选的,函数调用时可以忽略该参数,使用默认的值。 281 | ```dart 282 | // 定义add函数 283 | add({int x, int y=1, int z=0}){ 284 | print(x + y + z; 285 | } 286 | 287 | // 调用 288 | add(x:18); // 19 289 | add(x:18, y:2, z:10); // 30 290 | ``` 291 | 这里需要注意一下,SDK 1.21之前的版本中,命名参数不能使用`=`号来设置默认值,而SDK 1.21之后,只能使用`=`号来设置默认值。因此,请检查并升级SDK版本。 292 | 293 | #### 位置可选参数 294 | `位置可选参数`使用中括号来定义参数列表,中括号中的参数是可选的 295 | 296 | ```dart 297 | // 定义add函数 298 | add(int x, [int y, int z]){ 299 | int result = x; 300 | if (y != null){ 301 | result = result + y; 302 | } 303 | 304 | if (z != null){ 305 | result = result + z; 306 | } 307 | print(result); 308 | } 309 | 310 | // 调用 311 | add(18); // 18 312 | add(18,12); // 30 313 | add(18, 12, 15); // 45 314 | ``` 315 | 316 | 给`位置可选参数`设置默认值 317 | ```dart 318 | // 定义add函数 319 | add(int x, [int y=0, int z=0]){ 320 | print(x +y+z); 321 | } 322 | ``` 323 | 324 | 最后需要注意一下`命名可选参数`与`位置可选参数`的区别,前者中的参数与顺序无关,无需按顺序传参,且传参数时需使用冒号;后者与顺序相关,传参必须依照顺序。 325 | 326 | 327 | ### 匿名函数 328 | > 大部分函数都有名字,但我们也可以创建没有名字的函数,称为匿名函数,也被称为lambda表达式或者闭包。 329 | 330 | 331 | ```dart 332 | // 定义匿名函数,并将其赋值给一个变量func,注意,函数体最后的花括号处必须有分号结束。 333 | var func = (x,y){ 334 | return x + y; 335 | }; 336 | 337 | print(func(10,11)); // 21 338 | ``` 339 | 340 | 注意,匿名函数与普通函数基本相同,也有参数列表,函数体,只是省去了函数名而已。 341 | 342 | ### 箭头函数 343 | > Dart中的箭头函数与JavaScript中的基本相同。当函数体中只包含一个语句时,我们就可以使用`=>`箭头语法进行缩写。注意,箭头函数仅仅只是一个简洁表达的语法糖。 344 | 345 | 普通函数 346 | ```dart 347 | add(num x, num y){ 348 | return x + y; 349 | } 350 | 351 | print(add(18,12)); // 30 352 | ``` 353 | 354 | 箭头函数 355 | ```dart 356 | // 与上面的普通函数完全等价 357 | add(num x, num y) => x + y; 358 | 359 | print(add(18,12)); // 30 360 | ``` 361 | 362 | 箭头函数省略了花括号的表达,箭头后面跟一个表达式,函数的返回值也就是这个表达式的值。另外,箭头函数也可以与匿名函数结合,形成匿名箭头函数。 363 | 364 | ```dart 365 | var func = (num x, num y) => x + y; 366 | ``` 367 | 368 | ## 运算符 369 | Dart语言中的运算符与Java中的绝大多数相同。 370 | 371 | ### 算术运算符 372 | `+`、`-`、`*`、`/`、`%`同Java语言 373 | 374 | Dart中又多出了一个整除运算符`~/`,与普通除号的区别是将相除后的结果取整返回。 375 | 376 | ### 类型判定运算符 377 | 以下是Dart增加的类型相关的运算符。 378 | 379 | | 操作符 | 解释 | 380 | | --- | --- | 381 | | `as` | 用于类型转换 | 382 | | `is` | 如果对象是指定的类型就返回 True | 383 | | `is!` | 如果对象不是指定的类型返回 True | 384 | 385 | 当 `obj` 实现了 `T` 的接口时, `obj is T` 才是 true。类似于Java中的`instanceof`。 386 | 387 | Dart中使用 `as` 操作符把对象转换为特定的类型,如无法转换则会抛出异常,因此在转换前最好使用`is`运算符进行检测。 388 | 389 | ```dart 390 | // 将p转换为Person类型再操作 391 | (p as Person).name = 'Bruce'; 392 | ``` 393 | 394 | ### 条件表达式 395 | Dart中也支持三目表达式 396 | `condition ? expr1 : expr2` 397 | 398 | 除此外,Dart还增加了非空条件判断符`??` 399 | `expr1 ?? expr2` 400 | 上述运算表示,如果expr1的值不等于`null`,则返回其值; 否则执行表达式expr2并返回其结果。 401 | 402 | ```dart 403 | var str1 = "Hello"; 404 | var str2 = "world"; 405 | var result = str1 ?? str2.toUpperCase(); 406 | ``` 407 | 408 | ### 级联运算符 409 | > 我们通常使用`.`操作符调用对象的方法,这在Dart中也是支持的,但是Dart另外增加了一种级联运算符`..`,用两个点表示。 410 | 411 | `级联运算符`可以在同一个对象上连续调用多个方法以及访问成员变量。 使用它可以避免创建临时变量, 写出更流畅的代码。 412 | 413 | 假如类Person有三个方法,`setName`、`setAge`、`save`,则可如下调用 414 | ```dart 415 | new Person()..setName("Bob")..setAge(20)..save(); 416 | ``` 417 | 418 | 使用`级联运算符`调用方法,无需该方法返回对象本身即可连续的流式的调用该对象的其他方法。 419 | 420 | ### 条件成员访问符 421 | > 在Java中很容易碰到恼人的空指针错误,因此在方法调用前需要进行对象的非空判断,这样的判断语句使代码变得冗长,可读性差,不整洁。Dart中则发明了一个新的运算符用于处理此类情况。 422 | 423 | 条件成员访问符`?.`,它和`.`类似,但是运算符左边的对象不能为`null`,否则返回`null`,若对象不为`null`,则返回对象本身。 424 | 425 | ```dart 426 | // list1默认值为null 427 | List list1; 428 | print(list1?.length); // null 429 | 430 | List list2 = []; 431 | print(list2?.length); // 0 432 | ``` 433 | 434 | 435 | ## 分支与循环 436 | ### 条件分支 437 | Dart中的条件分支基本与Java相同 438 | 439 | `if`条件分支 440 | ```dart 441 | if(i < 0){ 442 | print('i < 0'); 443 | }else if(i == 0){ 444 | print('i = 0'); 445 | } else { 446 | print('i > 0'); 447 | } 448 | ``` 449 | 450 | `switch`条件分支 451 | ```dart 452 | // 在switch的case中可以使用整数、字符串、枚举类型和编译时常量 453 | String command = 'OPEN'; 454 | switch (command) { 455 | case 'CLOSED': 456 | break; 457 | case 'OPEN': 458 | break; 459 | default: 460 | print('Default'); 461 | } 462 | ``` 463 | 464 | ### 循环语句 465 | #### 基本循环 466 | Dart中的基本循环语句与Java相同 467 | 468 | ```dart 469 | // for循环 470 | for(int i = 0; i < 9; i++) { 471 | print(i); 472 | } 473 | 474 | // while循环 475 | while(true){ 476 | //do something 477 | } 478 | 479 | // do-while循环 480 | do{ 481 | //do something 482 | } while(true); 483 | ``` 484 | 485 | #### 特有循环 486 | ```dart 487 | var myList = ['Java','JavaScript','Dart']; 488 | 489 | // for...in...循环,类似Java中的增强for 490 | for (var it in myList ){ 491 | print(it); 492 | } 493 | 494 | // forEach循环。其参数为一个Function对象,这里传入一个匿名函数 495 | myList.forEach((var it){ 496 | print(it); 497 | }); 498 | 499 | // 可以使用匿名箭头函数简写 500 | myList.forEach((it) => print(it)); 501 | ``` 502 | 503 | 使用循环遍历Map 504 | ```dart 505 | var myMap = { 506 | 'zhangsan':'201901', 507 | 'lisi':'201902', 508 | 'wangwu':'201902' 509 | }; 510 | 511 | // forEach遍历Map 512 | myMap.forEach((k, v) => print("$k : $v")); 513 | 514 | // 根据键获取值来遍历。通过keys返回Map中所有键的集合 515 | for(var k in myMap.keys){ 516 | print("$k : ${myMap[k]}"); 517 | } 518 | ``` 519 | 520 | 521 | ------ 522 | 523 | 524 | # 视频课程 525 | 526 | 或关注博主视频课程,[**Flutter全栈式开发**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 527 | 528 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 529 | 530 | ------ 531 | 532 | **关注公众号:编程之路从0到1** 533 | 534 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 535 | -------------------------------------------------------------------------------- /45分钟快速入门(下).md: -------------------------------------------------------------------------------- 1 | 2 | ## 类和对象 3 | ### 类的定义 4 | ```dart 5 | // Dart中定义一个类 6 | class Person { 7 | String name; 8 | int age; 9 | 10 | Person(String name, int age) { 11 | this.name = name; 12 | this.age = age; 13 | } 14 | } 15 | ``` 16 | 17 | Dart中的类与Java中的相似,不同的是,Dart中没有`private`、`public`这些成员访问修饰符。如果是类私有的成员,不希望外面访问,只需要在成员变量之前加上一个下划线`_`变为私有即可。 18 | 19 | 20 | 以上代码,在Dart中还有一种简化写法,可以自动在构造方法中对成员变量初始化。 21 | ```dart 22 | // Dart中定义一个类 23 | class Person { 24 | String name; 25 | int age; 26 | 27 | // 在构造方法中初始化成员变量时,可使用如下写法简化 28 | Person(this.name, this.age); 29 | 30 | // 如需处理其他变量时,也可单独对其操作 31 | // Person(this.name, this.age, String address){ 32 | // print(address); 33 | // } 34 | // 注意,构造方法不能重载,以上注释掉 35 | } 36 | ``` 37 | 38 | 另外还需要注意一点,Dart中没有构造方法的重载,不能写两个同名的构造方法。 39 | 40 | ### Getters 和 Setters方法 41 | 在Java中,一般不会直接在类的外部去访问类成员,通常使用setter和getter方法来操作类的成员变量。而在Dart语言中,所有类中都包含隐式的getter方法,对于非`final`修饰的成员,类中还包含隐式的setter方法。这就意味着,在Dart中,你可以直接在类外部通过`.`操作符访问类成员。这一特点使得Dart语法更加简洁,不会写出满屏的setXXX、getXXX方法。 42 | 43 | 当然,很多时候我们调用setter和getter方法并不仅仅是为了赋值和访问,而是为了一些额外的处理,这时候我们只需要使用`set`与`get`关键字实现setter和getter方法即可。 44 | 45 | ```dart 46 | class Person { 47 | String userName; 48 | 49 | Person(this.userName); 50 | 51 | // 方法名前加get关键字 52 | String get name{ 53 | return "user:" + this.userName; 54 | } 55 | 56 | // 方法名前加set关键字 57 | set name(String name){ 58 | // do something 59 | this.userName = name; 60 | } 61 | } 62 | 63 | void main() { 64 | var p = new Person("zhangsan"); 65 | print(p.name); // user:zhangsan 66 | p.name = "Jack"; 67 | print(p.name); // user:Jack 68 | } 69 | ``` 70 | 71 | 要注意,在创建对象时,`new`关键字并不是必须的,可以省略不写。在写Flutter界面时,不建议写`new`关键字实例化对象,因为Flutter框架中没有类似的xml语言来描述UI界面,界面也是使用Dart语言来写,在使用Dart写UI时,要保持代码的简洁和结构化,省略`new`会更友好。 72 | 73 | ### 构造方法 74 | > 如果没有定义构造方法,则会有一个默认的无参构造方法,并且会调用超类的无参构造方法。 75 | 76 | #### 命名构造方法 77 | 上面已经说过,Dart类中两个同名构造方法不能重载,但是Dart语言为类新增了一种称为`命名构造方法`的东西。 78 | ```dart 79 | class Person { 80 | String userName; 81 | int age; 82 | 83 | Person(this.userName, this.age); 84 | 85 | // 命名构造方法 86 | Person.fromData(Map data) { 87 | this.userName = data['name']; 88 | this.age = data['age']; 89 | } 90 | } 91 | 92 | void main() { 93 | // 使用命名构造方法创建对象 94 | var p = new Person.fromData({ 95 | "name":"Bob", 96 | "age":19 97 | }); 98 | print(p.userName); 99 | } 100 | ``` 101 | 102 | 注意,使用命名构造方法可以为一个类实现多个构造方法,也可以更清晰的表明意图。 103 | 104 | #### 常量构造方法 105 | > 如果想提供一个状态永远不变的对像,在Dart中,我们可以创建一个编译时常量对象,节省开销。 106 | 107 | ```dart 108 | class ConstPoint { 109 | final num x; 110 | final num y; 111 | 112 | // 使用const修构造方法 113 | const ConstPoint(this.x, this.y); 114 | 115 | // 编译时常量对象,需使用const来创建对象 116 | static final ConstPoint origin = const ConstPoint(0, 0); 117 | } 118 | 119 | void main() { 120 | print(ConstPoint.origin.x); 121 | print(ConstPoint.origin.y); 122 | } 123 | ``` 124 | 125 | #### 工厂构造方法 126 | 当我们需要创建一个新的对象或者从缓存中取一个对象时,工厂构造方法就派上了用场。 127 | 128 | ```dart 129 | class Logger { 130 | final String name; 131 | 132 | // 创建一个静态Map做为缓存 133 | static final Map _cache = {}; 134 | 135 | // 定义一个命名构造方法,用下划线"_"修饰,将构造方法私有化 136 | Logger._internal(this.name); 137 | 138 | // 使用关键字factory修饰类同名构造方法 139 | factory Logger(String name) { 140 | if (_cache.containsKey(name)) { 141 | return _cache[name]; 142 | } else { 143 | // 调用命名构造方法创建新对象 144 | final logger= new Logger._internal(name); 145 | _cache[name] = logger; // 存入缓存 146 | return logger; 147 | } 148 | } 149 | } 150 | 151 | void main() { 152 | var uiLog = new Logger('UI'); 153 | var eventLog = new Logger('event'); 154 | } 155 | ``` 156 | 157 | #### 构造方法重定向 158 | 有时候一个构造方法会调动类中的其他构造方法来实例化,这时候可以使用构造方法重定向, 159 | 160 | ```dart 161 | class Point { 162 | num x; 163 | num y; 164 | 165 | // 同名构造方法 166 | Point(this.x, this.y); 167 | 168 | // 命名构造方法重定向到同名构造方法,中间使用一个冒号 169 | Point.alongXAxis(num x) : this(x, 0); 170 | } 171 | ``` 172 | 173 | ### 类的初始化列表 174 | 熟悉C++的朋友应该对初始化列表很了解了,Java中是没有这个特性的。 175 | 176 | ```dart 177 | class Point { 178 | final num x; 179 | final num y; 180 | final num distance; 181 | 182 | Point(x, y) 183 | : x = x, 184 | y = y, 185 | distance = sqrt(x * x + y * y){ 186 | print("这是构造方法"); 187 | } 188 | } 189 | 190 | void main() { 191 | var p = new Point(2, 3); 192 | print(p.distance); 193 | } 194 | ``` 195 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190323003650911.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 196 | - 初始化列表位于构造方法的小括号与大括号之间,在初始化列表之前需添加一个冒号。 197 | - 初始化列表是由逗号分隔的一些赋值语句组成。 198 | - 它适合用来初始化 `final`修饰的变量 199 | - 初始化列表的调用是在构造方法之前,也就是在类完成实例化之前,因此初始化列表中是不能访问 `this`的 200 | 201 | ### 运算符重载 202 | 这个特性,又很类似于C++中的运算符重载,在Java中是没用这种概念的。 203 | 204 | ```dart 205 | class Point { 206 | int x; 207 | int y; 208 | 209 | Point(this.x, this.y); 210 | 211 | // 使用operator关键字,为该类重载"+"运算符 212 | Point operator +(Point p) { 213 | return new Point(this.x + p.x, this.y + p.y); 214 | } 215 | 216 | // 为该类重载"-"运算符 217 | Point operator -(Point p) { 218 | return new Point(this.x - p.x, this.y - p.y); 219 | } 220 | } 221 | 222 | void main(){ 223 | var p1 = new Point(1,5); 224 | var p2 = new Point(7,10); 225 | 226 | // 重载运算符后,类可以使用“+”、“-” 运算符操作 227 | var p3 = p1 + p2; 228 | var p4 = p2 - p1; 229 | 230 | print("${p3.x}, ${p3.y}"); 231 | print("${p4.x}, ${p4.y}"); 232 | } 233 | ``` 234 | 打印结果: 235 | ``` 236 | 8, 15 237 | 6, 5 238 | ``` 239 | 240 | Dart中允许重载的运算符如下: 241 | 242 | | | | | | | | | 243 | | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | 244 | | `+`|`–`|`*`| `~/`|`/`| `%` | `^` | 245 | | `<`|`>`|`<=`|`>=`|`==`|`[]`|`[]=`| 246 | |`&` |`~` |`<<` |`>>`| || 247 | 248 | 249 | ### 类的继承 250 | Dart中的继承,与Java中相似,可以使用关键字`extends`继承父类,使用关键字`super`引用父类 251 | 252 | ```dart 253 | class Father { 254 | myFunction(){ 255 | // do something 256 | } 257 | } 258 | 259 | class Son extends Father { 260 | 261 | @override 262 | myFunction(){ 263 | super.myFunction(); 264 | // do something 265 | } 266 | } 267 | ``` 268 | 269 | 我们知道,Java中的类仅支持单继承,而Dart中的类也只支持单继承。但是Dart可以使用一种被称为混入的方式来达到多继承的效果,这需要使用`with`关键字。 270 | 271 | ```dart 272 | // 首先定义三个父类 273 | class Father1 { 274 | a(){ 275 | print("this is a func"); 276 | } 277 | 278 | common(){ 279 | print("common Father1"); 280 | } 281 | } 282 | 283 | class Father2 { 284 | b(){ 285 | print("this is b func"); 286 | } 287 | 288 | common(){ 289 | print("common Father2"); 290 | } 291 | } 292 | 293 | class Father3 { 294 | c(){ 295 | print("this is c func"); 296 | } 297 | 298 | common(){ 299 | print("common Father3"); 300 | } 301 | } 302 | 303 | //定义子类 304 | class Son extends Father1 with Father2,Father3{ 305 | 306 | } 307 | 308 | void main() { 309 | var obj = new Son(); 310 | obj.common(); 311 | obj.a(); 312 | obj.b(); 313 | obj.c(); 314 | } 315 | ``` 316 | 317 | 打印结果: 318 | ``` 319 | common Father3 320 | this is a func 321 | this is b func 322 | this is c func 323 | ``` 324 | 325 | 要注意,以上继承写法中,也可以直接使用`with`,等价于如下写法 326 | ```dart 327 | class Son with Father1,Father2,Father3{ 328 | 329 | } 330 | ``` 331 | 332 | ### 接口抽象 333 | #### 抽象类 334 | > Dart语言没有提供`interface`关键字来定义接口,但是Dart语言中保留了抽象类,同Java,使用`abstract`关键字来修饰抽象类。而Dart中的抽象类,实际上就相当于Java中的接口。 335 | 336 | ```dart 337 | abstract class Base { 338 | // 省略函数体即可定义抽象方法,不需加关键字 339 | func1(); 340 | func2(); 341 | } 342 | ``` 343 | 344 | 注意,抽象类是不能被实例化的,子类继承抽象类时,必须实现全部抽象方法。 345 | 346 | 347 | #### 隐式接口 348 | > 实际上在Dart中,每个类都隐式的定义了一个包含所有实例成员的接口, 并且该类实现了这个接口。 349 | 350 | 因此,如果我们想实现某个接口,但有又不想继承,则可以使用这种隐式接口机制。我们需要用到关键字`implements` 351 | 352 | ```dart 353 | class People { 354 | void greet(){ 355 | print("Hello"); 356 | } 357 | } 358 | 359 | class Student implements People{ 360 | @override 361 | void greet(){ 362 | print("Hi,I'm Alice."); 363 | } 364 | } 365 | 366 | greet(People p){ 367 | p.greet(); 368 | } 369 | 370 | void main() { 371 | greet(new Student()); 372 | } 373 | ``` 374 | 375 | ## 泛型 376 | Dart中也支持泛型,用法与Java中类似。 377 | 378 | ```dart 379 | // 泛型 380 | var names = new List(); 381 | names.add("zhangsan") 382 | 383 | var maps = new Map(); 384 | maps[1]="value"; 385 | 386 | // 字面量写法 387 | var infos = ['Seth', 'Kathy', 'Lars']; 388 | 389 | var pages = { 390 | 'index.html': 'Homepage', 391 | 'robots.txt': 'Hints for web robots' 392 | }; 393 | ``` 394 | 395 | ## 异常处理 396 | 如果关心具体异常,针对不同异常进行不同处理,可以使用`try...on`处理异常,`finally`是可选的,用于最后的处理。 397 | ```dart 398 | try { 399 | // 使除数为0 400 | print(11~/0); 401 | } on IntegerDivisionByZeroException { 402 | print("除数为0"); 403 | }on Exception{ 404 | print("Exception"); 405 | }finally { 406 | print("finally"); 407 | } 408 | ``` 409 | 410 | 不关心具体异常,只想捕获,避免异常继续传递,则可以使用`try...catch`处理 411 | 412 | ```dart 413 | try { 414 | print(11~/0); 415 | } catch(e){ 416 | // 打印报错信息 417 | print(e); 418 | }finally { 419 | print("finally"); 420 | } 421 | ``` 422 | 423 | 如果想获取更多异常信息,可以使用两个参数的`catch`,第二个参数是异常的调用栈信息 424 | ```dart 425 | try { 426 | print(11~/0); 427 | } catch(e,s){ 428 | print(s); 429 | } 430 | ``` 431 | 432 | 如果你既想针对不同异常进行不同处理,还想打印调用栈信息,那就将两种结合起来使用 433 | 434 | ```dart 435 | try { 436 | print(11~/0); 437 | } on IntegerDivisionByZeroException catch(e,s){ 438 | print(s); 439 | } on Exception catch(e,s){ 440 | print(s); 441 | } 442 | ``` 443 | 444 | 445 | ## 库与导入 446 | Dart使用`import`语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库。 447 | 448 | ```dart 449 | // 指定dart:前缀,表示导入标准库,如dart:io 450 | import 'dart:math'; 451 | 452 | // 也可以用相对路径或绝对路径来引用dart文件 453 | import 'lib/student/student.dart'; 454 | 455 | // 指定package:前缀,表示导入包管理系统中的库 456 | import 'package:utils/utils.dart'; 457 | ``` 458 | 459 | 导入库时,可以使用`as`关键字来给库起别名,避免命名空间冲突。 460 | ```dart 461 | import 'package:lib1/lib1.dart'; 462 | import 'package:lib2/lib2.dart' as lib2; 463 | 464 | // 使用lib1中的Element 465 | Element element1 = new Element(); 466 | // 使用lib2中的Element 467 | lib2.Element element2 = new lib2.Element(); 468 | ``` 469 | 470 | 使用`show`和`hide`关键字控制库中成员的可见性 471 | 472 | ```dart 473 | // 仅导入foo,屏蔽库中其他成员 474 | import 'package:lib1/lib1.dart' show foo; 475 | 476 | // 屏蔽foo,库中其他成员都可见 477 | import 'package:lib2/lib2.dart' hide foo; 478 | ``` 479 | 为了减少 APP 的启动时间,加载很少使用的功能,我们还可以延迟导入库。使用 `deferred as`关键字延迟导入 480 | 481 | ```dart 482 | import 'package:deferred/hello.dart' deferred as hello; 483 | 484 | // 当需要使用时,再通过库标识符调用 loadLibrary函数加载 485 | hello.loadLibrary(); 486 | ``` 487 | 488 | ## 异步编程 489 | Dart与JavaScript一样,是一个单线程模型。但这并不意味着Dart中不能进行异步编程,只是这种异步编程区别于传统的多线程异步方式。 490 | 491 | Dart中的所有代码都只在一个线程上运行,但Dart代码可以运行在多个**isolate**上。**isolate**可以看做一个微小的线程,**isolate**由虚拟机调度,**isolate**之间没有共享内存,因此它们之间没有竞争,不需要锁,不用担心死锁,因此开销小,性能高。由于没有共享内存,所以它们之间唯一的通信只能通过Port进行,而且Dart中的消息传递也总是异步的。 492 | 493 | Dart中两种方式可以使用`Future`对象来进行异步编程 494 | * 使用 `async` 和 `await`关键字 495 | * 使用 Future API 496 | 497 | 使用`async`和`await`编写代码非常简单,而且编写的代码看起来有点像同步代码,实际上是异步的。 498 | 499 | ```dart 500 | // 导入io库,调用sleep函数 501 | import 'dart:io'; 502 | 503 | // 模拟耗时操作,调用sleep函数睡眠2秒 504 | doTask() async{ 505 | await sleep(const Duration(seconds:2)); 506 | return "Ok"; 507 | } 508 | 509 | // 定义一个函数用于包装 510 | test() async { 511 | var r = await doTask(); 512 | print(r); 513 | } 514 | 515 | void main(){ 516 | print("main start"); 517 | test(); 518 | print("main end"); 519 | } 520 | ``` 521 | 522 | 运行结果: 523 | ``` 524 | main start 525 | main end 526 | Ok 527 | ``` 528 | 529 | 530 | 在函数签名中加入`async`关键字,表示该函数异步执行,`await`表示等待异步结果执行完成返回`Future`对象。但有一点需要注意,`await`只能在`async`函数中出现,因此往往需要再定义一个`async`函数,用于包装。上述代码中`test`函数就是用于包装。 531 | 532 | 533 | ------ 534 | 535 | 536 | # 视频课程 537 | 或关注博主视频课程,[**Flutter全栈式开发课程**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 538 | 539 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 540 | 541 | **关注公众号:编程之路从0到1** 542 | 543 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 544 | -------------------------------------------------------------------------------- /Dart 异步编程详解.md: -------------------------------------------------------------------------------- 1 | 2 | # Dart 异步编程 3 | 编程中的代码执行,通常分为`同步`与`异步`两种。简单说,同步就是按照代码的编写顺序,从上到下依次执行,这也是最简单的我们最常接触的一种形式。但是同步代码的缺点也显而易见,如果其中某一行或几行代码非常耗时,那么就会阻塞,使得后面的代码不能被立刻执行。 4 | 5 | 异步的出现正是为了解决这种问题,它可以使某部分耗时代码不在当前这条执行线路上立刻执行,那究竟怎么执行呢?最常见的一种方案是使用多线程,也就相当于开辟另一条执行线,然后让耗时代码在另一条执行线上运行,这样两条执行线并列,耗时代码自然也就不能阻塞主执行线上的代码了。 6 | 7 | 多线程虽然好用,但是在大量并发时,仍然存在两个较大的缺陷,一个是开辟线程比较耗费资源,线程开多了机器吃不消,另一个则是线程的锁问题,多个线程操作共享内存时需要加锁,复杂情况下的锁竞争不仅会降低性能,还可能造成死锁。因此又出现了基于事件的异步模型。简单说就是在某个单线程中存在一个事件循环和一个事件队列,事件循环不断的从事件队列中取出事件来执行,这里的事件就好比是一段代码,每当遇到耗时的事件时,事件循环不会停下来等待结果,它会跳过耗时事件,继续执行其后的事件。当不耗时的事件都完成了,再来查看耗时事件的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行。 8 | 9 | 我们很容易发现,这种基于事件的异步模型,只适合`I/O`密集型的耗时操作,因为`I/O`耗时操作,往往是把时间浪费在等待对方传送数据或者返回结果,因此这种异步模型往往用于网络服务器并发。如果是计算密集型的操作,则应当尽可能利用处理器的多核,实现并行计算。 10 | 11 | ![](https://img-blog.csdnimg.cn/20190505100632250.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 12 | 13 | ## Dart 的事件循环 14 | 15 | Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。 Dart虽然提供调用堆栈。 但是它使用事件在生产者和消费者之间传输上下文。 事件循环由单个线程支持,因此根本不需要同步和锁定。 16 | 17 | Dart 的两个队列分别是 18 | - `MicroTask queue` 微任务队列 19 | 20 | - `Event queue` 事件队列 21 | 22 | ![](https://img-blog.csdnimg.cn/20190505100705326.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 23 | 24 | 25 | Dart事件循环执行如上图所示 26 | 27 | 1. 先查看`MicroTask`队列是否为空,不是则先执行`MicroTask`队列 28 | 2. 一个`MicroTask`执行完后,检查有没有下一个`MicroTask`,直到`MicroTask`队列为空,才去执行`Event`队列 29 | 3. 在`Evnet` 队列取出一个事件处理完后,再次返回第一步,去检查`MicroTask`队列是否为空 30 | 31 | 32 | 我们可以看出,将任务加入到`MicroTask`中可以被尽快执行,但也需要注意,当事件循环在处理`MicroTask`队列时,`Event`队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。 33 | 34 | ## 调度任务 35 | 注意,以下调用的方法,都定义在`dart:async`库中。 36 | 37 | **将任务添加到`MicroTask`队列有两种方法** 38 | 39 | ```dart 40 | import 'dart:async'; 41 | 42 | void myTask(){ 43 | print("this is my task"); 44 | } 45 | 46 | void main() { 47 | // 1. 使用 scheduleMicrotask 方法添加 48 | scheduleMicrotask(myTask); 49 | 50 | // 2. 使用Future对象添加 51 | new Future.microtask(myTask); 52 | } 53 | ``` 54 | 55 | **将任务添加到`Event`队列** 56 | 57 | ```dart 58 | import 'dart:async'; 59 | 60 | void myTask(){ 61 | print("this is my task"); 62 | } 63 | 64 | void main() { 65 | new Future(myTask); 66 | } 67 | ``` 68 | 69 | 70 | 现在学会了调度任务,赶紧编写代码验证以上的结论 71 | 72 | ```dart 73 | import 'dart:async'; 74 | 75 | void main() { 76 | print("main start"); 77 | 78 | new Future((){ 79 | print("this is my task"); 80 | }); 81 | 82 | new Future.microtask((){ 83 | print("this is microtask"); 84 | }); 85 | 86 | print("main stop"); 87 | } 88 | ``` 89 | 90 | 运行结果: 91 | 92 | ``` 93 | main start 94 | main stop 95 | this is microtask 96 | this is my task 97 | ``` 98 | 99 | 可以看到,代码的运行顺序并不是按照我们的编写顺序来的,将任务添加到队列并不等于立刻执行,它们是异步执行的,当前`main`方法中的代码执行完之后,才会去执行队列中的任务,且`MicroTask`队列运行在`Event`队列之前。 100 | 101 | 102 | 103 | ## 延时任务 104 | 如需要将任务延伸执行,则可使用`Future.delayed`方法 105 | 106 | ```dart 107 | new Future.delayed(new Duration(seconds:1),(){ 108 | print('task delayed'); 109 | }); 110 | ``` 111 | 112 | 表示在延迟时间到了之后将任务加入到`Event`队列。需要注意的是,这并不是准确的,万一前面有很耗时的任务,那么你的延迟任务不一定能准时运行。 113 | 114 | ```dart 115 | import 'dart:async'; 116 | import 'dart:io'; 117 | 118 | void main() { 119 | print("main start"); 120 | 121 | new Future.delayed(new Duration(seconds:1),(){ 122 | print('task delayed'); 123 | }); 124 | 125 | new Future((){ 126 | // 模拟耗时5秒 127 | sleep(Duration(seconds:5)); 128 | print("5s task"); 129 | }); 130 | 131 | print("main stop"); 132 | } 133 | ``` 134 | 135 | 运行结果: 136 | ``` 137 | main start 138 | main stop 139 | 5s task 140 | task delayed 141 | ``` 142 | 143 | 从结果可以看出,`delayed`方法调用在前面,但是它显然并未直接将任务加入`Event`队列,而是需要等待1秒之后才会去将任务加入,但在这1秒之间,后面的`new Future`代码直接将一个耗时任务加入到了`Event`队列,这就直接导致写在前面的`delayed`任务在1秒后只能被加入到耗时任务之后,只有当前面耗时任务完成后,它才有机会得到执行。这种机制使得延迟任务变得不太可靠,你无法确定延迟任务到底在延迟多久之后被执行。 144 | 145 | 146 | ## Future 详解 147 | Future类是对未来结果的一个代理,它返回的并不是被调用的任务的返回值。 148 | 149 | ```dart 150 | void myTask(){ 151 | print("this is my task"); 152 | } 153 | 154 | void main() { 155 | Future fut = new Future(myTask); 156 | } 157 | ``` 158 | 159 | 如上代码,`Future`类实例`fut`并不是函数`myTask`的返回值,它只是代理了`myTask`函数,封装了该任务的执行状态。 160 | 161 | 162 | ### 创建 Future 163 | 164 | `Future`的几种创建方法 165 | 166 | - `Future()` 167 | - `Future.microtask()` 168 | - `Future.sync()` 169 | - `Future.value()` 170 | - `Future.delayed()` 171 | - `Future.error()` 172 | 173 | 其中`sync`是同步方法,任务会被立即执行 174 | ```dart 175 | import 'dart:async'; 176 | 177 | void main() { 178 | print("main start"); 179 | 180 | new Future.sync((){ 181 | print("sync task"); 182 | }); 183 | 184 | new Future((){ 185 | print("async task"); 186 | }); 187 | 188 | print("main stop"); 189 | } 190 | ``` 191 | 192 | 运行结果: 193 | ``` 194 | main start 195 | sync task 196 | main stop 197 | async task 198 | ``` 199 | 200 | ### 注册回调 201 | 当`Future`中的任务完成后,我们往往需要一个回调,这个回调立即执行,不会被添加到事件队列。 202 | 203 | ```dart 204 | import 'dart:async'; 205 | 206 | void main() { 207 | print("main start"); 208 | 209 | 210 | Future fut =new Future.value(18); 211 | // 使用then注册回调 212 | fut.then((res){ 213 | print(res); 214 | }); 215 | 216 | // 链式调用,可以跟多个then,注册多个回调 217 | new Future((){ 218 | print("async task"); 219 | }).then((res){ 220 | print("async task complete"); 221 | }).then((res){ 222 | print("async task after"); 223 | }); 224 | 225 | print("main stop"); 226 | } 227 | ``` 228 | 229 | 运行结果: 230 | ``` 231 | main start 232 | main stop 233 | 18 234 | async task 235 | async task complete 236 | async task after 237 | ``` 238 | 239 | 除了`then`方法,还可以使用`catchError`来处理异常,如下 240 | 241 | ```dart 242 | new Future((){ 243 | print("async task"); 244 | }).then((res){ 245 | print("async task complete"); 246 | }).catchError((e){ 247 | print(e); 248 | }); 249 | ``` 250 | 251 | 还可以使用**静态方法**`wait` 等待多个任务全部完成后回调。 252 | 253 | ```dart 254 | import 'dart:async'; 255 | 256 | void main() { 257 | print("main start"); 258 | 259 | Future task1 = new Future((){ 260 | print("task 1"); 261 | return 1; 262 | }); 263 | 264 | Future task2 = new Future((){ 265 | print("task 2"); 266 | return 2; 267 | }); 268 | 269 | Future task3 = new Future((){ 270 | print("task 3"); 271 | return 3; 272 | }); 273 | 274 | Future fut = Future.wait([task1, task2, task3]); 275 | fut.then((responses){ 276 | print(responses); 277 | }); 278 | 279 | print("main stop"); 280 | } 281 | ``` 282 | 283 | 运行结果: 284 | ``` 285 | main start 286 | main stop 287 | task 1 288 | task 2 289 | task 3 290 | [1, 2, 3] 291 | ``` 292 | 293 | 如上,`wait`返回一个新的`Future`,当添加的所有`Future`完成时,在新的`Future`注册的回调将被执行。 294 | 295 | 296 | ## async 和 await 297 | 在Dart1.9中加入了`async`和`await`关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用`Future`相关的API 298 | 299 | 将 `async` 关键字作为方法声明的后缀时,具有如下意义 300 | 301 | * 被修饰的方法会将一个 `Future` 对象作为返回值 302 | * 该方法会**同步**执行其中的方法的代码直到**第一个 await 关键字**,然后它暂停该方法其他部分的执行; 303 | * 一旦由 **await** 关键字引用的 **Future** 任务执行完成,**await**的下一行代码将立即执行。 304 | 305 | 306 | ```dart 307 | // 导入io库,调用sleep函数 308 | import 'dart:io'; 309 | 310 | // 模拟耗时操作,调用sleep函数睡眠2秒 311 | doTask() async{ 312 | await sleep(const Duration(seconds:2)); 313 | return "Ok"; 314 | } 315 | 316 | // 定义一个函数用于包装 317 | test() async { 318 | var r = await doTask(); 319 | print(r); 320 | } 321 | 322 | void main(){ 323 | print("main start"); 324 | test(); 325 | print("main end"); 326 | } 327 | ``` 328 | 329 | 运行结果: 330 | ``` 331 | main start 332 | main end 333 | Ok 334 | ``` 335 | 336 | 需要注意,**async** 不是并行执行,它是遵循Dart **事件循环**规则来执行的,它仅仅是一个语法糖,简化`Future API`的使用。 337 | 338 | 339 | ## Isolate 340 | 前面已经说过,将非常耗时的任务添加到事件队列后,仍然会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要`Isolate`,这个单词的中文意思是隔离。 341 | 342 | 简单说,可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程,或者说是协程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个`Isolate`完全是两条独立的执行线,且每个`Isolate`都有自己的**事件循环**,它们之间只能通过发送消息通信,所以它的资源开销低于线程。 343 | 344 | 从主`Isolate`创建一个新的`Isolate`有两种方法 345 | 346 | ### spawnUri 347 | `static Future spawnUri()` 348 | 349 | `spawnUri`方法有三个必须的参数,第一个是Uri,指定一个新`Isolate`代码文件的路径,第二个是参数列表,类型是`List`,第三个是动态消息。需要注意,用于运行新`Isolate`的代码文件中,必须包含一个main函数,它是新`Isolate`的入口方法,该main函数中的args参数列表,正对应`spawnUri`中的第二个参数。如不需要向新`Isolate`中传参数,该参数可传空`List` 350 | 351 | 352 | 主`Isolate`中的代码: 353 | ```dart 354 | import 'dart:isolate'; 355 | 356 | 357 | void main() { 358 | print("main isolate start"); 359 | create_isolate(); 360 | print("main isolate stop"); 361 | } 362 | 363 | // 创建一个新的 isolate 364 | create_isolate() async{ 365 | ReceivePort rp = new ReceivePort(); 366 | SendPort port1 = rp.sendPort; 367 | 368 | Isolate newIsolate = await Isolate.spawnUri(new Uri(path: "./other_task.dart"), ["hello, isolate", "this is args"], port1); 369 | 370 | SendPort port2; 371 | rp.listen((message){ 372 | print("main isolate message: $message"); 373 | if (message[0] == 0){ 374 | port2 = message[1]; 375 | }else{ 376 | port2?.send([1,"这条信息是 main isolate 发送的"]); 377 | } 378 | }); 379 | 380 | // 可以在适当的时候,调用以下方法杀死创建的 isolate 381 | // newIsolate.kill(priority: Isolate.immediate); 382 | } 383 | ``` 384 | 385 | 创建`other_task.dart`文件,编写新`Isolate`的代码 386 | 387 | ```dart 388 | import 'dart:isolate'; 389 | import 'dart:io'; 390 | 391 | 392 | void main(args, SendPort port1) { 393 | print("isolate_1 start"); 394 | print("isolate_1 args: $args"); 395 | 396 | ReceivePort receivePort = new ReceivePort(); 397 | SendPort port2 = receivePort.sendPort; 398 | 399 | receivePort.listen((message){ 400 | print("isolate_1 message: $message"); 401 | }); 402 | 403 | // 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信 404 | port1.send([0, port2]); 405 | // 模拟耗时5秒 406 | sleep(Duration(seconds:5)); 407 | port1.send([1, "isolate_1 任务完成"]); 408 | 409 | print("isolate_1 stop"); 410 | } 411 | ``` 412 | 413 | 运行主`Isolate`的结果: 414 | 415 | ```dart 416 | main isolate start 417 | main isolate stop 418 | isolate_1 start 419 | isolate_1 args: [hello, isolate, this is args] 420 | main isolate message: [0, SendPort] 421 | isolate_1 stop 422 | main isolate message: [1, isolate_1 任务完成] 423 | isolate_1 message: [1, 这条信息是 main isolate 发送的] 424 | ``` 425 | 426 | ![](https://img-blog.csdnimg.cn/20190505111443334.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 427 | 整个消息通信过程如上图所示,**两个Isolate是通过两对Port对象通信,一对Port分别由用于接收消息的`ReceivePort`对象,和用于发送消息的`SendPort`对象构成。其中`SendPort`对象不用单独创建,它已经包含在`ReceivePort`对象之中。需要注意,一对Port对象只能单向发消息,这就如同一根自来水管,`ReceivePort`和`SendPort`分别位于水管的两头,水流只能从`SendPort`这头流向`ReceivePort`这头。因此,两个`Isolate`之间的消息通信肯定是需要两根这样的水管的,这就需要两对Port对象。** 428 | 429 | 理解了`Isolate`消息通信的原理,那么在Dart代码中,具体是如何操作的呢? 430 | ![](https://img-blog.csdnimg.cn/20190505121830821.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 431 | **`ReceivePort`对象通过调用`listen`方法,传入一个函数可用来监听并处理发送来的消息。`SendPort`对象则调用`send()`方法来发送消息。`send`方法传入的参数可以是`null`,` num`, `bool`, `double`,`String`, `List` ,`Map`或者是自定义的类。** 在上例中,我们发送的是包含两个元素的`List`对象,第一个元素是整型,表示消息类型,第二个元素则表示消息内容。 432 | 433 | ### spawn 434 | `static Future spawn()` 435 | 436 | 除了使用`spawnUri`,更常用的是使用`spawn`方法来创建新的`Isolate`,我们通常希望将新创建的`Isolate`代码和`main Isolate`代码写在同一个文件,且不希望出现两个main函数,而是将指定的耗时函数运行在新的`Isolate`,这样做有利于代码的组织和代码的复用。`spawn`方法有两个必须的参数,第一个是需要运行在新`Isolate`的耗时函数,第二个是动态消息,该参数通常用于传送主`Isolate`的`SendPort`对象。 437 | 438 | `spawn`的用法与`spawnUri`相似,且更为简洁,将上面例子稍作修改如下 439 | 440 | ```dart 441 | import 'dart:isolate'; 442 | import 'dart:io'; 443 | 444 | void main() { 445 | print("main isolate start"); 446 | create_isolate(); 447 | print("main isolate end"); 448 | } 449 | 450 | // 创建一个新的 isolate 451 | create_isolate() async{ 452 | ReceivePort rp = new ReceivePort(); 453 | SendPort port1 = rp.sendPort; 454 | 455 | Isolate newIsolate = await Isolate.spawn(doWork, port1); 456 | 457 | SendPort port2; 458 | rp.listen((message){ 459 | print("main isolate message: $message"); 460 | if (message[0] == 0){ 461 | port2 = message[1]; 462 | }else{ 463 | port2?.send([1,"这条信息是 main isolate 发送的"]); 464 | } 465 | }); 466 | } 467 | 468 | // 处理耗时任务 469 | void doWork(SendPort port1){ 470 | print("new isolate start"); 471 | ReceivePort rp2 = new ReceivePort(); 472 | SendPort port2 = rp2.sendPort; 473 | 474 | rp2.listen((message){ 475 | print("doWork message: $message"); 476 | }); 477 | 478 | // 将新isolate中创建的SendPort发送到主isolate中用于通信 479 | port1.send([0, port2]); 480 | // 模拟耗时5秒 481 | sleep(Duration(seconds:5)); 482 | port1.send([1, "doWork 任务完成"]); 483 | 484 | print("new isolate end"); 485 | } 486 | ``` 487 | 488 | 运行结果: 489 | ``` 490 | main isolate start 491 | main isolate end 492 | new isolate start 493 | main isolate message: [0, SendPort] 494 | new isolate end 495 | main isolate message: [1, doWork 任务完成] 496 | doWork message: [1, 这条信息是 main isolate 发送的] 497 | ``` 498 | 499 | 无论是上面的`spawn`还是`spawnUri`,运行后都会包含两个Isolate,一个是主`Isolate`,一个是新`Isolate`,两个都双向绑定了消息通信的通道,即使新的`Isolate`中的任务完成了,它也不会立刻退出,因此,当使用完自己创建的`Isolate`后,最好调用`newIsolate.kill(priority: Isolate.immediate);`将`Isolate`立即杀死。 500 | 501 | ### Flutter 中创建Isolate 502 | 无论如何,在Dart中创建一个`Isolate`都显得有些繁琐,可惜的是Dart官方并未提供更高级封装。但是,如果想在Flutter中创建`Isolate`,则有更简便的API,这是由`Flutter`官方进一步封装`ReceivePort `而提供的更简洁API。[详细API文档](https://docs.flutter.io/flutter/foundation/compute.html) 503 | 504 | 使用`compute `函数来创建新的`Isolate`并执行耗时任务 505 | 506 | ```dart 507 | import 'package:flutter/foundation.dart'; 508 | import 'dart:io'; 509 | 510 | // 创建一个新的Isolate,在其中运行任务doWork 511 | create_new_task() async{ 512 | var str = "New Task"; 513 | var result = await compute(doWork, str); 514 | print(result); 515 | } 516 | 517 | 518 | String doWork(String value){ 519 | print("new isolate doWork start"); 520 | // 模拟耗时5秒 521 | sleep(Duration(seconds:5)); 522 | 523 | print("new isolate doWork end"); 524 | return "complete:$value"; 525 | } 526 | ``` 527 | 528 | `compute `函数有两个必须的参数,第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法,第二个参数为动态的消息类型,可以是被运行函数的参数。需要注意,使用`compute `应导入`'package:flutter/foundation.dart'`包。 529 | 530 | ### 使用场景 531 | `Isolate`虽好,但也有合适的使用场景,不建议滥用`Isolate`,应尽可能多的使用Dart中的事件循环机制去处理异步任务,这样才能更好的发挥Dart语言的优势。 532 | 533 | 那么应该在什么时候使用**Future**,什么时候使用**Isolate**呢? 534 | 一个最简单的判断方法是根据某些任务的平均时间来选择: 535 | * 方法执行在几毫秒或十几毫秒左右的,应使用`Future` 536 | * 如果一个任务需要几百毫秒或之上的,则建议创建单独的`Isolate` 537 | 538 | 除此之外,还有一些可以参考的场景 539 | 540 | * JSON 解码 541 | * 加密 542 | * 图像处理:比如剪裁 543 | 544 | 545 | 参考资料: 546 | [Dart 文档]( https://webdev.dartlang.org/articles/performance/event-loop) 547 | [Isolate 文档](https://api.dartlang.org/stable/2.3.0/dart-isolate/Isolate-class.html) 548 | 549 | 550 | 551 | # 视频课程 552 | 553 | 或关注博主视频课程,[**Flutter全栈式开发**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 554 | 555 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 556 | 557 | ------ 558 | 559 | **关注公众号:编程之路从0到1** 560 | 561 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 562 | -------------------------------------------------------------------------------- /Dart 网络编程.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dart 网络编程 3 | 以下提供Dart 关于网络编程方面的各种代码示例,对于具体的协议方面知识,请自行学习。 4 | 5 | ### TCP 服务端 6 | 7 | ```dart 8 | import 'dart:convert'; 9 | import 'dart:io'; 10 | 11 | void main() { 12 | //绑定本地localhost的8081端口(即绑定:127.0.0.1) 13 | ServerSocket.bind(InternetAddress.loopbackIPv4, 8081) 14 | .then((serverSocket) { 15 | serverSocket.listen((socket) { 16 | socket.cast>().transform(utf8.decoder).listen(print); 17 | }); 18 | }); 19 | } 20 | ``` 21 | 22 | 以上为简洁示例,不是非常清晰,等价于以下代码 23 | 24 | ```dart 25 | import 'dart:io'; 26 | import 'dart:convert'; 27 | 28 | void main() { 29 | startTCPServer(); 30 | } 31 | 32 | // TCP 服务端 33 | void startTCPServer() async{ 34 | ServerSocket serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 8081); 35 | 36 | //遍历所有连接到服务器的套接字 37 | await for(Socket socket in serverSocket) { 38 | // 先将字节流以utf-8进行解码 39 | socket.cast>().transform(utf8.decoder).listen((data) { 40 | print("from ${socket.remoteAddress.address} data:" + data); 41 | socket.add(utf8.encode('hello client!')); 42 | }); 43 | } 44 | } 45 | ``` 46 | 47 | ### TCP 客户端 48 | 49 | 对应的简洁表达如下 50 | 51 | ```dart 52 | import 'dart:convert'; 53 | import 'dart:io'; 54 | 55 | void main() { 56 | // 连接127.0.0.1的8081端口 57 | Socket.connect('127.0.0.1', 8081).then((socket) { 58 | socket.write('Hello, Server!'); 59 | socket.cast>().transform(utf8.decoder).listen(print); 60 | }); 61 | } 62 | ``` 63 | 64 | 更清晰写法如下 65 | 66 | ```dart 67 | import 'dart:convert'; 68 | import 'dart:io'; 69 | 70 | void main() { 71 | startTCPClient(); 72 | } 73 | 74 | // TCP 客户端 75 | void startTCPClient() async { 76 | //连接服务器的8081端口 77 | Socket socket = await Socket.connect('127.0.0.1', 8081); 78 | socket.write('Hello, Server!'); 79 | socket.cast>().transform(utf8.decoder).listen(print); 80 | } 81 | ``` 82 | 83 | ### UDP 服务端 84 | 85 | ```dart 86 | import 'dart:io'; 87 | import 'dart:convert'; 88 | 89 | void main() { 90 | startUDPServer(); 91 | } 92 | 93 | // UDP 服务端 94 | void startUDPServer() async { 95 | RawDatagramSocket rawDgramSocket = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, 8081); 96 | 97 | //监听套接字事件 98 | await for (RawSocketEvent event in rawDgramSocket) { 99 | // 数据包套接字不能监听数据,而是监听事件。 100 | // 当事件为RawSocketEvent.read的时候才能通过receive函数接收数据 101 | if(event == RawSocketEvent.read) { 102 | print(utf8.decode(rawDgramSocket.receive().data)); 103 | rawDgramSocket.send(utf8.encode("UDP Server:already received!"), InternetAddress.loopbackIPv4, 8082); 104 | } 105 | } 106 | } 107 | ``` 108 | 109 | ### UDP 客户端 110 | 111 | ```dart 112 | import 'dart:convert'; 113 | import 'dart:io'; 114 | 115 | void main() { 116 | startUDPClent(); 117 | } 118 | 119 | // UDP 客户端 120 | void startUDPClent() async { 121 | RawDatagramSocket rawDgramSocket = await RawDatagramSocket.bind('127.0.0.1', 8082); 122 | 123 | rawDgramSocket.send(utf8.encode("hello,world!"), InternetAddress('127.0.0.1'), 8081); 124 | 125 | //监听套接字事件 126 | await for (RawSocketEvent event in rawDgramSocket) { 127 | if(event == RawSocketEvent.read) { 128 | // 接收数据 129 | print(utf8.decode(rawDgramSocket.receive().data)); 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | ### HTTP服务器与请求 136 | 137 | 关于HTTP的详细说明,可参加本人 [相关博客](https://blog.csdn.net/yingshukun/category_9281313.html) 138 | 139 | ```dart 140 | import 'dart:io'; 141 | 142 | void main() { 143 | HttpServer 144 | .bind(InternetAddress.loopbackIPv4, 8080) 145 | .then((server) { 146 | server.listen((HttpRequest request) { 147 | // 打印请求的path 148 | print(request.uri.path); 149 | if(request.uri.path.startsWith("/greet")){ 150 | var subPathList = request.uri.path.split("/"); 151 | 152 | if(subPathList.length >=3){ 153 | request.response.write('Hello, ${subPathList[2]}'); 154 | request.response.close(); 155 | }else{ 156 | request.response.write('Hello, '); 157 | request.response.close(); 158 | } 159 | }else{ 160 | request.response.write('Welcome to test server!'); 161 | request.response.close(); 162 | } 163 | }); 164 | }); 165 | } 166 | ``` 167 | 168 | 浏览器输入`http://localhost:8080/greet/zhangsan`访问 169 | 170 | 以上是使用浏览器向服务器发出请求,接下来我们使用代码模拟浏览器发请求 171 | 172 | ```dart 173 | import 'dart:convert'; 174 | import 'dart:io'; 175 | 176 | void main() { 177 | HttpClient client = HttpClient(); 178 | 179 | client.getUrl(Uri.parse("https://www.baidu.com/")) 180 | .then((HttpClientRequest request) { 181 | // 设置请求头 182 | request.headers.add(HttpHeaders.userAgentHeader, 183 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36"); 184 | return request.close(); 185 | }) 186 | .then((HttpClientResponse response) { 187 | // 处理响应 188 | response.transform(utf8.decoder).listen((contents) { 189 | print(contents); 190 | }); 191 | }); 192 | } 193 | ``` 194 | 195 | 通常的,我们并不会直接使用Dart 标准库提供的`http` 网络请求API,因为标准库库使用上仍然过于繁琐,第三方库则更加简洁强大。在Flutter上,主要使用`dio`库,功能十分强大,另外还可以使用官方推出的`http`库,更加简洁精炼,链接如下 196 | 197 | - [http](https://pub.dev/packages/http) 198 | - [dio](https://pub.dev/packages/dio) 199 | 200 | ### WebSocket 201 | 202 | > **WebSocket**是一种在单个[TCP](https://baike.baidu.com/item/TCP)连接上进行[全双工](https://baike.baidu.com/item/%E5%85%A8%E5%8F%8C%E5%B7%A5)通信的协议。它的出现使得客户端和服务端都可以主动的推送消息,可以是文本也可以是二进制数据。而且没有同源策略的限制,不存在跨域问题。协议的标识符就是`ws`。像https一样如果加密的话就是`wxs`。 203 | 204 | WebSocket 是独立的、创建在 TCP 上的协议。 205 | 206 | Websocket 通过[HTTP](https://baike.baidu.com/item/HTTP)/1.1 协议的101状态码进行握手。 207 | 208 | 为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking) 209 | 210 | #### 服务端 211 | 212 | Web套接字服务器使用普通的HTTP服务器来接受Web套接字连接。初始握手是HTTP请求,然后将其升级为Web套接字连接。服务器使用[WebSocketTransformer](https://api.dart.dev/stable/2.7.1/dart-io/WebSocketTransformer-class.html)升级请求, 并侦听返回的Web套接字上的数据 213 | 214 | ```dart 215 | import 'dart:io'; 216 | 217 | void main() async { 218 | HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8083); 219 | await for (HttpRequest req in server) { 220 | if (req.uri.path == '/ws') { 221 | // 将一个HttpRequest转为WebSocket连接 222 | WebSocket socket = await WebSocketTransformer.upgrade(req); 223 | socket.listen((data) { 224 | print("from IP ${req.connectionInfo.remoteAddress.address}:${data}"); 225 | socket.add("WebSocket Server:already received!"); 226 | }); 227 | } 228 | } 229 | } 230 | ``` 231 | 232 | #### 客户端 233 | 234 | ```dart 235 | import 'dart:io'; 236 | 237 | void main() async { 238 | WebSocket socket = await WebSocket.connect('ws://127.0.0.1:8083/ws'); 239 | socket.add('Hello, World!'); 240 | 241 | await for (var data in socket) { 242 | print("from Server: $data"); 243 | 244 | // 关闭连接 245 | socket.close(); 246 | } 247 | } 248 | 249 | ``` 250 | 251 | 252 | 注意:本篇内容主要为Dart编程示例,在实际开发中,还有许多问题需要处理,例如TCP的粘包问题,心跳机制,并在Dart中将WebSocket结合ProtoBuf使用等,相关内容请关注后续的Flutter项目实战课程。 253 | 254 | # 视频课程 255 | 256 | 或关注博主视频课程,[**Flutter全栈式开发**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 257 | 258 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 259 | 260 | ------ 261 | 262 | **关注公众号:编程之路从0到1** 263 | 264 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 265 | -------------------------------------------------------------------------------- /Dart 语言Stream详解.md: -------------------------------------------------------------------------------- 1 | 2 | # 异步之 Stream 详解 3 | 关于Dart 语言的Stream 部分,应该回到语言本身去寻找答案,许多资料在Flutter框架中囫囵吞枣式的解释`Stream`,总有一种让人云山雾罩的感觉,事实上从Dart语言本身去了解Stream并不复杂,接下来就花点时间好好学习一下`Stream`吧! 4 | 5 | `Stream`和 `Future`都是Dart中异步编程的核心内容,在之前的文章中已经详细叙述了关于`Future`的知识,请查看 [Dart 异步编程详解](https://blog.csdn.net/yingshukun/article/details/89840009),本篇文章则主要基于 Dart2.5 介绍`Stream`的知识。 6 | 7 | 8 | ## 什么是Stream 9 | `Stream`是Dart语言中的所谓异步数据序列的东西,简单理解,其实就是一个异步数据队列而已。我们知道队列的特点是先进先出的,`Stream`也正是如此 10 | 11 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190915152423489.gif) 12 | 13 | 更形象的比喻,`Stream`就像一个传送带。可以将一侧的物品自动运送到另一侧。如上图,在另一侧,如果没有人去抓取,物品就会掉落消失。 14 | 15 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190915152851279.gif) 16 | 17 | 但如果我们在末尾设置一个监听,当物品到达末端时,就可以触发相应的响应行为。 18 | 19 | > 在Dart语言中,`Stream`有两种类型,一种是点对点的单订阅流(Single-subscription),另一种则是广播流。 20 | 21 | ## 单订阅流 22 | 单订阅流的特点是只允许存在一个监听器,即使该监听器被取消后,也不允许再次注册监听器。 23 | 24 | ### 创建 Stream 25 | 创建一个`Stream`有9个构造方法,其中一个是构造广播流的,这里主要看一下其中5个构造单订阅流的方法 26 | 27 | #### periodic 28 | ```java 29 | void main(){ 30 | test(); 31 | } 32 | 33 | test() async{ 34 | // 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数 35 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 36 | // await for循环从流中读取 37 | await for(var i in stream){ 38 | print(i); 39 | } 40 | } 41 | 42 | // 可以在回调函数中对值进行处理,这里直接返回了 43 | int callback(int value){ 44 | return value; 45 | } 46 | ``` 47 | 48 | 打印结果: 49 | ``` 50 | 0 51 | 1 52 | 2 53 | 3 54 | 4 55 | ... 56 | ``` 57 | 58 | 该方法从整数0开始,在指定的间隔时间内生成一个自然数列,以上设置为每一秒生成一次,`callback`函数用于对生成的整数进行处理,处理后再放入`Stream`中。这里并未处理,直接返回了。要注意,这个流是无限的,它没有任何一个约束条件使之停止。在后面会介绍如何给流设置条件。 59 | 60 | #### fromFuture 61 | 62 | ```java 63 | void main(){ 64 | test(); 65 | } 66 | 67 | test() async{ 68 | print("test start"); 69 | Future fut = Future((){ 70 | return "async task"; 71 | }); 72 | 73 | // 从Future创建Stream 74 | Stream stream = Stream.fromFuture(fut); 75 | await for(var s in stream){ 76 | print(s); 77 | } 78 | print("test end"); 79 | } 80 | ``` 81 | 打印结果: 82 | ``` 83 | test start 84 | async task 85 | test end 86 | ``` 87 | 该方法从一个`Future`创建`Stream`,当`Future`执行完成时,就会放入`Stream`中,而后从`Stream`中将任务完成的结果取出。这种用法,很像异步任务队列。 88 | 89 | #### fromFutures 90 | 从多个`Future`创建`Stream`,即将一系列的异步任务放入`Stream`中,每个`Future`按顺序执行,执行完成后放入`Stream` 91 | 92 | ```java 93 | import 'dart:io'; 94 | 95 | void main() { 96 | test(); 97 | } 98 | 99 | test() async{ 100 | print("test start"); 101 | Future fut1 = Future((){ 102 | // 模拟耗时5秒 103 | sleep(Duration(seconds:5)); 104 | return "async task1"; 105 | }); 106 | Future fut2 = Future((){ 107 | return "async task2"; 108 | }); 109 | 110 | // 将多个Future放入一个列表中,将该列表传入 111 | Stream stream = Stream.fromFutures([fut1,fut2]); 112 | await for(var s in stream){ 113 | print(s); 114 | } 115 | print("test end"); 116 | } 117 | ``` 118 | 119 | #### fromIterable 120 | 该方法从一个集合创建`Stream`,用法与上面例子大致相同 121 | ```java 122 | // 从一个列表创建`Stream` 123 | Stream stream = Stream.fromIterable([1,2,3]); 124 | ``` 125 | 126 | #### value 127 | 这是Dart2.5 新增的方法,用于从单个值创建`Stream` 128 | ```java 129 | test() async{ 130 | Stream stream = Stream.value(false); 131 | // await for循环从流中读取 132 | await for(var i in stream){ 133 | print(i); 134 | } 135 | } 136 | ``` 137 | 138 | ### 监听 Stream 139 | 监听`Stream`,并从中获取数据也有三种方式,一种就是我们上文中使用的`await for`循环,这也是官方推荐的方式,看起来更简洁友好,除此之外,另两种方式分别是使用`forEach`方法或`listen`方法 140 | 141 | ```java 142 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 143 | // 使用forEach,传入一个函数进去获取并处理数据 144 | stream.forEach((int x){ 145 | print(x); 146 | }); 147 | ``` 148 | 149 | 使用 `listen` 监听 150 | `StreamSubscription listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError})` 151 | ```java 152 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 153 | stream.listen((x){ 154 | print(x); 155 | }); 156 | ``` 157 | 158 | 还可以使用几个可选的参数 159 | ```java 160 | test() async{ 161 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 162 | stream = stream.take(5); 163 | stream.listen( 164 | (x)=>print(x), 165 | onError: (e)=>print(e), 166 | onDone: ()=>print("onDone")); 167 | } 168 | ``` 169 | 170 | - `onError`:发生Error时触发 171 | - `onDone`:完成时触发 172 | - `unsubscribeOnError`:遇到第一个Error时是否取消监听,默认为`false` 173 | 174 | ### Stream 的一些方法 175 | #### take 和 takeWhile 176 | `Stream take(int count)` 用于限制`Stream`中的元素数量 177 | 178 | ```java 179 | test() async{ 180 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 181 | // 当放入三个元素后,监听会停止,Stream会关闭 182 | stream = stream.take(3); 183 | 184 | await for(var i in stream){ 185 | print(i); 186 | } 187 | } 188 | ``` 189 | 打印结果: 190 | ``` 191 | 0 192 | 1 193 | 2 194 | ``` 195 | `Stream.takeWhile(bool test(T element))` 与 `take`作用相似,只是它的参数是一个函数类型,且返回值必须是一个`bool`值 196 | ```java 197 | stream = stream.takeWhile((x){ 198 | // 对当前元素进行判断,不满足条件则取消监听 199 | return x <= 3; 200 | }); 201 | 202 | ``` 203 | 204 | #### skip 和 skipWhile 205 | 206 | ```java 207 | test() async{ 208 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 209 | stream = stream.take(5); 210 | // 表示从Stream中跳过两个元素 211 | stream = stream.skip(2); 212 | 213 | await for(var i in stream){ 214 | print(i); 215 | } 216 | } 217 | ``` 218 | 219 | 打印结果: 220 | ``` 221 | 2 222 | 3 223 | 4 224 | ``` 225 | 请注意,该方法只是从`Stream`中获取元素时跳过,被跳过的元素依然是被执行了的,所耗费的时间依然存在,其实只是跳过了执行完的结果而已。 226 | 227 | `Stream skipWhile(bool test(T element))` 方法与`takeWhile`用法是相同的,传入一个函数对结果进行判断,表示跳过满足条件的。 228 | 229 | #### toList 230 | `Future> toList()` 表示将`Stream`中所有数据存储在List中 231 | 232 | ```java 233 | test() async{ 234 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 235 | stream = stream.take(5); 236 | List data = await stream.toList(); 237 | for(var i in data){ 238 | print(i); 239 | } 240 | } 241 | ``` 242 | 243 | #### 属性 length 244 | 等待并获取流中所有数据的数量 245 | ```java 246 | test() async{ 247 | Stream stream = Stream.periodic(Duration(seconds: 1), callback); 248 | stream = stream.take(5); 249 | var len = await stream.length; 250 | print(len); 251 | } 252 | ``` 253 | 254 | ### StreamController 255 | 它实际上就是`Stream`的一个帮助类,可用于整个 `Stream` 过程的控制。 256 | 257 | ```java 258 | import 'dart:async'; 259 | 260 | void main() { 261 | test(); 262 | } 263 | 264 | test() async{ 265 | // 创建 266 | StreamController streamController = StreamController(); 267 | // 放入事件 268 | streamController.add('element_1'); 269 | streamController.addError("this is error"); 270 | streamController.sink.add('element_2'); 271 | streamController.stream.listen( 272 | print, 273 | onError: print, 274 | onDone: ()=>print("onDone")); 275 | } 276 | ``` 277 | 278 | 使用该类时,需要导入`'dart:async'`,其`add`方法和`sink.add`方法是相同的,都是用于放入一个元素,`addError`方法用于产生一个错误,监听方法中的`onError`可获取错误。 279 | 280 | 还可以在`StreamController`中传入一个指定的`stream` 281 | ```java 282 | test() async{ 283 | Stream stream = Stream.periodic(Duration(seconds: 1), (e)=>e); 284 | stream = stream.take(5); 285 | 286 | StreamController sc = StreamController(); 287 | // 将 Stream 传入 288 | sc.addStream(stream); 289 | // 监听 290 | sc.stream.listen( 291 | print, 292 | onDone: ()=>print("onDone")); 293 | } 294 | ``` 295 | 296 | 现在来看一下`StreamController`的原型,它有5个可选参数 297 | ```dart 298 | factory StreamController( 299 | {void onListen(), 300 | void onPause(), 301 | void onResume(), 302 | onCancel(), 303 | bool sync: false}) 304 | ``` 305 | 306 | - `onListen` 注册监听时回调 307 | - `onPause` 当流暂停时回调 308 | - `onResume` 当流恢复时回调 309 | - `onCancel` 当监听器被取消时回调 310 | - `sync` 当值为`true`时表示同步控制器`SynchronousStreamController`,默认值为`false`,表示异步控制器 311 | 312 | ```java 313 | test() async{ 314 | // 创建 315 | StreamController sc = StreamController( 316 | onListen: ()=>print("onListen"), 317 | onPause: ()=>print("onPause"), 318 | onResume: ()=>print("onResume"), 319 | onCancel: ()=>print("onCancel"), 320 | sync:false 321 | ); 322 | 323 | StreamSubscription ss = sc.stream.listen(print); 324 | 325 | sc.add('element_1'); 326 | 327 | // 暂停 328 | ss.pause(); 329 | // 恢复 330 | ss.resume(); 331 | // 取消 332 | ss.cancel(); 333 | 334 | // 关闭流 335 | sc.close(); 336 | } 337 | ``` 338 | 打印结果: 339 | ``` 340 | onListen 341 | onPause 342 | onCancel 343 | ``` 344 | 345 | 因为监听器被取消了,且关闭了流,导致`"element_1"`未被输出,`"onResume"`亦未输出 346 | 347 | ## 广播流 348 | 如下,在普通的单订阅流中调用两次`listen`会报错 349 | ```java 350 | test() async{ 351 | Stream stream = Stream.periodic(Duration(seconds: 1), (e)=>e); 352 | stream = stream.take(5); 353 | 354 | stream.listen(print); 355 | stream.listen(print); 356 | } 357 | ``` 358 | 359 | ``` 360 | Unhandled exception: 361 | Bad state: Stream has already been listened to. 362 | ``` 363 | 364 | 前面已经说了单订阅流的特点,而广播流则可以允许多个监听器存在,就如同广播一样,凡是监听了广播流,每个监听器都能获取到数据。要注意,如果在触发事件时将监听者正添加到广播流,则该监听器将不会接收当前正在触发的事件。如果取消监听,监听者会立即停止接收事件。 365 | 366 | 有两种方式创建广播流,一种直接从`Stream`创建,另一种使用`StreamController`创建 367 | ```java 368 | test() async{ 369 | // 调用 Stream 的 asBroadcastStream 方法创建 370 | Stream stream = Stream.periodic(Duration(seconds: 1), (e)=>e) 371 | .asBroadcastStream(); 372 | stream = stream.take(5); 373 | 374 | stream.listen(print); 375 | stream.listen(print); 376 | } 377 | ``` 378 | 379 | 使用`StreamController` 380 | ```java 381 | test() async{ 382 | // 创建广播流 383 | StreamController sc = StreamController.broadcast(); 384 | 385 | sc.stream.listen(print); 386 | sc.stream.listen(print); 387 | 388 | sc.add("event1"); 389 | sc.add("event2"); 390 | } 391 | ``` 392 | 393 | ## StreamTransformer 394 | 该类可以使我们在`Stream`上执行数据转换。然后,这些转换被推回到流中,以便该流注册的所有监听器可以接收 395 | 396 | **构造方法原型** 397 | ```dart 398 | factory StreamTransformer.fromHandlers({ 399 | void handleData(S data, EventSink sink), 400 | void handleError(Object error, StackTrace stackTrace, EventSink sink), 401 | void handleDone(EventSink sink) 402 | }) 403 | ``` 404 | 405 | - `handleData`:响应从流中发出的任何数据事件。提供的参数是来自发出事件的数据,以及`EventSink`,表示正在进行此转换的当前流的实例 406 | - `handleError`:响应从流中发出的任何错误事件 407 | - `handleDone`:当流不再有数据要处理时调用。通常在流的`close()`方法被调用时回调 408 | 409 | ```java 410 | void test() { 411 | StreamController sc = StreamController(); 412 | 413 | // 创建 StreamTransformer对象 414 | StreamTransformer stf = StreamTransformer.fromHandlers( 415 | handleData: (int data, EventSink sink) { 416 | // 操作数据后,转换为 double 类型 417 | sink.add((data * 2).toDouble()); 418 | }, 419 | handleError: (error, stacktrace, sink) { 420 | sink.addError('wrong: $error'); 421 | }, 422 | handleDone: (sink) { 423 | sink.close(); 424 | }, 425 | ); 426 | 427 | // 调用流的transform方法,传入转换对象 428 | Stream stream = sc.stream.transform(stf); 429 | 430 | stream.listen(print); 431 | 432 | // 添加数据,这里的类型是int 433 | sc.add(1); 434 | sc.add(2); 435 | sc.add(3); 436 | 437 | // 调用后,触发handleDone回调 438 | // sc.close(); 439 | } 440 | ``` 441 | 442 | 打印结果: 443 | ``` 444 | 2.0 445 | 4.0 446 | 6.0 447 | ``` 448 | 449 | ## 总结 450 | 与流相关的操作,主要有四个类 451 | - `Stream` 452 | - `StreamController` 453 | - `StreamSink` 454 | - `StreamSubscription` 455 | 456 | `Stream`是基础,为了更方便控制和管理`Stream`,出现了`StreamController`类。在`StreamController`类中, 提供了`StreamSink` 作为事件输入口,当我们调用`add`时,实际上是调用的`sink.add`,通过`sink`属性可以获取`StreamController`类中的`StreamSink` ,而`StreamSubscription`类则用于管理事件的注册、暂停与取消等,通过调用`stream.listen`方法返回一个`StreamSubscription`对象。 457 | 458 | 459 | 460 | # 视频课程 461 | 462 | 或关注博主视频课程,[**Flutter全栈式开发**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 463 | 464 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 465 | 466 | ------ 467 | 468 | **关注公众号:编程之路从0到1** 469 | 470 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 471 | -------------------------------------------------------------------------------- /Dart 语言标准流与文件操作.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 标准输入输出流 4 | - `stdin` 5 | - `stdout` 6 | - `stderr` 7 | 8 | 9 | ```java 10 | // 导入io包 11 | import 'dart:io'; 12 | 13 | void main() { 14 | // 向标准输出流写字符串 15 | stdout.write('root\$:'); 16 | // 从标准输入流读取一行字符串 17 | var input = stdin.readLineSync(); 18 | // 带换行符的写数据 19 | stdout.writeln("input data:$input"); 20 | // 向标准错误流写数据 21 | stderr.writeln("has not error"); 22 | } 23 | ``` 24 | 25 | `stdin`除了可以使用`readLineSync`读一行字符串,还可以使用`readByteSync`读取一个字节。 26 | 27 | # 文件操作 28 | ## 写文件 29 | 一种简便的操作方式,无需手动关闭文件,文件写入完成后会自动关闭 30 | ```java 31 | import 'dart:io'; 32 | 33 | void main() async{ 34 | // 创建文件 35 | File file = new File('test.txt'); 36 | String content = 'The easiest way to write text to a file is to create a File'; 37 | 38 | try { 39 | // 向文件写入字符串 40 | await file.writeAsString(content); 41 | print('Data written.'); 42 | } catch (e) { 43 | print(e); 44 | } 45 | } 46 | ``` 47 | 48 | `writeAsString`原型 49 | ```dart 50 | Future writeAsString(String contents, 51 | {FileMode mode: FileMode.write, 52 | Encoding encoding: utf8, 53 | bool flush: false}) 54 | ``` 55 | 56 | - `mode` 文件模式,这里默认为写模式 57 | - `encoding` 字符编码,默认为utf-8 58 | - `flush` 是否立刻刷新缓存,默认为false 59 | 60 | 文件模式`FileMode`的常量 61 | |常量值| 说明 | 62 | |:--|:--| 63 | | read | 只读模式 | 64 | | write | 可读可写模式,如果文件存在则会覆盖 | 65 | | append | 追加模式,可读可写,文件存在则往末尾追加 | 66 | | writeOnly | 只写模式 | 67 | | writeOnlyAppend | 只写模式下的追加模式,不可读 | 68 | 69 | 70 | 71 | 72 | 除了`writeAsString`方法外,还可以使用`writeAsBytes`写入一个字节列表。需要注意的是,这两个方法都是异步执行的,返回值都是`Future`,如果有必要,也可以使用同步方法执行写入操作 73 | 74 | - `writeAsStringSync` 75 | - `writeAsBytesSync` 76 | 77 | 如需要更灵活的控制,可以使用如下方式操作文件,但是需要手动关闭文件 78 | 79 | ```java 80 | import 'dart:io'; 81 | 82 | void main() async{ 83 | // 创建文件 84 | File file = new File('test.txt'); 85 | // 文件模式设置为追加 86 | IOSink isk = file.openWrite(mode: FileMode.append); 87 | 88 | // 多次写入 89 | isk.write('A woman is like a tea bag'); 90 | isk.writeln('you never know how strong it is until it\'s in hot water.'); 91 | isk.writeln('-Eleanor Roosevelt'); 92 | await isk.close(); 93 | print('Done!'); 94 | } 95 | ``` 96 | 97 | 98 | ## 读文件 99 | 简便的方式 100 | - `readAsBytes` 101 | - `readAsBytesSync` 102 | - `readAsString` 103 | - `readAsStringSync` 104 | - `readAsLines` 105 | - `readAsLinesSync` 106 | 107 | ```java 108 | void main() async{ 109 | File file = new File('test.txt'); 110 | try{ 111 | String content = await file.readAsString(); 112 | print(content); 113 | }catch(e){ 114 | print(e); 115 | } 116 | } 117 | ``` 118 | 119 | 另一种更低级别的方式 120 | ```java 121 | import 'dart:io'; 122 | import 'dart:convert'; 123 | 124 | void main() async{ 125 | try { 126 | // LineSplitter Dart语言封装的换行符,此处将文本按行分割 127 | Stream lines = new File('test.txt').openRead() 128 | .transform(utf8.decoder).transform(const LineSplitter()); 129 | 130 | await for (var line in lines) { 131 | print(line); 132 | } 133 | } catch (_) {} 134 | } 135 | ``` 136 | 137 | ## 文件的其他操作 138 | ```java 139 | import 'dart:io'; 140 | 141 | void main() async{ 142 | File file = new File('test.txt'); 143 | 144 | // 判断文件是否存在 145 | if(await file.exists()){ 146 | print("文件存在"); 147 | }else{ 148 | print("文件不存在"); 149 | } 150 | 151 | // 复制文件 152 | await file.copy("test-1.txt"); 153 | 154 | // 修改文件名。当传入不同路径时,可用来移动文件 155 | await file.rename("test-2.txt"); 156 | 157 | // 获取文件 size 158 | print(await file.length()); 159 | } 160 | ``` 161 | 相应的,这些方法还有一个带`Sync`后缀的同步版本方法,例如`copySync`、`renameSync`等。 162 | 163 | 要获取文件更多的信息,还可以使用`File`等多个类的超类`FileSystemEntity`来操作 164 | 165 | ```java 166 | import 'dart:io'; 167 | 168 | void main() async{ 169 | String path = 'test.txt'; 170 | 171 | // 判断路径是否是文件夹 172 | if (!await FileSystemEntity.isDirectory(path)) { 173 | print('$path is not a directory'); 174 | } 175 | 176 | Directory dir = Directory(r'D:\workspace\dart_space\Tutorial'); 177 | // 目录是否存在 178 | if(await dir.exists()){ 179 | // 从目录的list方法获取FileSystemEntity对象 180 | Stream fse = await dir.list(); 181 | await for (FileSystemEntity entity in fse) { 182 | if(entity is File){ 183 | print("entity is file"); 184 | } 185 | 186 | // 打印文件信息 187 | print(await entity.stat()); 188 | // 删除 189 | await entity.delete(); 190 | } 191 | }else{ 192 | // 不存在则创建。recursive为true时,创建路径上所有不存在的目录 193 | await dir.create(recursive: true); 194 | } 195 | } 196 | ``` 197 | 198 | 需注意,`delete`中包含一个可选的参数,原型` Future delete({bool recursive: false})`,`recursive`默认为false,当删除目录时,目录必须为空才能删除;当`recursive`设置为true时,将删除目录下的所有子目录及文件。 199 | 200 | 201 | ------ 202 | 203 | **关注公众号:编程之路从0到1** 204 | 205 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 206 | 207 | # 视频课程 208 | 或关注博主视频,[**Flutter全栈式开发**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 209 | -------------------------------------------------------------------------------- /Dart 调用C语言混合编程.md: -------------------------------------------------------------------------------- 1 | # ~~Dart 调用C语言~~ 2 | 本篇博客研究Dart语言如何调用C语言代码混合编程,最后我们实现一个简单示例,在C语言中编写简单加解密函数,使用dart调用并传入字符串,返回加密结果,调用解密函数,恢复字符串内容。 3 | 4 | **随着Dart SDK版本迭代,本文章部分内容已过时,最新版本教程已经上传B站,请查看 [Dart FFI 入门](https://www.bilibili.com/video/BV1v44y1i7ed)** 5 | 6 | ## 环境准备 7 | ### 编译器环境 8 | 如未安装过VS编译器,则推荐使用GCC编译器,下载一个64位Windows版本的GCC——`MinGW-W64` 9 | [下载地址](https://sourceforge.net/projects/mingw-w64/files/) 10 | 11 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190517193751536.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 12 | 如上,它有两个版本,`sjlj`和`seh`后缀表示异常处理模式,`seh` 性能较好,但不支持 32位。 `sjlj` 稳定性好,可支持 32位,推荐下载`seh` 版本 13 | 14 | 将编译器安装到指定的目录,完成安装后,还需要配置一下环境变量,将安装目录下的`bin`目录加入到系统Path环境变量中,`bin`目录下包含`gcc.exe`、`make.exe`等工具链。 15 | 16 | **测试环境** 17 | 配置完成后,检测一下环境是否搭建成功,打开`cmd`命令行,输入`gcc -v`能查看版本号则成功。 18 | 19 | ### Dart SDK环境 20 | 去往Dart 官网下载最新的2.3 版本SDK,注意,旧版本不支持`ffi` [下载地址](https://dart.dev/tools/sdk/archive) 21 | 22 | 下载安装后,同样需要配置环境变量,将`dart-sdk\bin`配置到系统Path环境变量中。 23 | 24 | 25 | ## 测试Dart `ffi`接口 26 | 关于C语言相关的各种知识,包括构建、动态库编译与加载,请学习我的[《C语言专栏》](https://blog.csdn.net/yingshukun/category_9291402.html),只有掌握这些基础知识,才能应对各种报错问题。 27 | ### 简单示例 28 | 创建测试工程,打开`cmd`命令行 29 | ``` 30 | mkdir ffi-proj 31 | cd ffi-proj 32 | mkdir bin src 33 | ``` 34 | 35 | 创建工程目录`ffi-proj`,在其下创建`bin `、`src`文件夹,在`bin`中创建`main.dart`文件,在`src`中创建`test.c`文件 36 | 37 | 编写`test.c` 38 | 我们在其中包含了windows头文件,用于`showBox`函数,调用Win32 API,创建一个对话框 39 | ```c 40 | #include 41 | 42 | int add(int a, int b){ 43 | return a + b; 44 | } 45 | 46 | 47 | void showBox(){ 48 | MessageBox(NULL,"Hello Dart","Title",MB_OK); 49 | } 50 | ``` 51 | 52 | 进入`src`目录下,使用gcc编译器,将C语言代码编译为dll动态库 53 | ```shell 54 | gcc test.c -shared -o test.dll 55 | ``` 56 | 57 | 58 | 编写`main.dart` 59 | 60 | ```dart 61 | import 'dart:ffi' as ffi; 62 | import 'dart:io' show Platform; 63 | 64 | /// 根据C中的函数来定义方法签名(所谓方法签名,就是对一个方法或函数的描述,包括返回值类型,形参类型) 65 | /// 这里需要定义两个方法签名,一个是C语言中的,一个是转换为Dart之后的 66 | typedef NativeAddSign = ffi.Int32 Function(ffi.Int32,ffi.Int32); 67 | typedef DartAddSign = int Function(int, int); 68 | 69 | /// showBox函数方法签名 70 | typedef NativeShowSign = ffi.Void Function(); 71 | typedef DartShowSign = void Function(); 72 | 73 | void main(List args) { 74 | if (Platform.isWindows) { 75 | // 加载dll动态库 76 | ffi.DynamicLibrary dl = ffi.DynamicLibrary.open("../src/test.dll"); 77 | 78 | // lookupFunction有两个作用,1、去动态库中查找指定的函数;2、将Native类型的C函数转化为Dart的Function类型 79 | var add = dl.lookupFunction("add"); 80 | var showBox = dl.lookupFunction("showBox"); 81 | 82 | // 调用add函数 83 | print(add(8, 9)); 84 | // 调用showBox函数 85 | showBox(); 86 | } 87 | } 88 | ``` 89 | 90 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190517200625101.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70) 91 | 92 | ### 深入用法 93 | 这里写一个稍微深入一点的示例,我们在C语言中写一个简单加密算法,然后使用dart调用C函数加密解密 94 | 95 | 编写`encrypt_test.c`,这里写一个最简单的异或加密算法,可以看到加密和解密实际上是一样的 96 | ```c 97 | #include 98 | 99 | #define KEY 'abc' 100 | 101 | void encrypt(char *str, char *r, int r_len){ 102 | int len = strlen(str); 103 | for(int i = 0; i < len && i < r_len; i++){ 104 | r[i] = str[i] ^ KEY; 105 | } 106 | 107 | if (r_len > len) r[len] = '\0'; 108 | else r[r_len] = '\0'; 109 | 110 | } 111 | 112 | void decrypt(char *str, char *r, int r_len){ 113 | int len = strlen(str); 114 | for(int i = 0; i < len && i < r_len; i++){ 115 | r[i] = str[i] ^ KEY; 116 | } 117 | 118 | if (r_len > len) r[len] = '\0'; 119 | else r[r_len] = '\0'; 120 | } 121 | ``` 122 | 123 | 编译为动态库 124 | ``` 125 | gcc encrypt_test.c -shared -o encrypt_test.dll 126 | ``` 127 | 128 | 编写`main.dart` 129 | 130 | ```dart 131 | import 'dart:ffi'; 132 | import 'dart:io' show Platform; 133 | import "dart:convert"; 134 | 135 | 136 | /// encrypt函数方法签名,注意,这里encrypt和decrypt的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同 137 | typedef NativeEncrypt = Void Function(CString,CString,Int32); 138 | typedef DartEncrypt = void Function(CString,CString,int); 139 | 140 | 141 | void main(List args) { 142 | if (Platform.isWindows) { 143 | // 加载dll动态库 144 | DynamicLibrary dl = DynamicLibrary.open("../src/encrypt_test.dll"); 145 | var encrypt = dl.lookupFunction("encrypt"); 146 | var decrypt = dl.lookupFunction("decrypt"); 147 | 148 | 149 | CString data = CString.allocate("helloworld"); 150 | CString enResult = CString.malloc(100); 151 | encrypt(data,enResult,100); 152 | print(CString.fromUtf8(enResult)); 153 | 154 | print("-------------------------"); 155 | 156 | CString deResult = CString.malloc(100); 157 | decrypt(enResult,deResult,100); 158 | print(CString.fromUtf8(deResult)); 159 | } 160 | } 161 | 162 | /// 创建一个类继承Pointer指针,用于处理C语言字符串和Dart字符串的映射 163 | class CString extends Pointer { 164 | 165 | /// 申请内存空间,将Dart字符串转为C语言字符串 166 | factory CString.allocate(String dartStr) { 167 | List units = Utf8Encoder().convert(dartStr); 168 | Pointer str = allocate(count: units.length + 1); 169 | for (int i = 0; i < units.length; ++i) { 170 | str.elementAt(i).store(units[i]); 171 | } 172 | str.elementAt(units.length).store(0); 173 | 174 | return str.cast(); 175 | } 176 | 177 | // 申请指定大小的堆内存空间 178 | factory CString.malloc(int size) { 179 | Pointer str = allocate(count: size); 180 | return str.cast(); 181 | } 182 | 183 | /// 将C语言中的字符串转为Dart中的字符串 184 | static String fromUtf8(CString str) { 185 | if (str == null) return null; 186 | int len = 0; 187 | while (str.elementAt(++len).load() != 0); 188 | List units = List(len); 189 | for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load(); 190 | return Utf8Decoder().convert(units); 191 | } 192 | } 193 | ``` 194 | 195 | 运行结果 196 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190517204254208.jpg) 197 | 可以看到将`"helloworld"`字符串加密后变成一串乱码,解密字符串后,恢复内容 198 | 199 | 200 | #### 完善代码 201 | 上述代码虽然实现了我们的目标,但是存在明显的内存泄露,我们使用CString 的`allocate`和`malloc`申请了堆内存,但是却没有手动释放,这样运行一段时间后可能会耗尽内存空间,手动管理内存往往是`C/C++`中最容易出问题的地方,这里我们只能进行一个简单的设计来回收内存 202 | ```dart 203 | /// 创建Reference 类来跟踪CString申请的内存 204 | class Reference { 205 | final List> _allocations = []; 206 | 207 | T ref(T ptr) { 208 | _allocations.add(ptr.cast()); 209 | return ptr; 210 | } 211 | 212 | // 使用完后手动释放内存 213 | void finalize() { 214 | for (final ptr in _allocations) { 215 | ptr.free(); 216 | } 217 | _allocations.clear(); 218 | } 219 | } 220 | ``` 221 | 222 | 修改代码 223 | 224 | ```dart 225 | import 'dart:ffi'; 226 | import 'dart:io' show Platform; 227 | import "dart:convert"; 228 | 229 | 230 | /// encrypt函数方法签名,注意,这里encrypt和decrypt的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同 231 | typedef NativeEncrypt = Void Function(CString,CString,Int32); 232 | typedef DartEncrypt = void Function(CString,CString,int); 233 | 234 | 235 | void main(List args) { 236 | if (Platform.isWindows) { 237 | // 加载dll动态库 238 | DynamicLibrary dl = DynamicLibrary.open("../src/hello.dll"); 239 | var encrypt = dl.lookupFunction("encrypt"); 240 | var decrypt = dl.lookupFunction("decrypt"); 241 | 242 | // 创建Reference 跟踪CString 243 | Reference ref = Reference(); 244 | 245 | CString data = CString.allocate("helloworld",ref); 246 | CString enResult = CString.malloc(100,ref); 247 | encrypt(data,enResult,100); 248 | print(CString.fromUtf8(enResult)); 249 | 250 | print("-------------------------"); 251 | 252 | CString deResult = CString.malloc(100,ref); 253 | decrypt(enResult,deResult,100); 254 | print(CString.fromUtf8(deResult)); 255 | 256 | // 用完后手动释放 257 | ref.finalize(); 258 | } 259 | } 260 | 261 | class CString extends Pointer { 262 | 263 | /// 开辟内存控件,将Dart字符串转为C语言字符串 264 | factory CString.allocate(String dartStr, [Reference ref]) { 265 | List units = Utf8Encoder().convert(dartStr); 266 | Pointer str = allocate(count: units.length + 1); 267 | for (int i = 0; i < units.length; ++i) { 268 | str.elementAt(i).store(units[i]); 269 | } 270 | str.elementAt(units.length).store(0); 271 | 272 | ref?.ref(str); 273 | return str.cast(); 274 | } 275 | 276 | factory CString.malloc(int size, [Reference ref]) { 277 | Pointer str = allocate(count: size); 278 | ref?.ref(str); 279 | return str.cast(); 280 | } 281 | 282 | /// 将C语言中的字符串转为Dart中的字符串 283 | static String fromUtf8(CString str) { 284 | if (str == null) return null; 285 | int len = 0; 286 | while (str.elementAt(++len).load() != 0); 287 | List units = List(len); 288 | for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load(); 289 | return Utf8Decoder().convert(units); 290 | } 291 | } 292 | ``` 293 | 294 | 295 | ## 总结 296 | `dart:ffi`包目前正处理开发中,暂时释放的只有基础功能,且使用`dart:ffi`包后,Dart代码不能进行`aot`编译,不过Dart开发了`ffi`接口后,极大的扩展了dart语言的能力边界,就如同的Java的Jni一样,如果`ffi`接口开发得足够好用,Dart就能像Python那样成为一门真正的胶水语言。 297 | 298 | 大家如果有兴趣进一步研究,可以查看`dart:ffi`包源码,目前该包总共才5个dart文件,源码很少,适合学习。 299 | 300 | 参考资料: 301 | [dart:ffi 源码](https://github.com/dart-lang/sdk/tree/master/sdk/lib/ffi) 302 | [dart:ffi 官方示例](https://github.com/dart-lang/sdk/blob/master/samples/ffi/sqlite/docs/sqlite-tutorial.md) 303 | 304 | ## 视频课程 305 | 如需要获取完整的**Flutter全栈式开发课程**,请 [**点击跳转**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 306 | 307 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 308 | 309 | 310 | **欢迎关注我的公众号:编程之路从0到1** 311 | 312 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301102949549.jpg) 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Gitter](https://badges.gitter.im/bczl-flutter/community.svg)](https://gitter.im/bczl-flutter/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 4 | 5 | # Learn Dart 6 | 7 | Dart 编程入门到深入系列: 8 | 9 | **基础篇** 10 | 11 | [Dart语言——45分钟快速入门(上)](https://github.com/arcticfox1919/learn-dart/blob/master/45%E5%88%86%E9%92%9F%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8%EF%BC%88%E4%B8%8A%EF%BC%89.md) 12 | 13 | [Dart语言——45分钟快速入门(下)](https://github.com/arcticfox1919/learn-dart/blob/master/45%E5%88%86%E9%92%9F%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8%EF%BC%88%E4%B8%8B%EF%BC%89.md) 14 | 15 | **进阶篇** 16 | 17 | [Dart 异步编程详解之一文全懂](https://github.com/arcticfox1919/learn-dart/blob/master/Dart%20%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E8%AF%A6%E8%A7%A3.md) 18 | 19 | [Dart 语言异步之Stream详解](https://github.com/arcticfox1919/learn-dart/blob/master/Dart%20%E8%AF%AD%E8%A8%80Stream%E8%AF%A6%E8%A7%A3.md) 20 | 21 | [Dart 语言标准流与文件操作](https://github.com/arcticfox1919/learn-dart/blob/master/Dart%20%E8%AF%AD%E8%A8%80%E6%A0%87%E5%87%86%E6%B5%81%E4%B8%8E%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C.md) 22 | 23 | [Dart 网络编程](https://github.com/arcticfox1919/learn-dart/blob/master/Dart%20%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B.md) 24 | 25 | **实战篇** 26 | 27 | [Dart 爬虫开发体验](https://blog.bczl.xyz/archives/4) 28 | 29 | [Dart 全栈之服务端开发](https://arcticfox.blog.csdn.net/article/details/120826195) 30 | 31 | [Dart FFI调用C语言混合编程](https://github.com/arcticfox1919/learn-dart/blob/master/Dart%20%E8%B0%83%E7%94%A8C%E8%AF%AD%E8%A8%80%E6%B7%B7%E5%90%88%E7%BC%96%E7%A8%8B.md) 32 | 33 | [LuaDardo中Dart与Lua的相互调用](https://arcticfox.blog.csdn.net/article/details/119582403) 34 | 35 | [Socks5代理服务器开发(Dart实现)](https://www.bilibili.com/video/BV1954y1k7nc/) 36 | 37 | [几百行代码带你手撸虚拟机(Dart实现)](https://www.bilibili.com/video/BV1LL411F72N/) 38 | 39 | 40 | ***关注我,不错过!*** 41 | 42 | 微信公众号:编程之路从0到1 43 | 44 | ![编程之路从0到1](https://img-blog.csdnimg.cn/20190301102949549.jpg) 45 | 46 | 47 | 48 | **视频教程** 49 | 50 | [**Flutter 全栈开发之Dart编程指南**](https://study.163.com/course/courseMain.htm?courseId=1209508814&share=2&shareId=480000001855430) 51 | 52 | [**Flutter 全栈式开发指南**](https://study.163.com/course/courseMain.htm?courseId=1210111872&share=2&shareId=480000001855430) 53 | 54 | [**Flutter全栈式开发-高级篇**](https://study.163.com/course/courseMain.htm?courseId=1211268802&share=2&shareId=480000001855430) 55 | 56 | 或关注博主 [**Flutter全栈式开系列**](http://m.study.163.com/provider/480000001855430/index.htm?share=2&shareId=480000001855430) 57 | 58 | ![qr_adv](https://img-blog.csdnimg.cn/img_convert/eb3c16913c155e08e1443a0029003aa1.png) 59 | 60 | 61 | - [CSDN博客](https://blog.csdn.net/yingshukun) 62 | 63 | - [个人博客](https://blog.bczl.xyz/) 64 | --------------------------------------------------------------------------------