Advertisement

Flutter 开发者必备知识

阅读量:

作者:禅与计算机程序设计艺术

1.简介

Flutter 是 Google 推出的一款最新跨平台 UI 框架,旨在高效地为 iOS、Android 和 Web 平台开发高质量的应用原生界面。本文将从多个方面对 Flutter 的核心技术进行深入解析。

  • 第一部分分别阐述了 Dart 编程语言与 Flutter 框架的基本概念及其核心功能。
  • 第二部分具体说明了 Flutter 中不同 Widget 类型及其功能实现的具体细节。
  • 第三部分重点阐述了 Flutter 动画功能的实现原理及其应用场景。
  • 第四部分深入解析了 Flutter 局部布局机制的设计理念与技术实现。
  • 第五部分全面解析了 Dart 语言中的异步编程模型及其实际应用。
  • 第六部分深入探讨了 Flutter 插件系统的开发流程及其扩展可能性。

最后,还会涉及一些 Dart/Flutter 的应用案例,以及最后提出若干建议。

如果你有相关基础,或对 Flutter 有热情,欢迎加入 QQ 群(879339926)参与讨论。

2. Dart/Flutter 是什么?

2.1 Dart

Dart 是一种多功能编程语言,在多种环境中都能运行并发挥其作用。它具备语法设计简单明了、支持静态类型系统的特点,并基于单继承设计实现功能;允许指定可选参数以提高灵活性;支持函数式编程与类别的结合使用;同时提供mixins 和接口功能以增强代码复用性。Dart 能够开发网页端(Web)、桌面端及移动端应用;同时也能用于开发服务器端(Dart VM 和 Flutter)相关功能;此外还适用于开发命令行工具、后台任务等任何需要与代码交互的应用场景。

Dart提供混合内核(mixins),使一个类能够采用多个混合内核(mixin),从而获得不同的特性(features)。混合内核(mixin)能够扩展其他类的行为(behavior),并提供额外的方法(methods)、属性(attributes)和功能(functionality)。Flutter正是通过混合内核(mixin)来实现其widgets功能的。

Dart 也具备注解功能,并采用 @AnnotationName 对代码块进行标记,在此基础之上进而实现类似于 AOP 的作用

Dart 的运行时环境基于虚拟机架构(VM),为开发者提供了高性能且可靠的运行环境。该平台集成了一系列现代化的开发工具集合体, 为开发者提供了友好的IDE体验与高效的代码实现功能体。

2.2 Flutter

Flutter 是 Google 推出的于2018年推出的一款跨平台应用程序框架。该应用框架基于Skia Graphics库进行图形绘制。特别适用于打造流畅运行界面的原生用户体验。其核心编程语言采用Dart语言,并在此基础上提供了包括Flutter Futures、Streams、Stateulling等多种高级API功能模块以及一系列官方支持库。

Flutter以Dart语言为基础开发用户界面的各种组件,这些组件的构成方式涵盖颜色元素、文本信息以及图像元素等基本要素,并提供按钮控件、输入控件以及滚动视图等多种常见功能类型。其中还包括动画效果、布局系统以及路由管理等功能模块,并通过Flutter提供的各种Widget来实现所有功能模块的具体开发与应用。

Flutter 的声明式和响应式编程模式使 UI 逻辑与UI表现相互独立,并降低了代码的复杂性。这种设计使得开发者更容易维护UI代码,并显著提高了开发效率。

Flutter 覆盖多种操作系统平台(包括 Android、iOS、Web、MacOS 和 Windows),并且还包含一个尚未发布的 Linux 环境(此环境可能很快会开放)。此外该应用还具备热更新功能(此功能可帮助开发者迅速进行迭代开发)。

3. Flutter 里的 Widget 是什么?

3.1 StatelessWidget

在Flutter开发中,默认情况下使用的是最基础的一个 widget类——QuartZWidget。基于其命名逻辑设计的QW组件主要用于表示固定不变的状态。该组件仅受自身属性的影响,并不会根据外界环境变化而发生任何变化,在每次调用 setState方法后会自动生成新的子节点布局。常见的QW实例包括空容器(Chip)、填充空间(Pad)、对齐组件(Center)、排列布局(Align)、文本框(Text)和图标容器(Icon)等。

3.2 StatefulWidget

