Flutter笔记_基础篇
Posted 巨头之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter笔记_基础篇相关的知识,希望对你有一定的参考价值。
这是之前在入手flutter时做的笔记,由于笔记是使用幕布来记录的,幕布不支持markdown格式,所以在幕布上的笔记基本都很少会去整理,这次用了pandoc工具将幕布的笔记转为markdown格式,无奈支持很不良好,基本还是得手动来整理,所以这一篇也整理了一半,没办法 太浪费时间 放弃了,就贴个笔记的幕布链接吧。本篇主要了解下移动端技术,原生和跨平台技术进行对比
本篇主要记录学习 “Flutter实战” 一书的笔记:
https://book.flutterchina.club/chapter1/mobile_development_intro.html
原生开发与跨平台技术的比较
原生开发
-
速度快,性能高,可访问平台全部功能,并且各个平台有特定的UI效果
-
开发维护成本高,不同平台需要多套代码
-
内容固定,无法动态化,新功能更新及bug修复都得发版(虽然有热修复等工具~ 也存在局限性), 电商项目对动态化很高依赖,这也是很大的弊端
-
推广成本高,针对于网页端的便捷~可直接通过链接进行推广,移动端首先得安装,这个在一定工程上增加了推广难度
跨平台
H5+原生(Cordova/Ionic/微信小程序)
-
介绍:一般原生的WebView为容器,H5作为页面并承载到容器上,针对平台特定的功能,H5调用不到,这时就喜欢原生来实现,提供js接口给H5调用来交互
-
H5的页面,优点在于动态化和推广方面, 而性能和加载响应速度方面则是主要问题,加载响应问题依赖于网速及服务器;一般在开发中,单纯为了展示的静态则用H5来进行开发,而重交互的页面则使用原生开发,当然也考虑到需求改动的问题,所以页面是H5还是原生,在这里要结合多方面考虑
-
H5的局限性在于WebView容器的局限. WebView是javascript与原生API之间通信的桥梁,主要负责JavaScript与原生之间传递调用消息,而消息的传递必须遵守一个标准的协议,它规定了消息的格式与含义,我们把依赖于WebView的用于在JavaScript与原生之间通信并实现了某种消息传输协议的工具称之为WebView
JavaScript Bridge(桥), 简称 JsBridge,它也是混合开发框架的核心
Js开发+原生渲染(React Native/Weex/快应用)
RN跨平台框架原理 :
-
React中虚拟DOM最终会映射为浏览器DOM树,而RN中虚拟DOM会通过 JavaScriptCore 映射为原生控件树
-
JavaScriptCore是一个JavaScript解释器;它在RN中的作用是为Js提供运行环境,也是JavaScript与原生应用之间通信的桥梁,作用和JsBridge一样
-
RN中将虚拟DOM映射为原生控件的过程中分两步:
-
1.布局消息传递; 将虚拟DOM布局信息传递给原生;
-
2.原生根据布局信息通过对应的原生控件渲染控件树;
-
响应式编程:
a.开发者只需关注状态转移(数据),当状态发生变化,React框架会自动根据新的状态重新构建UI;
b.React框架在接收到用户状态改变通知后,会根据当前渲染树,结合最新的状态改变,通过Diff算法,计算出树中变化的部分,然后只更新变化的部分(DOM操作),从而避免整棵树重构,提高性能
在这一步中,React会在DOM的基础上建立一个抽象层,即虚拟DOM树,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到真实DOM中,而不是每次改变都去操作一下DOM。为什么不能每次改变都直接去操作DOM树?这是因为在浏览器中每一次DOM操作都有可能引起浏览器的重绘或回流:
- 如果DOM只是外观风格发生变化,如颜色变化,会导致浏览器重绘界面。
- 如果DOM树的结构发生变化,如尺寸、布局、节点隐藏等导致,浏览器就需要回流(及重新排版布局)而浏览器的重绘和回流都是比较昂贵的操作,如果每一次改变都直接对DOM进行操作,这会带来性能问题,而批量操作只会触发一次DOM更新
Weex/快应用的原理:
-
Weex的原理跟RN一样,核心也是在于dom树、响应式编程和原生控件渲染(桥接);不同点是在于语法层面, Weex支持Vue和Rax语法,而RN支持React语法
-
快应用原理跟RN一样,采用原生JavaScript开发,其开发框架和微信小程序很像。React Native和Weex的渲染/排版引擎是集成到框架中的,每个App都需要重新打包一份,导致安装包体积较大。而快应用渲染/排版引擎是集成到Rom中的,应用中无需打包,安装包体积小.
优缺点:
-
采用web开发技术栈,社区庞大、上手快、开发成本较低;原生渲染,性能相比H5提高很多; 动态化较好
-
渲染时需要Js和原生之间通信,在有些场景如拖动可能会导致通信频繁导致卡顿;Js脚本语言执行时需要JIT,执行效率和AOT代码有差距
-
由于是原生控件渲染,所以不同平台的控件需要自行维护,除此之外,其控件系统也会受到原生UI系统限制,例如,在android中,手势冲突消歧规则是固定的,这在使用不同人写的控件嵌套时,手势冲突问题将会变得非常棘手
自绘UI+原生(QT for mobile/Flutter)
-
思路:通过在不同平台实现一个统一接口的渲染引擎来绘制UI,而不依赖系统原生控件,所以可以做到不同平台UI的一致性;自绘制引擎解决的是UI的跨平台问题,涉及到其它系统能力调用,仍然要涉及原生开发;
-
优缺点
-
优点:
a.性能高;由于自绘引擎是直接调用系统API来绘制UI,所以性能和原生控件接近;
b.灵活、组件库易维护、UI外观保真度和一致性高,不受原生布局系统的限制 -
缺点:动态性不足;为了保证UI绘制性能,自绘UI系统一般都会采用AOT模式编译其发布包,所以应用发布后,不能像Hybrid和RN那些使用JavaScript(JIT)作为开发语言的框架那样动态下发代码
-
Flutter的优点
Flutter使用自己的高性能渲染引擎来绘制widget,有自身的一套widget,这样就可以保证既不使用WebView,也不使用操作系统的原生控件以此保证在Android和ios上UI的一致性,而且也可以避免对原生控件依赖而带来的限制及高昂的维护成本
问题
为什么Flutter APP的Android安装包比iOS安装包小?
Flutter使用Skia作为其2D渲染引擎,Skia是Google的一个2D图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现,Skia是跨平台的,并提供了非常友好的API,目前Google Chrome浏览器和Android均采用Skia作为其绘图引擎,由于Android系统已经内置Skia,所以Flutter在打包APK(Android应用安装包)时,不需要再将Skia打入APK中,但iOS系统并未内置Skia,所以构建iPA时,也必须将Skia一起打包,从而导致IOS包比Android体积大
采用Dart语言开发的好处
JIT(动态解释/即时编译)和AOT(静态编译):
- AOT程序的典型代表是用C/C++开发的应用,它们必须在执行前编译成机器码;
- JIT的代表则非常多,如JavaScript、python等,事实上,所有脚本语言都支持JIT模式
- 有些语言既可以以JIT方式运行也可以以AOT方式运行,如Java、Python, 它们可以在第一次执行时编译成中间字节码、然后在之后执行时可以动态将字节码转为机器码
- 区分AOT还是JIT: 区分是否为AOT的标准就是看代码在执行之前是否需要编译,只要需要编译,无论其编译产物是字节码还是机器码,都属于AOT
优点:
- 开发效率高
- 基于JIT的快速开发周期; Flutter在开发阶段采用JIT模式,这样就避免了每次改动都要进行编译,极大的节省了开发时间
- 基于AOT的发布包; Flutter在发布时可以通过AOT生成高效的ARM代码以保证应用性能。而JavaScript则不具有这个能力
- 高性能: Dart支持AOT , 这一点比JavaScript用JIT更好,可以提供流畅、高保真的的UI体验
- 快速内存分配: Flutter框架使用函数式流,这使得它在很大程度上依赖于底层的内存分配器,而Dart开发团队的很多成员都是来自Chrome团队的,Chrome V8的JavaScript引擎在内存分配上也已经做的很好,所以在内存分配上Dart拥有JavaScript引擎在内存分配的优点
- 类型安全: 由于Dart是类型安全的语言,支持静态类型检测,所以可以在编译前发现一些类型的错误,并排除潜在问题
Dart语法
变量声明
var
类似于JavaScript中的var,它可以接收任何类型的变量,但最大的不同是Dart中var变量一旦赋值,类型便会确定,则不能再改变其类型
```dart
var t; t = "hi world"; //变量t的类型已经确定为String
t = 1000;//类型一旦确定,就不能改变,这里会报错
```
dynamic和Object
Object 是Dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象. dynamic与var一样都是关键词
-
相同点:声明的变量可以赋值任意对象。而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型
- 不同点:dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合,而Object声明的对象只能使用Object的属性与方法,否则编译器会报错也就是说,dynamic声明的对象拥有该对象的所有属性和方法等(包括object的属性方法),而object声明的对象只拥有object的属性与方法
final和const: 常量
- 相同点: 被final或const修饰的变量,变量类型可以省略,两者修饰的变量都是常量
- 不同点:const要求在声明时初始化,赋值必需为编译时常量;final变量在第一次使用时被初始化, 并不要求赋的值一定是编译时常量,可以是常量也可以不是常量
…省略
Flutter起航
1.计数器应用示例(AndroidStudio新创建的默认项目)
-
读该示例项目的代码,从项目的lib/main.dart文件入手,main函数为入口,运行起MyApp类实例(MyApp类实例代表flutter应用),MyApp类的build函数构建了App名称、主题以及应用首页路由(路由其实就类似Activity,可以看做页面),MyHomePage为App的首页,MyHomePage继承自StatefulWidget类,表示它是一个有状态的widget。在MyHomePage中维护着一个状态类_MyHomePageState类,在_MyHomePageState类中有状态变量、状态的自增函数和build函数,build函数中构建了一个Scaffold实例,Scaffold 是 Material库中提供的一个widget,它提供了默认的导航栏、标题和包含主屏幕widget树的body属性,build函数用于构建UI界面和逻辑,接收状态响应的变化并渲染UI
-
整个流程:当右下角的floatingActionButton按钮被点击之后,会调用_incrementCounter,在_incrementCounter中,首先会自增_counter计数器(状态),然后setState会通知Flutter框架状态发生变化,接着,Flutter会调用build方法以新的状态重新构建UI,最终显示在设备屏幕上。
-
_MyHomePageState类的build方法是构建UI界面和逻辑,为什么要将build方法放在State中,而不是放在StatefulWidget中?
-
状态访问不便
MyHomePageState维护着UI和状态,当UI发生改变时需要读取状态,如果build方法是放在StatefulWidget中的话,build方法就需要访问外部_MyHomePageState类的状态,那就需要将状态声明为公开的,状态将不再具有私密性,这样依赖,对状态的修改将会变的不可控;而将build()方法放在State中的话,构建过程则可以直接访问状态,这样会很方便
-
继承StatefulWidget不便
-
2.路由管理
路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity,在IOS中指一个ViewController。所谓路由管理,就是管理页面之间如何跳转。通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈
-
MaterialPageRoute
MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。如果想自定义路由切换动画,可以自己继承PageRoute来实现
-
Navigator
Navigator是一个路由管理的widget,它通过一个栈来管理一个路由widget集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈,
下面是最常用的两个:Future push(BuildContext context, Route
route) :将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。bool pop(BuildContext context, [ result ]) :
将栈顶路由出栈,result为页面关闭时返回给上一个页面的数据
-
命名路由
就是给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式-
路由表
路由表就是记录哪个名称对应哪个路由,路由表的定义 "Map<String, WidgetBuilder> routes; " . 它是一个Map,key为路由的名称,是个字符串;value是个builder回调函数,用于生成相应的路由Widget。我们在通过路由名称入栈新路由时,应用会根据路由名称在路由表中找到对应的WidgetBuilder回调函数,然后调用该回调函数生成路由widget并返回
-
注册路由表
return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), //注册路由表 routes: "new_page":(context)=>NewRoute(), , home: new MyHomePage(title: 'Flutter Demo Home Page'), );
-
通过路由名打开新路由页
要通过路由名称来打开新路由,可以使用:
Future pushNamed(BuildContext context, String routeName,Object arguments)
-
命名路由参数
在Flutter最初的版本中,命名路由是不能传递参数的,后来才支持了参数; 传递路由参数的流程: //注册一个路由 routes: "new_page": (context)=> NewRoute(), , //在路由页通过RouteSetting对象获取路由参数: class NewRoute extends StatelessWidget @override Widget build(BuildContext context) var arguments = ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text("New Router"), ), body: Center( child: Text("This Is New Router $arguments") ) );
-
…省略
可点击查看 笔记 Flutter笔记_基础篇(1)
以上是关于Flutter笔记_基础篇的主要内容,如果未能解决你的问题,请参考以下文章
初恋 Office,热恋幕布/印象笔记/ Typora,最终归属却是 Effie