Flutter笔记_基础篇

Posted 巨头之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter笔记_基础篇相关的知识,希望对你有一定的参考价值。

这是之前在入手flutter时做的笔记,由于笔记是使用幕布来记录的,幕布不支持markdown格式,所以在幕布上的笔记基本都很少会去整理,这次用了pandoc工具将幕布的笔记转为markdown格式,无奈支持很不良好,基本还是得手动来整理,所以这一篇也整理了一半,没办法 太浪费时间 放弃了,就贴个笔记的幕布链接吧。本篇主要了解下移动端技术,原生和跨平台技术进行对比

本篇主要记录学习 “Flutter实战” 一书的笔记:
https://book.flutterchina.club/chapter1/mobile_development_intro.html

原生开发与跨平台技术的比较

原生开发

  1. 速度快,性能高,可访问平台全部功能,并且各个平台有特定的UI效果

  2. 开发维护成本高,不同平台需要多套代码

  3. 内容固定,无法动态化,新功能更新及bug修复都得发版(虽然有热修复等工具~ 也存在局限性), 电商项目对动态化很高依赖,这也是很大的弊端

  4. 推广成本高,针对于网页端的便捷~可直接通过链接进行推广,移动端首先得安装,这个在一定工程上增加了推广难度

跨平台

H5+原生(Cordova/Ionic/微信小程序)

  1. 介绍:一般原生的WebView为容器,H5作为页面并承载到容器上,针对平台特定的功能,H5调用不到,这时就喜欢原生来实现,提供js接口给H5调用来交互

  2. H5的页面,优点在于动态化和推广方面, 而性能和加载响应速度方面则是主要问题,加载响应问题依赖于网速及服务器;一般在开发中,单纯为了展示的静态则用H5来进行开发,而重交互的页面则使用原生开发,当然也考虑到需求改动的问题,所以页面是H5还是原生,在这里要结合多方面考虑

  3. H5的局限性在于WebView容器的局限. WebView是javascript与原生API之间通信的桥梁,主要负责JavaScript与原生之间传递调用消息,而消息的传递必须遵守一个标准的协议,它规定了消息的格式与含义,我们把依赖于WebView的用于在JavaScript与原生之间通信并实现了某种消息传输协议的工具称之为WebView
    JavaScript Bridge(桥), 简称 JsBridge,它也是混合开发框架的核心

Js开发+原生渲染(React Native/Weex/快应用)

RN跨平台框架原理 :

  1. React中虚拟DOM最终会映射为浏览器DOM树,而RN中虚拟DOM会通过 JavaScriptCore 映射为原生控件树

  2. JavaScriptCore是一个JavaScript解释器;它在RN中的作用是为Js提供运行环境,也是JavaScript与原生应用之间通信的桥梁,作用和JsBridge一样

  3. RN中将虚拟DOM映射为原生控件的过程中分两步:

  • 1.布局消息传递; 将虚拟DOM布局信息传递给原生;

  • 2.原生根据布局信息通过对应的原生控件渲染控件树;

  1. 响应式编程:

    a.开发者只需关注状态转移(数据),当状态发生变化,React框架会自动根据新的状态重新构建UI;

    b.React框架在接收到用户状态改变通知后,会根据当前渲染树,结合最新的状态改变,通过Diff算法,计算出树中变化的部分,然后只更新变化的部分(DOM操作),从而避免整棵树重构,提高性能

    在这一步中,React会在DOM的基础上建立一个抽象层,即虚拟DOM树,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到真实DOM中,而不是每次改变都去操作一下DOM。为什么不能每次改变都直接去操作DOM树?这是因为在浏览器中每一次DOM操作都有可能引起浏览器的重绘或回流:

    • 如果DOM只是外观风格发生变化,如颜色变化,会导致浏览器重绘界面。
    • 如果DOM树的结构发生变化,如尺寸、布局、节点隐藏等导致,浏览器就需要回流(及重新排版布局)而浏览器的重绘和回流都是比较昂贵的操作,如果每一次改变都直接对DOM进行操作,这会带来性能问题,而批量操作只会触发一次DOM更新

​​
Weex/快应用的原理:

  1. Weex的原理跟RN一样,核心也是在于dom树、响应式编程和原生控件渲染(桥接);不同点是在于语法层面, Weex支持Vue和Rax语法,而RN支持React语法

  2. 快应用原理跟RN一样,采用原生JavaScript开发,其开发框架和微信小程序很像。React Native和Weex的渲染/排版引擎是集成到框架中的,每个App都需要重新打包一份,导致安装包体积较大。而快应用渲染/排版引擎是集成到Rom中的,应用中无需打包,安装包体积小.

优缺点:

  1. 采用web开发技术栈,社区庞大、上手快、开发成本较低;原生渲染,性能相比H5提高很多; 动态化较好

  2. 渲染时需要Js和原生之间通信,在有些场景如拖动可能会导致通信频繁导致卡顿;Js脚本语言执行时需要JIT,执行效率和AOT代码有差距

  3. 由于是原生控件渲染,所以不同平台的控件需要自行维护,除此之外,其控件系统也会受到原生UI系统限制,例如,在android中,手势冲突消歧规则是固定的,这在使用不同人写的控件嵌套时,手势冲突问题将会变得非常棘手

自绘UI+原生(QT for mobile/Flutter)

  1. 思路:通过在不同平台实现一个统一接口的渲染引擎来绘制UI,而不依赖系统原生控件,所以可以做到不同平台UI的一致性;自绘制引擎解决的是UI的跨平台问题,涉及到其它系统能力调用,仍然要涉及原生开发;

  2. 优缺点

    • 优点:

      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笔记_基础篇的主要内容,如果未能解决你的问题,请参考以下文章

Flutter基础篇——常用Widget

初恋 Office,热恋幕布/印象笔记/ Typora,最终归属却是 Effie

Flutter基础widgets教程-FloatingActionButton篇

Net基础篇_学习笔记_第九天_数组_三个练习

Python成长笔记 - 基础篇 python面向对象

Flutter系列博文链接