在 Flutter 中,默认情况下不会为 widget 实例维护任何内部或全局的状态信息。因此,在需要展示固定 UI 的情况下使用 ?Widget 是合适的。然而,在处理某些动态或交互式的 UI 元素时,则可以选择使用 ?State 来管理这些元素的状态。当需要展示固定 UI 的情况下使用 ?Widget 是合适的吗?对于那些依赖于某种动态变化的状态或者交互操作的情况来说,则更适合采用 ?State 来进行管理

比如,在屏幕方向发生改变的时候(或者说是当屏幕朝向发生变化时),可能会引发当前页面(也就是组件)的 reassemble 方法被调用。这个方法的主要职责是完成页面或组件重新整合所需的资源清理任务。此时可以通过设置状态的方式(即记录下来),保存当前的方向信息,并在didUpdateWidget方法中读取之前保存的状态信息(即之前记录的方向),从而来进行相应的事务处理。

除了现有的imated组件外,在Flutter中还提供了丰富的自定义组件。这些组件能够继承自imated组件。

3.3 CustomPaint

在 Flutter 应用中,CustomPaint 被认为是最为复杂的 widget 之一。通过Canvas与CanvasContext的交互关系,在绘制过程中 CustomPaint能够呈现丰富的图形样式。值得注意的是,其功能依赖于Painter对象的引入——Painter类则负责处理绘图相关操作。其中paint方法是核心功能模块——该对象被用来指定绘图区域,并在绘画时会动态调用一系列drawXXX方法实现细节动画效果。这种机制赋予了CustomPaint极强的灵活性与适应性——使其能够在UI界面中呈现多种独特的视觉效果组合。

3.4 LayoutBuilder

LayoutBuilder 可用于自定义子 widget 的布局设置。每当 LayoutBuilder 调用 setState 方法时,它会重新执行 builder 函数,并使用 LayoutConstraints 参数来生成一个新的Widget结构。该生成的复合型 widget 结构将嵌入到父级 widget 的布局系统中。通常用于组织并配置一组子 widget组件,并根据需要调整它们的位置、大小、间距等属性后返回组合型 widgets结构。

例如,在构建一个ButtonGroup组件时,请确保该组件包含三个按钮,并且每个按钮具有相同的尺寸一致但并非等宽设计。当使用Stack控件来实现该效果时,默认情况下会根据内容进行调整(即Stack会自动拉伸或收缩以适应不同宽度的内容),这可能导致按钮之间存在间隙(即在垂直方向上可能无法完全紧邻)。这时建议采用LayoutBuilder工具来精确计算每个按钮的实际宽度,并将其整合在一起以消除间隙问题(即通过LayoutBuilder动态计算按钮的宽度并将其整合在一起)以确保最终效果理想

4. Flutter 中的动画是如何实现的?

Flutter 中的动画主要包含三种类型:Tween Animation、AnimationController 和 AnimatedBuilder。其中前两种基于 timeline-based 动画机制(anime),其特点是每个帧之间都是平滑过渡的效果;第三种则采用 state-based 动画机制(anime),通过控制台的 animate 方法来实现动画效果。

4.1 Tween Animation

每个帧都是一个平滑过渡动画的过程被称为 Tween 动画。它可用于控制 widget 属性值的变化过程。当使用 Tween 时,请先创建一个 AnimationController 实例,并将其传递给 tween 对象作为参数进行配置。接着创建一个 Tweent 对象,并设置其目标属性值。然后调用 tween.animate 函数,并传递 controller 参数以完成动画逻辑配置。最后一步是获取 tween 生成的动画对象,并将其应用于驱动 widget 的相应属性值变化过程。

具体的代码如下所示:

