Flutter 官方文档阅读记录

Flutter问题统计

1、Flutter 单线程模式。

参考文献

Flutter中文网 : book.flutterchina.club/chapter2/th…

www.cnblogs.com/lulushen/p/…

www.jianshu.com/p/a4affde4c…

介绍:

Dart在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。微任务队列的执行优先级高于事件队列。

Dart 中事件的执行顺序:Main > MicroTask > EventQueue

在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。

通常使用 scheduleMicrotask(…)或者Future.microtask(…)方法向微任务队列插入一个任务。

通常使用 Future 向 EventQueue加入事件,也可以使用 async 和 await 向 EventQueue 加入事件。

在Dart中,所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。

  • Flutter 单线程是怎么执行异步程序的?

2、强类型语言还是弱类型语言?

  • 什么是强类型语言、什么是弱类型语言 ?有哪些优缺点

    强类型语言是一种强制类型定义的语言,一旦某一个变量被定义类型,如果不经过强制转换,则它永远就是该数据
    类型了,强类型语言包括Java、.net 、Python、C++等语言。
    ​
    弱类型语言是一种弱类型定义的语言,某一个变量被定义类型,该变量可以根据环境变化自动进行转换,不需要经
    过显性强制转换。弱类型语言包括vb 、PHP、javascript等语言。
    ​
    强类型语言在速度上略逊于弱类型语言,但是强类型定义语言带来的严谨性又能避免不必要的错误,
    复制代码
    • 强类型的语言可以在编译过程中发现源代码的错误,从而保证程序更加健壮。
    • 强类型定义语言带来的严谨性又能避免不必要的错误。
    • 强类型语言在速度上略逊于弱类型语言
  • dart属于哪种。Swift属于哪种、OC属于哪种

    Dart 本身是一个强类型语言,任何变量都是有确定类型的,在 Dart 中,当用`var`声明一个变量后,Dart 
    在编译时会根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定,而 JavaScript 是纯
    粹的弱类型脚本语言,var 只是变量的声明方式而已。
    复制代码

    所以无论是 OC 还是 Swift 都是强类型语言。 JS是弱类型语言。

    • 所有的变量必须先声明,后使用;
    • 指定类型的变量只能接收类型与之匹配的值。

    强类型变量两方面的含义。

3、Dart 关键字

  • dynamicObject 区别

    Object 是 Dart 所有对象的根基类,也就是说在 Dart 中所有类型都是Object的子类(包括Function和Null)

    dynamicObject不同的是dynamic声明的对象编译器会提供所有可能的组合,而Object声明的对象只能使用 Object 的属性与方法, 否则编译器会报错(可以使用强制累心转换)

     dynamic a;
     Object b = "";
     main() {
       a = "";
       printLengths();
     }   
    ​
     printLengths() {
       // 正常
       print(a.length);
       // 报错 The getter 'length' is not defined for the class 'Object'
       print(b.length);
     }
    复制代码

    但是这里的 a.xx (xx是a对象没有的属性)。编译也是不会报错的。

    反而。b.xx 也可以使用强制类型转换来操作 (b as String).length

  • final 和 const 区别

    如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。 一个 final 变量只能被设置一次,两者区别在于:const 变量是一个编译时常量(编译时直接替换为常量值),final变量在第一次使用时被初始化。
    复制代码
  • 函数可选参数、命名参数

    用[]标记为可选的位置参数,并放在参数列表的最后面

    {} 标记可选命名参数

4、Future Stream

Future

随着 ECMAScript 标准发布后,这个问题得到了非常好的解决,而解决回调地狱的两大神器正是 ECMAScript6 引入了Promise,以及ECMAScript7 中引入的async/await。 而在 Dart 中几乎是完全平移了 JavaScript 中的这两者:Future相当于Promise,而async/await连名字都没改。接下来我们看看通过Future和async/await如何消除上面示例中的嵌套问题。

Future与JavaScript中的Promise非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。

  Future.delayed(Duration(seconds: 2),(){
     //return "hi world!";
     throw AssertionError("Error");  
  }).then((data){
     //执行成功会走到这里  
     print("success");
  }).catchError((e){
     //执行失败会走到这里  
     print(e);
  }).whenComplete((){
     //无论成功或失败都会走到这里
  });
复制代码

有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作,比如我们有一个界面,需要先分别从两个网络接口获取数据.Future.wait,它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调

    Future.wait([
      // 2秒后返回结果  
      Future.delayed(Duration(seconds: 2), () {
        return "hello";
      }),
      // 4秒后返回结果  
      Future.delayed(Duration(seconds: 4), () {
        return " world";
      })
    ]).then((results){
      print(results[0]+results[1]);
    }).catchError((e){
      print(e);
    });
