本篇注重flutter底层
Dart语法
dart内置包
1 | import 'dart:core'; |
提供了小型但关键的内置功能,这个包库被自动导入每个dart程序
1 | import 'dart:async'; |
异步编程通常使用回调函数,dart提供其他方法:future和stream,dart:async库中包含了future和Stream的更多信息
Future就像对未来某个时刻的结果的承诺。Stream是一种获取值序列(如事件)的方法。
1 | import 'dart:math'; |
dart:math库提供常见的函数,如正弦余弦、最大值最小值,pi和e等
Api:https://api.dart.dev/stable/2.8.4/dart-math/dart-math-library.html
1 | import 'dart:convert'; |
jsonDecode将json编码的字符串解码为dart对象
jsonEncode将受支持的Dart对象编码为json格式的字符串
utf8.decode()将utf8编码的字节解码为dart字符串
utf8.encode()将Dart字符串编码为utf8编码字节的列表:
1 | import 'dart:io' |
网络实现
1 | var httpClient = new HttpClient(); |
变量和基本类型
变量 var
如果不希望变量修改,可以使用final或者const定义变量
数字类型:int整数、double浮点数类型、num数字类型
布尔类型
枚举类型 enum
List数组
List,是泛型类型数据结构,支持任意数据类型的数组。默认定义方式为可扩展数组
1 | // 通过List对象定义int数组 |
Set集合
Set是无序集合类型,Set跟List都能保存一组数据,区别就是Set的元素都是唯一的。
1 | //初始化set |
Map类型
Map是一种哈希类型数据,map类型的数据都是由key和value两个值组成,key是唯一的,value不必唯一,key和value可以为相同类型,也可以将key和value分别指定为别的类型
1 | //初始化map |
修改map
1 | //添加数据 |
final表示这个变量不能在发生更改,但是这个初始化的值在编译时是不确定的,在运行时确定其值,且不能更改,如http接口、三方库的随机数据
const是在定义时初始化
泛型
类泛型
1 | //定义类的泛型很简单,只需要在类名后加: <T>;如果需要多个泛型类型参数,可以在尖括号中追加,用逗号分隔 |
函数泛型
1 | //定义函数的泛型 |
集合泛型
Map<string,int>={}
运算符
is对象是该类型,返回true
is!对象不是该类型返回false
as类型转换
引入与导出
引入分为引入内置库、引入pub包管理器提供的库、引入本地文件(自定义的库)
内置库随flutter sdk安装到本地,
dart语言使用pub管理第三方开源包,
导入本地包时直接通过路径访问dart脚本
指定库的别名
如果导入的两个包,包含了同名的类或者函数,就会出现命名冲突,因此提供别名机制。
1 | import 'dart:math' as math;// 使用 as 关键词,指定包的别名,通过别名访问包中的方法 |
部分导入
分为只导入需要的部分和隐藏不需要的部分两种
1 | import 'package:lib1/lib1.dart' show foo; |
延迟加载:
在需要的时候进行加载,可以减少app启动时间
1 | import 'package:deferred/hello.dart' deferred as hello; |
flutter对于平台级的包是plugin,主要是与平台相关的功能
flutter对于纯dart开发的包的package,与平台无关,第三方包的前缀为package
导出更大的库 export
library
类
默认类的成员属性和方法都是共有的,以下划线 ( _ ) 开头命名的属性和方法代表私有(private)成员属性,
类中跟类名同名的方法,为构造方法,如果没有自定义构造方法,会自动生成一个不带参数的默认构造方法
1 | //实例化 |
Factory构造方法类似设计模式中的工厂模式,用来创建对象。factory 构造方法只能访问静态属性和静态成员方法,因此不能访问this引用。
extends是继承,调用父类的方法要用super
,重写父类的方法要用@override
,子类中实现父类的方法时要加override
flutter中只支持单继承,且构造函数不能继承,Dart语言中一个类只能继承一个父类,但是Dart语言提供了mixins机制,可以复用多个类,达到类似多继承的效果
1 | // 定一个父类 |
implement接口
flutter没有专门的接口,每一个类有一个隐形的接口,接口中包含类中所有变量和方法,可以拥有父类的api,类bimplement类a
1 | class Point implements Comparable, Location {...} |
抽象类是不能实例化的类,一般都是用来定义接口,抽象方法就是没有实现的方法。只能继承或写接口
1 | // 使用abstract关键词修饰的类,就是抽象类 |
混合mixins的对象是类,可以混合多个
dart中..符号(两个连点)代表连续调用函数或方法,属于流式编程。
函数
函数允许定义0个或多个参数,也可以使用命名参数,就是在函数调用传参的时候,直接根据参数名传递参数,忽略参数定义的顺序。
命名参数用花括号{}表示,命名参数定义后,调用函数时可以不传参,不传参的时候默认参数为null,也可以自定义默认参数
1 | // 参数包括在 花括号{} 之间 |
闭包函数又叫匿名函数,就是没有函数名的函数,直接将函数赋值给一个变量,通过变量名调用函数即可。
1 | var f = (a, b) { |
闭包函数的一种是箭头函数,如果闭包函数只有一个表达式的时候,可以使用箭头函数简写。
1 | //上面的函数等价于 |
闭包函数内部可以引用包含闭包函数的所有层级作用域中的变量,与闭包函数调用的位置无关。
值传递
dart是值传递而非引用传递
值传递和引用传递属于函数调用时参数的求值策略,是用于区分两种内存分配方式,值传递在调用栈上分配,引用传递在堆上分配。
值传递是原来函数的一份拷贝,拷贝的是值,而引用传递拷贝的是指针地址,可以改变值或者指针地址,
除了c、c#、c++,其他的语言如java、oc、Dart等都是值传递。值传递用起来更简单,同时还能规避很多因为此而产生的不必要的问题
flutter页面间传参
1.通过定义构造方法传值
需要在接收数据的页面事先定义好构造方法,构造方法中是要接收的参数。
2.通过指定路由传递参数
在Flutter中我们可以把要传递的参数放到Navigator中,然后传递给指定的路由,在接收的页面提取出需要的参数即可,这种方式相比方式一更加灵活一些。
3.通过onGenerateRoute提取参数后传给相应Widget
这种方式可以看做是前两种方式的结合体,我们可以在onGenerateRoute提取参数,然后传递给指定的Widget。
Key
flutter受到react或vue启发,每个wideget的构建方法都会有一个key的参数可选,用key标识元素,当移除或添加元素时可以准确识别
Key对于每个元素是独一无二的
ObjectKey与ValueKey
GlobalKey的使用场景是从父组件跨子Widget来传递状态时使用
future异步
dart是单线程语言,flutter中的异步操作是通过单线程再通过调度任务优先级执行的,也就是future。future是对zone的封装
future与await、async 的区别是future提供了链式调用,future中的.then没有创建新的Event,而是一个个普通函数,全部执行完在开始执行下一个future
创建future
1 | void testFuture(){ |
多个future的执行顺序
future的执行顺序为future在EventQueue中的排列顺序,当需要延迟执行时,采用future.delay()执行,放在最后执行
future如果执行完才添加.then函数,则该任务会被放到MicroTask,当前future执行完才去执行microtask,microtask执行完才去下一个future
Flutter 与Native通信
Flutter中定义了三种不同类型的channel,分别是
BasicMessageChannel:用于传递字符串和半结构化的信息
MethodChannel:传递方法调用
EventChannel:用于数据流通信
单线程模型
Isolate多线程
flutter中isolate是有自己的内存和单线程控制的运行实体,isolate类似于线程,默认情况Dart启动一个Isolate,main函数就是他的入口。运行中的flutter程序是由一个或多个isolate组成的,通常情况下程序运行在main isolate中。
isolate是通过flutter Engine层面的线程实现的。多个isolate无法共享内存,必须通过api进行通信。
执行完后检查并执行Microtask Queue,通常使用scheduleMicrotask将事件添加到MicroTask Queue
最后执行EventQueue队列中的代码,通常使用future向EventQueue加入时间,也可以使用async和await添加事件
针对比较消耗CPU的任务,最好创建一个新的 Isolate 去处理,避免阻塞主 Isolate (也就是主线程),这样可以利用设备的多核特性。
1 | // 导入isolate包 |
多个Isolate 之间只能通过消息通讯,主要通过ReceivePort和SendPort两个类处理消息通讯。
ReceivePort 负责接收 SendPort 发送的消息, SendPort 和 ReceivePort 是捆绑关系, SendPort 是由 ReceivePort 创建的。
事件流
Stream是dart语言自带的,可以把stream想象成管道,只从一端流入数据,通过管道从另一端流出数据。通过StreamController控制stream,
Stream支持任何数据的传输,包括基本值、对象、事件、集合等。传输数据时可以通过listen监听来自StreamController的属性,监听之后可以通过StreamSubscription订阅对象并接受stream发送数据变更的通知。
Stream也是异步处理的核心,与Future相比,Future表示将来一次异步获得的数据,而Stream是多次异步获取的数据。Future是返回一次值,Stream返回多次值。Stream有StreamBuilder负责监听Stream,当Stream数据流出时会自动重新构建组件,并通过Builder进行回调。
常用函数
https://github.com/Sky24n/common_utils
Flutter底层原理
flutter开发概述(拷贝)
flutter的开发通过继承无状态的StatelessWidget
控件还有有状态的StatefulWidget
,然后通过嵌套Widget
下的child/children
,在对应的Widget
下面建立控件及控件样式
flutter中container控件就是结合Align
、Padding
、DecoratedBox
、ConstrainedBox
、Transform
等组合而成,形成编辑好的业务模板
对于状态管理,flutter通过继承statefulwidget,然后在state对象内通过变量访问和setstate出发更新
flutter中存在Widget、Element、RenderObject、Layer四棵树,Element与RenderObject是一一对应的关系,与Widget是多对一的关系,
flutter执行原理
flutter ui是直接通过skia渲染的。
flutter内部执行事件循环机制。App执行main函数时,优先处理microtask queue,直到队列为空,microtask queue为空后开始处理event queue。每执行一次event,又会判断此时新的microytask queue是否为空,如果有再取出执行。执行microtask queue时eventqueue会被阻塞,直到再次为空。
当microtask queue和event queue都为空时,App正常退出。
microtask queue中尽量避免任务太多或者长时间处理,否则会使App的绘制和交互等行为卡住,因此绘制和交互这些作为event存放在event queue中。
flutter渠道
flutter定义了master、beta、dev、stable四个渠道
master为最新的channel
dev为当月最新且充分测试的channel
beta为每月最新且充分测试后的channel
stable为当月最稳定的channel
稳定性依次提高
生命周期
生命周期存在于有状态组件statefulwidget中
createState()
创建一个StatefulWidget时立即调用,当要创建新的 StatefulWidget 的时候,会立即执行 createState,而且只执行一次,createState 必须要实现:
initState()
此方法只会调用一次,初始化state,将state与对应的Buildcontext产生关联,必须调用super.initState()。在此方法内注册streams ChangeNotifiers或其他会改变的数据的监听
didChangeDependencies()
state的依赖对象发生变化时调用该方法,同时build()方法会被调用
build()
这个方法经常被调用,build 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,所以不要在 build 里写业务逻辑,可以把业务逻辑写到你的 StatelessWidget 的构造函数里。
didUpdateWidge
当widget的状态变化时调用,一般不会用到,只有在使用 key 对 Widget 进行复用的时候才会调用。
deactivate()
当切换页面时会调用该方法
dispose()
widget销毁前调用该方法。当 View 不需要再显示,从渲染树中移除的时候,State 就会永久的从渲染树中移除,就会调用 dispose 生命周期,这时候就可以在 dispose 里做一些取消监听、动画的操作,和 initState 是相反的。
setState()
通知组件刷新
1 |
|
App生命周期
想知道flutterapp是在前台还是后台等,需要了解app生命周期,使用WidgetsBindingObserver ,
在首页mixWidgetsBindingObserver类,同时初始化监听和移除,
1 | class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { |
AppLifecycleState 就是 App 的生命周期,有:
- resumed
- inactive
- paused
- suspending
在Android程序中插入flutter组件
在IOS中插入flutter组件
音视频
对于flutter或者ios、android开发,在会写界面的基础上,在音视频、性能优化、监控、热修插件、动画效果、渲染等有深入就会很抢手。
音视频基础
视频流从加载到播放一般经过解协议、解封装、解编码等过程
视频封装协议是指我们常见的mp4、avi、rmvb、mkv、flv等常见后缀格式,在传输过程中把音频和视频都打包在一起的封装
流媒体协议一般有http、rtsp、RTMP等,最常见的就说http网络协议,RTSP和RTMP一般用于直播流或远程监控。
对于编码,音频编码是指音频数据的编码方式,有mp3、pcm、aac、ac-3等,数据大小按采样率声道数样本格式计算。一般来说,wav/pcm编码的音频质量比较好,体积也会比较大,MP3有损压缩会在保证音频质量的同时压缩体积,aac也是有损压缩
视频编码是图像的编码方式,有h263、h264、h265(HEVC)、MPEG-2、MPEG-4等,
音视频开发接sdk和视频源url,确认自己需要支持的封装协议、视频编码和音频编码。如果允许客户上传,最好在服务端提供转格式与编码率等功能
ffmpeg
Flutter Web
flutterweb要求flutter版本大于1.5.4
安装webdev工具
1 | flutter packages pub global activate webdev |
如果提示出错安装stagehand,输入命令
1 | flutter packages pub global activate stagehand |
以git方式在pubspec.yaml中引入web库
1 | dependencies: |
热更新
1 | webdev serve --auto restart |
输出静态文件
1 | webdev build |
dart2js
Flutter Windows
开启windows支持
1 | flutter config --enable-windows-desktop |
windows应用编译需要visual studio工具
创建一个release版本
1 | flutter build --<platform>-- |
窗口尺寸
1 | import 'package:window_size/window_size.dart' as window_size |
file_chooser