复制代码
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    final controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    
    final animation = Tween<double>(begin: 0, end: 300).animate(controller);
    
    return Center(
      child: Container(
        width: animation.value,
        height: animation.value * 0.5,
        color: Colors.red,
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

这个例子中的动画对象按照Container的尺寸逐步放大,在这一过程中其宽度从初始状态开始逐步增加至300像素,并相应地提升其高度至同样比例。具体而言,在这一过程中容器的高度始终保持在容器宽度的一半位置上。

4.2 AnimationController

AnimationController 是 Flutter 中用于控制 timeline-based animation 核心功能模块。该控件承担展示动画、切换场景以及调整动画状态的任务。

AnimationController 必须配置两个属性:duration 和 vsync。duration 用于设置动画的持续时间长度;vsync 则用于配置同步机制,默认情况下会触发一次事件每秒。

一般情况下而言,在这里我们只需使用 play() 方法以启动视频片段。如需实现无限循环播放效果,请调用 repeat() 方法。通过调用 pause() 方法可暂时停止当前播放。若需重新启动视频片段,请使用 resume() 方法。最后,在完成所有操作后,请确保调用 dispose() 方法以释放相关资源。

具体的代码如下所示:

复制代码
    class _MyAnimatedBoxState extends State<MyAnimatedBox> with TickerProviderStateMixin {
    
      late AnimationController _animationController;
    
      @override
      void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat();
      }
    
      @override
      void dispose() {
    _animationController.dispose();
    super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    final sizeAnimation = CurvedAnimation(
      parent: _animationController, 
      curve: Curves.elasticInOut
    );
    
    return Align(
      alignment: Alignment.center,
      child: Container(
        width: sizeAnimation.value, 
        height: sizeAnimation.value / 2, 
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(sizeAnimation.value), 
          color: Colors.blue[900],
        ),
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在这个示例中,在CurvedAnimation类中我们对动画曲线进行了调节以实现 animation效果,在该示例中 曲线参数设置为弹性进退模型(ElasticInOut)通过这种方式可以让 animation 运动轨迹更加自然流畅 并呈现出富有弹性的视觉效果

4.3 AnimatedBuilder

AnimatedBuilder 是一种旨在实现 timeline-based animation 的 widget。它在每一帧中都会调用 builder 函数,并传递更新后的动画值。

具体的代码如下所示:

复制代码
    class _MyAnimatedBoxState extends State<MyAnimatedBox> with TickerProviderStateMixin {
    
      late AnimationController _animationController;
    
      @override
      void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat();
      }
    
      @override
      void dispose() {
    _animationController.dispose();
    super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (_, __) => Align(
        alignment: Alignment.center,
        child: Container(
          width: _animationController.value * 200, 
          height: _animationController.value * 200 / 2, 
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(_animationController.value * 200), 
            color: Colors.blue[900],
          ),
        ),
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

这里采用 AnimatedBuilder 的原因是由于我们只需获取动画的值,并非必须对其添加额外逻辑;因此只需利用这一 widget 即可实现目标。

5. Flutter 中的布局是怎样实现的?

布局作为Flutter的核心功能之一,在界面展示方面发挥着关键作用。其主要职责是将各个widget按照指定的位置和尺寸渲染到屏幕上。具体而言,在实现过程中主要分为两个步骤:首先通过父 widget 的performLayout方法以确定自身的布局逻辑与显示位置;其次通过子 widget 的paint方法实现图形元素的具体绘制过程。

5.1 一级布局 - ListView 和 Column

在 Flutter 应用开发中,ListView 和 Column 被视为最为广泛应用的一级布局组件。它们不仅支持将一组 widget 按照水平或垂直方向进行排列布局,并且都具备滚动功能。

ListView 和 Column 的主要区别在于 Column 可以在竖向方向上放置 widget 而不是 horizontally 面向下方。然而,在 default 设置下,默认情况下 TopPadding 和 BottomPadding 会影响布局效果。

具体的代码如下所示:

复制代码
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('ListView and Column')),
        body: Padding(
          padding: const EdgeInsets.all(10.0),
          child: Column(
            children: <Widget>[
              Expanded(
                child: Image.network("https://picsum.photos/id/${DateTime.now().millisecondsSinceEpoch % 100 + 1}/200/300"),
              ),
              Text("This is a text in column"),
              Row(children: [
                IconButton(icon: Icon(Icons.favorite_border), onPressed: () {}), 
                Text("Button")
              ],),
            ],
          ),
        ),
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在这个示例中,默认情况下展开组件在水平方向,并按顺序排列各组件;随后,在垂直方向上安排所有组件的位置,并确保各组件之间的间距合理;所有组件都设置了宽度为100%以充分利用剩余空间,并根据需求调整其显示内容的位置

5.2 二级布局 - GridView 和 Flexible

在 Flutter 应用开发中,GridView 和 Flexible 被视为常见的二级布局模块。通过网格系统进行排版设计,在界面设计中提供了灵活多样的排版可能性。支持 widget 根据需求调整尺寸比例,在组件开发中提供了高度可定制的功能性组件组合。结合 Scrolling Controller 实现数据列表的动态滚动功能,在用户体验优化方面发挥了重要作用

具体的代码如下所示:

复制代码
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('GridView and Flexible')),
        body: SafeArea(
          bottom: false,
          top: true,
          left: true,
          right: true,
          child: Padding(
            padding: const EdgeInsets.all(10.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text("This is a grid view", style: TextStyle(fontSize: 24)),
                Expanded(
                  child: GridView.count(
                    crossAxisCount: 3,
                    shrinkWrap: true,
                    physics: BouncingScrollPhysics(),
                    children: List.generate(
                      9,
                      (index) => Card(
                        margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
                        child: Center(child: Text("$index")),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在这个例子中,我们采用了GridView来实现九宫格卡片布局,并通过Expanded来填充父容器。GridView.count方法中的crossAxisCount参数配置了横向网格的数量;当shrinkWrap设为true时,则表示子widget不发生重叠以模拟瀑布流效果。BouncingScrollPhysics()负责实现列表的惯性滑动效果。

5.3 混合布局 - Stack、Positioned、Transform

属于 Flutter 特有的混合排版系统中包含了 Stack、Positioned 和 Transform 三个组件元素;这些组件不仅能够实现多种复杂的排版效果,并且能够灵活地结合使用不同基础布局模式以达到预期的设计目标。

Stack 用于组织子 widget 的层级布局结构,在 Positioned 中设置坐标值来安排各个子 widget 的位置,并通过 Transform 执行一系列变形操作以达到视觉效果需求。具体的代码如下所示:

复制代码
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('Stack, Positioned, Transform')),
        body: SafeArea(
          bottom: false,
          top: true,
          left: true,
          right: true,
          child: Stack(
            fit: StackFit.expand,
            children: [
              Positioned(
                left: MediaQuery.of(context).size.width / 4,
                top: MediaQuery.of(context).size.height / 4,
                child: Opacity(opacity:.8, child: CircleAvatar(backgroundImage: NetworkImage("https://picsum.photos/id/${DateTime.now().millisecondsSinceEpoch % 100 + 1}/100"))),
              ),
              Positioned(left: 100, top: 200, child: Text("Hello World!", style: TextStyle(fontSize: 24))),
              Transform.rotate(angle: 45*pi/180, origin: Offset(.0, 0.0), child: Container(width: 100, height: 100, color: Colors.green))
            ],
          ),
        ),
      ),
    );
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在这个例子中,我们通过 Stack 来达成一个圆形头像的目标,在其上叠加一个文字,并完成一个旋转效果。Positioned 用于影响子组件的绝对位置设置,Transform 完成一些变换效果。fit 参数则会影响 Stack 在空间不足时的布局扩展方式。

6. Dart 中的异步机制是如何工作的?

Dart 语言实现了两种并发模型:单线程和事件驱动模型。

单线程模型就是只有一个线程来负责处理所有的事情,在这种情况下所有的I/O操作(包括读取文件、发送/接收网络请求以及数据库操作)必须在另一个独立的线程上进行异步执行否则整个应用系统会陷入阻塞状态。这种模式确保了代码设计上的简洁明了因此在计算密集型的任务中通常采用这种模式。

其核心概念是程序在运行过程中会定期触发一系列事件,并将这些触发的事件会被编码为消息然后通过消息队列系统将这些消息发布出去由相应的接收线程按照预先设定好的处理顺序依次处理这种方式实现了对I/O操作的隐式管理从而简化了程序的实现逻辑然而需要具备深入掌握异步编程原理的前提条件并且开发人员仍然无法完全掌控CPU资源的状态

Dart 采用了基于事件驱动的架构来实现并发处理。该语言支持 Future、Stream、Isolate 和 Timer 等多种异步机制,并且这些异步机制均遵循基于事件的统一模式进行操作。

Future 通常用于表示某个任务的状态或结果,并且可以绑定回调函数,在特定条件下触发并反馈结果状态。Stream 通常用于传递非同步的数据流,并能够订阅并接收数据变化,在新数据到达时会自动触发订阅者的处理流程。Isolate 通常用于在独立于当前线程的任务空间中运行代码逻辑,并特别适用于构建服务器端服务程序。Timer 对象常用于实现延迟调用功能,并特别适合用来创建具有延迟执行功能的组件。

具体来说,在远程文件下载的情况下,我们可以使用HttpClient来执行请求下载操作,并在成功回调时执行相应的处理逻辑。

复制代码
    import 'dart:io';
    
    main() async {
      var httpClient = new HttpClient();
      try {
    var request = await httpClient.getUrl(Uri.parse("http://example.com"));
    var response = await request.close();
    if (response.statusCode == HttpStatus.ok) {
      var contents = await response.transform(utf8.decoder).join();
      print(contents);
    } else {
      print("Error getting data");
    }
      } catch (e) {
    print("Error: $e");
      } finally {
    httpClient.close();
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

这个例子中,在我们的实现中使用了HttpClient作为一个单一实例负责发送HTTP请求。通过try-catch-finally结构,在正常情况下获取响应数据时会输出结果;当遇到异常情况时则会记录错误信息。

7. Flutter 中的插件系统

在 Flutter 中引入第三方能力是通过构建插件系统实现的关键。为了便于开发者快速上手并降低开发门槛,Flutter SDK 在初始版本中就已经集成了一系列功能模块(如图片加载、SharedPreferences 等),这些功能模块能够显著提升开发效率,并简化操作流程。然而尽管如此,在实际应用中发现该系统仍存在一定的局限性,在这种情况下建议开发者根据自身需求进行定制化扩展以满足复杂场景下的功能需求

7.1 创建插件

Flutter 插件基于 Dart Package 开发,并将包含 pubspec 文件以及 Plugin 类。

该pubspec文件用于记录插件的基本信息及其相关依赖项。其中包含了 插件名称、版本号、开发者等关键元数据 插件类中共设有三个核心功能模块:

attachedToEngine方法在Flutter启动后立即执行,在此时刻段内可以获得应用上下文、方法通道等信息。
regarding registration在插件注册完成后会立即触发该操作。
regarding registration当插件与Flutter通信时会触发相应的回调函数。
attachedToEngine方法在Flutter退出后立即执行以确保数据完整性。
after being detached fromEngine退出后应立即执行该操作以防止数据泄露风险。

具体示例如下:

pubspec.yaml

复制代码
    name: my_plugin
    description: A new flutter plugin project.
    
    version: 0.0.1
    
    environment:
      sdk: ">=2.1.0 <3.0.0"
    
    dependencies:
      flutter:
    sdk: flutter
    
    dev_dependencies:
      pedantic: ^1.9.0
      test: any
    
    flutter:
      plugin:
    platforms:
      android:
        package: com.example.my_plugin
        pluginClass: MyPlugin
      ios:
        pluginClass: MyPlugin
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

lib/my_plugin.dart

复制代码
    import 'dart:async';
    
    import 'package:flutter/services.dart';
    import 'package:meta/meta.dart';
    
    class MyPlugin {
      static const MethodChannel _channel =
      const MethodChannel('my_plugin');
    
      static Future<String?> get platformVersion async {
    final String? version = await _channel.invokeMethod('getPlatformVersion');
    return version;
      }
    }
    
    class MyPluginInterface {
      final BinaryMessenger messenger;
    
      MyPluginInterface({@required this.messenger});
    
      Future<void> doSomething() async {
    final Map<String, dynamic> args = <String, dynamic>{};
    final ByteData? result =
        await messenger.send('my_plugin.doSomething', args);
    final String response =
        const Utf8Decoder().convert(result!)?? 'Unknown error occurred.';
    print(response);
      }
    }
    
    class MyPluginFactory implements PluginFactory {
      @override
      MyPlugin create() {
    return MyPlugin._internal();
      }
    }
    
    class MyPluginHandler implements PlatformMessageHandler {
      @override
      Future<ByteData?> handleMethodCall(
      int channelId, String method, ByteData? arguments) async {
    switch (method) {
      case "my_plugin.doSomething":
        break;
    
      default:
        throw ArgumentError.value(method, 'Unsupported method');
    }
    return null;
      }
    }
    
    class MyPluginRegistrar implements Registrar {
      @override
      void addInstance(InstanceManager instanceManager) {}
    
      @override
      void addListener(VoidCallback listener) {}
    
      @override
      void removeListener(VoidCallback listener) {}
    
      @override
      bool get ready => true;
    
      @override
      FutureOr<T> invokeAsync<T>(String method, dynamic arguments) {
    throw UnimplementedError();
      }
    
      @override
      R invokeSync<R>(String method, dynamic arguments) {
    throw UnimplementedError();
      }
    
      @override
      Future<dynamic> send(int channelId, String message) {
    throw UnimplementedError();
      }
    }
    
    class MyPluginRegistry {
      static void registerWith() {
    BinaryMessages.registerNamedPlugin('my_plugin', MyPluginFactory());
      }
    }
    
    extension MyPluginExtension on BuildContext {
      MyPluginInterface get myPlugin => MyPluginInterface(messenger: SystemChannels.platform);
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

7.2 使用插件

7.2.1 安装插件

安装插件有两种方式:

  • 添加 dependencies 到 pubspec.yaml
  • 配置项目级别的Gradle配置

当采用第一种方式时,在项目根目录中执行命令 flutter packages get 会成功下载和安装相应的插件。

若采用第二种方式,则需更新项目层级的 build.gradle 文件,并增添如下配置项:

复制代码
    buildscript {
      ...
       repositories {
       google()
       jcenter()
       }
    
       dependencies {
     classpath 'com.google.gms:google-services:4.3.3'
    
     // Add this line to include the Firebase Messaging dependency.
     classpath 'com.google.firebase:firebase-plugins:1.0.5'
     apply plugin: 'com.google.gms.google-services'
       }
    }
    ...
    allprojects {
    repositories {
        google()
        jcenter()
    }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

然后在应用级别的 build.gradle 文件中增加依赖关系:

复制代码
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    
    // Add this line to use the Firebase Messaging library.
    implementation 'com.google.firebase:firebase-messaging:20.0.0'
    
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-2'
    }
    apply plugin: 'com.google.gms.google-services'
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

最后,我们需要在项目级别的 build.gradle 文件中添加插件的注册代码:

复制代码
    apply plugin: 'com.google.gms.google-services'
    // ADD THIS AT THE END OF THE FILE
    
    // Run this once to be able to run the application with the plugin
    afterEvaluate{
    signingConfigs {
        release {
            storeFile file("release.keystore")
            storePassword "<PASSWORD>"
            keyAlias "keyalias"
            keyPassword "keypassword"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
    }
    
    flutter {
    source '../path/to/my_plugin'
    target 'app'
    }
    
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    
    // Add this line to use the Firebase Messaging library.
    implementation 'com.google.firebase:firebase-messaging:20.0.0'
    
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-2'
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

这时候就可以在应用中使用 Firebase Messaging 插件了。

7.2.2 使用插件

7.2.2.1 获取实例

第一步必须于我们的MainActivity中注册插件。通常情况下,在Application启动时会注册该插件,并会在Application关闭后自动取消注册。

复制代码
    public class MainActivity extends AppCompatActivity implements LifecycleObserver {
    
      private MyPluginInterface plugin;
    
      public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    // Register the plugin
    plugin = MyPluginRegistry.registrarFor("my_plugin").activity();
    
    // Observe lifecycle events
    getLifecycle().addObserver(this);
      }
    
      @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
      void onDestroy() {
    // Unregister the plugin when the activity is destroyed
    plugin.unregister();
      }
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在MyPluginInterface类中, 通过registrarFor方法可以获得一个插件实例, activity()方法会返回一个MyPluginInterface对象, 允许调用插件的相关功能。

注意:我们需要调用 unregister() 方法来释放插件的资源,避免内存泄漏。

7.2.2.2 调用方法

插件的调用方法与 Android 中一样,可以采用异步或同步的方式。

异步方式:

复制代码
    final completer = Completer<void>();
    plugin.doSomething().then((_) {
      completer.complete();
    }).catchError((error) {
      completer.completeError(error);
    });
    return completer.future;
    
      
      
      
      
      
      
    
    代码解读

同步方式:

复制代码
    try {
      plugin.doSomething();
      return true;
    } catch (error) {
      return false;
    }
    
      
      
      
      
      
    
    代码解读

全部评论 (0)

还没有任何评论哟~