复制代码
Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){
});
复制代码
  • async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用 then 方法添加回调函数。
  • await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。

Stream

Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。举个例子:

Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){
});
复制代码
I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3
复制代码
  • 区别。

    上面可以看出。 Future 虽然可以处理多个回调。 但是是等多个回调,都完成了在执行then。Stream则是每个都执行。

上面的代码依次会输出:

5、Flutter 的 四棵树

在 Flutter 中, widget 的功能是“描述一个UI元素的配置信息、Widgt是immutable的这会 限制Widget 中定义的属性(即配置信息)必须是不可变的(final),为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的。

DiagnosticableTree 诊断树 <= Widget

渲染过程的四棵树 Widget树 => 生成 Element树 => 生成Render树 => layer 树

既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter 框架的的处理流程是这样的:

  1. 根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
  2. 根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
  3. 根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。

真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。我们通过一个例子来说明,假设有如下 Widget 树:

Container( // 一个容器 widget
  color: Colors.blue, // 设置容器背景色
  child: Row( // 可以将子widget沿水平方向排列
    children: [
      Image.network('https://www.example.com/1.png'), // 显示图片的 widget
      const Text('A'),
    ],
  ),
);
复制代码

如果 Container 设置了背景色,Container 内部会创建一个新的 ColoredBox 来填充背景

而 Image 内部会通过 RawImage 来渲染图片、Text 内部会通过 RichText 来渲染文本,所以最终的 Widget树、Element 树、渲染树结构如下:

这里需要注意:

三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。比如 StatelessWidget 和 StatefulWidget 都没有对应的 RenderObject。

渲染树在上屏前会生成一棵 Layer 树,这个我们将在后面原理篇介绍,在前面的章节中读者只需要记住以上三棵树就行。

6、Context

它是BuildContext类的一个实例,表示当前 widget 在 widget 树中的上下文。实际上,context是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle),比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法。下面是在子树中获取父级 widget 的一个示例:

