Flutter基础(一)

Dart基础知识

Posted by Ted on February 20, 2018

一、声明变量

1.明确知道类型

可以直接用:

String a = "aaaa";
ClassA classA = new ClassA();

这种方式直接声明变量,和java基本上都一样

2.var

可以接受任何类型的值,但是一旦确定了类型,则不能更换类型,因此这点和js也略有不同,毕竟Dart语言是强类型语言。

var test = "aaa";
    //test = 1;//编译不通过

3.dynamic

dynamic类似java中的Object,顾名思义,就是一个支持多类型的关键词,它与var的区别在于,它的类型是可以换的,

dynamic test = "aaa";
test = 1;//编译可以通过

4.final与const

这两个都有代表常量的意思,区别在于final修饰的变量,只能被赋值一次。而const的用法就比较奇妙了,用于声明变量,效果和final一样,而用于生成对象,效果又和new一样,但是这个对象里面的各种属性的值,就不能重新赋值了。const也只能用于构造函数里面的参数都是final的类。 还是举个栗子比较明了:

class Point{
  final int x;
  final int y;

 const Point(this.x, this.y);
}

Point p = const Point(2, 3);//p的属性无法在改变。

final a = [1,2,3];
a.add(4);
print(a);//[1, 2, 3, 4]
//a = [2,3,4];//编译不通过

var b = const  [1,2,3];
//b.add(4);//运行时报错,好奇为什么不在编译的时候就提示不通过呢
b = [1,2,3];//编译可通过
b.add(4);//编译可通过
print(b);//[1,2,3,4]

const c = const [1,2,3];
//c.add(4);//运行时出错
//c = [2,3,4];//编译时出错

二、Dart并发

1、isolate定义

isolate是Dart对actor并发模式的实现。运行中的Dart程序由一个或多个actor组成,这些actor也就是Dart概念里面的isolate。isolate是有自己的内存和单线程控制的运行实体。isolate本身的意思是“隔离”,因为isolate之间的内存在逻辑上是隔离的。isolate中的代码是按顺序执行的,任何Dart程序的并发都是运行多个isolate的结果。因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

isolate之间的通信

由于isolate之间没有共享内存,所以他们之间的通信唯一方式只能是通过Port进行,而且Dart中的消息传递总是异步的。

isolate与普通线程的区别

我们可以看到isolate神似Thread,但实际上两者有本质的区别。操作系统内的线程之间是可以有共享内存的而isolate没有,这是最为关键的区别。

2、Flutter Engine Runners与Dart Isolate

那我们还要从Runner具体的实现说起,Runner是一个抽象概念,我们可以往Runner里面提交任务,任务被Runner放到它所在的线程去执行,这跟iOS GCD的执行队列很像。我们查看iOS Runner的实现实际上里面是一个loop,这个loop就是CFRunloop,在iOS平台上Runner具体实现就是CFRunloop。被提交的任务被放到CFRunloop去执行。

Dart的Isolate是Dart虚拟机自己管理的,Flutter Engine无法直接访问。Root Isolate通过Dart的C++调用能力把UI渲染相关的任务提交到UI Runner执行这样就可以跟Flutter Engine相关模块进行交互,Flutter UI相关的任务也被提交到UI Runner也可以相应的给Isolate一些事件通知,UI Runner同时也处理来自App方面Native Plugin的任务。

所以简单来说Dart isolate跟Flutter Runner是相互独立的,他们通过任务调度机制相互协作。

3、抢占式调度、时间分片和共享资源

大多数支持多个并发执行线程的计算机语言(包括 Java、Kotlin、Objective-C 和 Swift)都使用抢占式来切换线程。每个线程都被分配一个时间分片来执行,如果超过了分配的时间,线程将被上下文切换抢占。但是,如果在线程间共享的资源(如内存)正在更新时发生抢占,则会导致竞态条件

竞态条件具有双重不利,因为它可能会导致严重的错误,包括应用程序崩溃并导致数据丢失,而且由于它取决于独立线程的时序,所以它特别难以找到并修复。在调试器中运行应用程序时,竞态条件常常消失不见。

解决竞态条件的典型方法是使用来保护共享资源,阻止其他线程执行,但锁本身可能导致卡顿,甚至更严重的问题(包括死锁饥饿)。

Dart 采取了不同的方法来解决这个问题。Dart 中的线程称为 isolate,不共享内存,从而避免了大多数锁。isolate 通过在通道上传递消息来通信,这与Erlang中的 actor 或 JavaScript 中的 Web Worker 相似。

Dart 与 JavaScript 一样,是单线程的,这意味着它根本不允许抢占。相反,线程显式让出(使用async/await、FutureStream)CPU。这使开发人员能够更好地控制执行。单线程有助于开发人员确保关键功能(包括动画和转场)完成而无需抢占。这通常不仅是用户界面的一大优势,而且还是客户端——服务器代码的一大优势。

当然,如果开发人员忘记了让出 CPU 的控制权,这可能会延迟其他代码的执行。然而我们发现,忘记让出 CPU 通常比忘记加锁更容易找到和修复(因为竞态条件很难找到)。

4

以下有几点关于dart的事件循环机制需要牢记于心:

  • Dart事件循环执行两个队列里的事件:event队列和microtask队列。
  • event队列的事件来自dart(future,timer,isolate message等)和系统(用户输入,I/O等)。
  • 目前为止,microtask队列的事件只来自dart。
  • 事件循环会优先清空microtask队列,然后才会去处理event队列。
  • 当两个队列都清空后,dart就会退出。
  • main方法,来自event队列和microtask队列的所有事件都运行在Dart的main isolate中。

当你要安排一个任务时,请遵守以下规则:

  • 如果可以,尽量将任务放入event队列中。
  • 使用Future的then方法或whenComplete方法来指定任务顺序。
  • 为了保持你app的可响应性,尽量不要将大计算量的任务放入这两个队列。
  • 大计算量的任务放入额外的isolate中。

isolateDart平台对线程的实现方案,但和普通Thread不同的是,isolate拥有独立的内存,isolate由线程和独立内存构成。正是由于isolate线程之间的内存不共享,所以isolate线程之间并不存在资源抢夺的问题,所以也不需要锁。

通过isolate可以很好的利用多核CPU,来进行大量耗时任务的处理。isolate线程之间的通信主要通过port来进行,这个port消息传递的过程是异步的。通过Dart源码也可以看出,实例化一个isolate的过程包括,实例化isolate结构体、在堆中分配线程内存、配置port等过程。

isolate看起来其实和进程比较相似,之前请教阿里架构师宗心问题时,宗心也说过“isolate的整体模型我自己的理解其实更像进程,而asyncawait更像是线程”。如果对比一下isolate和进程的定义,会发现确实isolate很像是进程。