class ContextRoute extends StatelessWidget  {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Context测试"),
      ),
      body: Container(
        child: Builder(builder: (context) {
          // 在 widget 树中向上查找最近的父级`Scaffold`  widget 
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}
复制代码

7、State生命周期

一个 StatefulWidget 类会对应一个 State 类,State表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息可以:

  1. 在 widget 构建时可以被同步读取。
  2. 在 widget 生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。

State 中有两个常用属性:

  1. widget,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的 widget 实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。
  2. context。StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。
  • State生命周期

    下面我们来看看各个回调函数:

    • initState:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。

    • didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build()Inherited widget发生了变化,那么此时Inherited widget的子 widget 的didChangeDependencies()回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。

    • build():此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景被调用:

      1. 在调用initState()之后。
      2. 在调用didUpdateWidget()之后。
      3. 在调用setState()之后。
      4. 在调用didChangeDependencies()之后。
      5. 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
    • reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

    • didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的 keyruntimeType 同时相等时会返回true,也就是说在在新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。

    • deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

    • dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。

8、StatefulWidget 、为什么要设计出一个state。

Flutter 设计的 Widget 和 State 就是分开的, 一个负责渲染、一个负责生命周期、热重载、刷新等功能。 他只能是帮页面和状态分开设计。

但是State里面依然有两个比较重要的属性。

  1. widget,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的 widget 实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。
  2. context。StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。

9、为什么build方法放在 State 中?

主要是为了提高开发的灵活性。如果将build()方法在StatefulWidget中则会有两个问题:

1、状态访问不便。

我们的StatefulWidget有很多状态,而每次状态改变都要调用build方法。如果放在StatefulWidget中不方便调用。

2、承StatefulWidget不便。

如果 build 在 Widget 中。 他的 build 方法、 必须帮 state 传过去。 那他的子类就只能使用父类的 state 这样设计就不太合理了。

    @override
    Widget build(BuildContext context, State state){
      //由于子类要用到AnimatedWidget 的状态对象_animatedWidgetState,
      //所以AnimatedWidget 必须通过某种方式将其状态对象_animatedWidgetState
      //暴露给其子类   
      super.build(context, _animatedWidgetState)
    }
复制代码

10、子 widget 获取父级的State

book.flutterchina.club/chapter2/fl…

  • 1、通过Context获取

    context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。

    一般来说,如果 StatefulWidget 的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其 State 对象;如果StatefulWidget的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其State对象。但是通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:

    如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;

    如果 State不希望暴露,则不提供of方法

    这个约定在 Flutter SDK 里随处可见。所以,上面示例中的Scaffold也提供了一个of方法,我们其实是可以直接调用它的:

    // 直接通过of静态方法来获取ScaffoldState
    ScaffoldState _state=Scaffold.of(context);
          
    static ScaffoldState of(BuildContext context) {
        assert(context != null);
        final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>();
        if (result != null)
          return result;
        );
      }
    复制代码
  • 2、 通过GlobalKey

    Flutter还有一种通用的获取State对象的方法——通过GlobalKey来获取! 步骤分两步:

    1. 给目标StatefulWidget添加GlobalKey

      //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
      static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
      ...
      Scaffold(
          key: _globalKey , //设置key
          ...  
      )
      复制代码
    2. 通过GlobalKey来获取State对象

      _globalKey.currentState.openDrawer()
      复制代码
      注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复
      复制代码

      GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。

* 11、pubspec.yaml

一、逐一解释一下各个字段的意义:

  • name:应用或包名称。

  • description: 应用或包的描述、简介。

  • version:应用或包的版本号。

  • dependencies:应用或包依赖的其它包或插件。

  • dev_dependencies:开发环境依赖的工具包(而不是flutter应用本身依赖的包)。

  • flutter:flutter相关的配置选项。

二、依赖

  • 最常用的pub仓库包。。 依赖方式

    english_words: ^4.0.0
    复制代码

    直接依赖便可

  • 依赖本地包

    如果我们正在本地开发一个包,包名为pkg1,我们可以通过下面方式依赖:

    dependencies:
      pkg1:
            path: ../../code/pkg1
    复制代码

    路径可以是相对的,也可以是绝对的。

  • 依赖git包

    dependencies:
      pkg1:
        git:
          url: git://github.com/xxx/pkg1.git
    复制代码
    dependencies:
      package1:
        git:
          url: git://github.com/flutter/packages.git
          path: packages/package1   
    复制代码

    上面假定包位于Git存储库的根目录中。如果不是这种情况,可以使用path参数指定相对位置,例如:

12、assets

Flutter APP 安装包中会包含代码和 assets(资源)两部分。Assets 是会打包到程序安装包中的,可在运行时访问。常见类型的 assets 包括静态数据(例如JSON文件)、配置文件、图标和图片等。

在构建期间,Flutter 将 asset 放置到称为 asset bundle 的特殊存档中,应用程序可以在运行时读取它们 (但不能修改)

  • assets 变体

    flutter:
      assets:
        - graphics/background.png
    复制代码

    那么这两个graphics/background.pnggraphics/dark/background.png 都将包含在您的 asset bundle中。前者被认为是main asset (主资源),后者被认为是一种变体(variant)。

  • 加载本地 assets

    • 通过rootBundle (opens new window)对象加载:每个Flutter应用程序都有一个rootBundle (opens new window)对象, 通过它可以轻松访问主资源包,直接使用package:flutter/services.dart中全局静态的rootBundle对象来加载asset即可。

    • 通过 DefaultAssetBundle (opens new window)加载:建议使用 DefaultAssetBundle (opens new window)来获取当前 BuildContext 的AssetBundle。 这种方法不是使用应用程序构建的默认 asset bundle,而是使父级 widget 在运行时动态替换的不同的 AssetBundle,这对于本地化或测试场景很有用。

      通常,可以使用DefaultAssetBundle.of()在应用运行时来间接加载 asset(例如JSON文件),而在widget 上下文之外,或其它AssetBundle句柄不可用时,可以使用rootBundle直接加载这些 asset,例如:

      import 'dart:async' show Future;
      import 'package:flutter/services.dart' show rootBundle;
      ​
      Future<String> loadAsset() async {
        return await rootBundle.loadString('assets/config.json');
      }
      复制代码
  • 设置特地平台的,开机图片和app图标。

    book.flutterchina.club/chapter2/fl…

  • 加载图片

    类似于原生开发,Flutter也可以为当前设备加载适合其分辨率的图像。

    • …/my_icon.png
    • …/2.0x/my_icon.png
    • …/3.0x/my_icon.png

    AssetImage('graphics/background.png')

  • 设置特地平台的,开机图片和app图标。 book.flutterchina.club/chapter2/fl…

... 更新中

Guess you like

Origin juejin.im/post/7038836371435290655