跟🤡杰哥一起学Flutter (二、从 Android ✈ Flutter 的知识迁移)

1. 引言

🤡上节《开发初体验》阐述了杰哥 开始学Flutter的动机 (适配鸿蒙)Flutter开发环境搭建及常见问题 (Windows系统) ,以及 逐行解读Demo里的main.dart,算是对Flutter建立了简单的基础认知。

😆 搭完环境,跑完demo,按照一般教程的套路,要么开始学一个个Widget,要么按部就班学Dart语法。可以,但对于我们这种练习时长两年半的安卓🐔来说,效率太低了。🤔️更快的学习方式应该是利用 知识的可迁移 ,通过比对 Android 和 Flutter 中 相近知识点的差异 来进行 发散学习

😗 So,本节以官方文档为引 → 英文《Flutter for Android developers》/ 中文《给 Android 开发者的 Flutter 指南》进行对比学习,笔者在原内容的基础上做了一些调教 (内容调整,精简代码和添加注释),理解起来会更容易(个人感觉),有错的地方还望不吝指出,万分感激😁,当然喜欢原汁原味的可以直接看原文档。话不多说,直接开冲。

2. View(视图) VS Widget(控件)

2.1. 声明式UI

AndroidFlutter 采用两种不同的编程风格来完成UI编程,分别是:命令式 (主动设置)声明式 (被动变化) 。怎么理解?写个简单例子:按钮控制文本的显示和隐藏,先是Android代码:

Dart 复制代码
var visible = true
val tv = findViewById<TextView>(R.id.tv)
val bt = findViewById<Button>(R.id.bt)

bt.setOnClickListener {
    visible = !visible;
    if(visible) {
        tv.visibility = View.VISIBLE
        bt.text = "隐藏文本"
    } else {
         tv.visibility = View.GONE
        bt.text = "显示文本"
    }
}

需要先找到控件,然后 主动 设置控件的属性,再看看Flutter怎么写:

Dart 复制代码
class _TestPageState extends State<MyHomePage> {
  var visible = false;

  void _switchVisible() {
    setState(() {
      visible = !visible;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column
      (
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Visibility(visible: visible, child: const Text("文本")),
          ElevatedButton(
            onPressed: _switchVisible,
            child: Text(visible ? '隐藏文本' : '显示文本'),
          ),
        ]
    );
  }
}

不用关心具体的设置细节,只需 配置好状态(数据) 和 界面(控件) 的关系 ,Flutter 会根据状态变化 自动更新UI 。一个通俗易懂的比喻😝:命令式 → 你让TA动,声明式 → TA自己动。

2.2. "三棵树🌲"渲染机制

先提一嘴 Flutter的三棵树渲染机制 ,有助于后面内容的学习😄,Flutter将视图树 (View Tree) 的概念进行了扩展,将视图数据的组合和渲染抽象成三个部分(内容摘取自:《Flutter 陈航 09-视图渲染 三棵树 Widget》):

  • Widget对视图的结构化描述 ,存储视图渲染相关的 配置信息:布局、渲染属性、事件响应等信息。
  • ElementWidget的实例化对象 ,承载视图构建的上下文数据,连接Widget到完成最终 渲染桥梁
  • RenderObject负责实现视图渲染的对象

Widget被设计成不可变的 (immutable)

当视图渲染的配置信息发生变化,Flutter会 重建Widget树 来进行数据更新。因为不涉及实际渲染位图,所以它只是一个 轻量级 的数据结构,重建成本很低。另外,得益于Widget的 不可变性 ,可以以较低成本进行 渲染结点 (Element) 的 复用 ,因此在真实渲染树中可能存在 不同Widget对应同一个渲染结点的情况 (多对一)。

把Flutter的渲染过程简单分成这三步:

  • 通过 Widget树 生成对应的 Element树
  • 创建相应的 RenderObject 并关联到 Element.renderObject 属性上;
  • 构建成 RenderObject树深度优先遍历 ,确定树中各对象的 位置和尺寸 (布局) ,把它们 绘制 到不同图层上。SkiaVsync信号同步 时直接从渲染树 合成Bitmap ,最后交给 GPU渲染

例子:Row容器的左边放一个Image,有边放一个Column套两个Text:

Flutter遍历完Widget树,创建子Widget对应的Element的同时,创建与之关联的、负责实际布局和绘制的 RenderObject:

增加Element中间层的好处 (提高渲染效率)

在这一层 将Widget树的变化(diff)做抽象 ,只将 真正需要修改的部分同步到RenderObject树 中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。Element是可复用的 ,Widget触发重建,Flutter会根据重新前后Widget树的 渲染类型及属性变化情况 决定后续的 复用或新建 。比如:只是调整了一个渲染样式,Flutter会通知Element复用现有节点,只是 同步属性 到RenderObject出发绘制。如果Widget树中涉及到 Widget类型替换或变更,Flutter 则会将老的Element及RenderObject 摘除,让新Widget 重新走一遍创建 Element 和 RenderObject 的流程,挂载到 Element 树和 RenderObject 树上。

2.3. StatelessWidget & StatefulWidget

上节解读官方Demo里的main.dart后,我们知道了:

  • 没有状态改变 ,只是用作展示 → 用 StatelessWidget
  • 需要保存 (绑定) 状态 ,且可能出现状态变化 → 用 StatefulWidget

两者的本质行为是一致的,每一帧都会重建,不同之处在于后者有一个 跨帧存储和恢复状态的State对象 。有些读者可能会混淆,这里特意把 WidgetState(状态) 拎出来:

Widget是不变的变化的是与之绑定的State ,State的任何更改都会 强制Widget重新构建!!!

StatefulWidget发生状态变化时的代码调用流程

调用了状态类中某个方法,里面调用了 setState (通知Flutter框架重新构建Widget) → build ()didUpdateWidget (处理Widget的更新)dispose(释放资源)initState (初始化状态)didChangeDependencies (处理依赖关系的变化) → build(根据新的状态构建Widget)

两条编码建议

  • 不要滥用StatefulWidget !假如父布局是StatefulWidget,其State每调用一次更新UI,会间接触发所有子View的销毁和重建 。虽然Flutter通过Element层最大程度降低 对真实渲染视图的修改,提高了渲染效率。但无法避免大量的Widget对象的销毁和重建,如果某个Widget的重建涉及耗时操作,页面的渲染性能也会急剧下降。不涉及到状态变化的Widget,老老实实用StatelessWidget。
  • 不要在build()的方法内部进行耗时操作!除了我们主动通过State刷新UI外,在某些特殊场景下,Widget 的build()有可能会执行多次。

2.4. 如何在布局中添加或删除一个组件

在Android中可以调用父View的 addChild()/addView()removeChild()/removeView() 动态地增删子View。而在Flutter中,Widget是不可变的,没法这样玩,要实现类似的效果,可以这样做:在State里定义一个标志位 ,定义一个方法 → 根据标志位的值返回不同Widget,然后在合适的地方调用这个方法。示例如下:

Dart 复制代码
class _SampleAppPageState extends State<SampleAppPage> {
  bool toggle = true;	// 定义标志位
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  // 根据标志位返回不同Widget的方法
  Widget _getToggleChild() {
    if (toggle) {
      return const Text('Toggle One');
    } else {
      return ElevatedButton(
        onPressed: () {},
        child: const Text('Toggle Two'),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: Center(
        child: _getToggleChild(),	// 调用生成不同Widget的方法
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}

2.5. Widget 如何实现动画

在Android中可以通过XML定义动画,也可以调用View.animate() 来实现动画。而在Flutter中,通过将 Widget 嵌入一个 动画Widget 的方式来实现 Widget 的动画效果。这里改下官方例子,干掉了CurvedAnimation (曲线插值),直接采用AnimationController默认的线性插值器,使代码更易理解:

Dart 复制代码
/// with 关键字用于混入(mixin),它允许将其它类的功能添加到另一个类中,从而实现多继承
/// 这段声明说明,_MyFadeTest 可以访问 TickerProviderStateMixin 中的方法和属性
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    /// 初始化AnimationController实例 → 控制动画的执行过程,包括:开始、停止、暂停、反向播放
    controller = AnimationController(
      /// 动画执行时长
      duration: const Duration(milliseconds: 2000),
      /// 垂直同步信号,用于同步屏幕刷新和动画更新,避免出现屏幕闪烁、撕裂等问题。
      /// 这里的this → TickerProviderStateMixin
      /// 这个Tiker除了在垂直同步时发出信号,还在运行时创建一个介于0-1间的线性差值。
      vsync: this,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        /// 过渡动画组件,用于实现透明度渐变动画
        child: FadeTransition(
          opacity: controller,	// 子控件透明度
          child: const FlutterLogo(
            size: 100,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Fade',
        onPressed: () {
          controller.forward();	// 按钮按下时开始执行动画
        },
        child: const Icon(Icons.brush),
      ),
    );
  }
}

通过上述代码,总结下Flutter Widget动画的基本玩法:

  • 自定义State类时 with TickerProviderStateMixin
  • 重写 initState() 时 初始化一个 AnimationController 实例,传入 duration和 vsync → Ticker
  • 把需要执行动画的 Widget 放到 动画Widget 中,在合适的时机,调用 controller.forward() 执行动画;

2.6. 如何创建自定义Widget

在Android中自定义View,一般是直接继承View或其子类进行重写和扩展。Flutter中 推荐 我们通过 组合 的方式来自定义Widget,而不是继承它们。使用代码示例 (🐶继承重写Widget也可以,不过比直接组合复杂):

Dart 复制代码
class CustomButton extends StatelessWidget {
  final String label;

  const CustomButton(this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text(label),
    );
  }
}

/// 需要用到自定义Widget的地方
@override
Widget build(BuildContext context) {
  return const Center(
    child: CustomButton('Hello'),
  );
}

2.7. 常用View对应的Widget

2.7.1. LinearLayout

水平方向Row竖直方向Column,代码示例:

Dart 复制代码
@override
Widget build(BuildContext context) {
  return const Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Row One'),
      Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Column One'),
          Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[Text('Row One'), Text('Row Two')])
        ],
      );
    ],
  );
}

运行效果:

2.7.2. RelativeLayout

通过组合使用 ColumnRowStack Widget 实现 RelativeLayout 的效果。

2.7.3. ListView

Flutter中的listView依旧对应Android里的ListView,不过不需要Adapter,得益于 Flutter widget 不可变的特点,只需向ListView传入一组 widget,Flutter 会保证滑动的快速顺畅。代码示例如下:

Dart 复制代码
class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView(children: _getListData()),
    );
  }

  List<Widget> _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $i'),
      ));
    }
    return widgets;
  }
}

如何知道点击了哪个列表项?

Flutter 没有 Android ListView里的 onItemClickListener,解决方法是:把列表项Widget放到一个GestureDetector 里,传入onTap属性来实现点击监听。

如何动态更新ListView?

  • 少量数据 :在 setState() 里创建一个新的 List,并 将数据从旧列表拷贝到新列表
  • 动态列表或大量数据 :创建一个 ListView.Builder,接收两个参数:列表初始长度 和 ItemBuilder方法

ListView.Builder 使用代码示例:

Dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Sample App'),
    ),
    body: ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    ),
  );
}

Widget getRow(int i) {
  return GestureDetector(
    onTap: () {
      setState(() {
        widgets.add(getRow(widgets.length));
        developer.log('row $i');
      });
    },
    child: Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row $i'),
    ),
  );
}

2.7.4. ScrollView

直接使用 ListView 这个Widget,在Flutter里,它既是 ScrollView 也是 ListView。

2.8. 如何为Widget添加点击监听器

在Android中可以调用 setOnClickListener 为View添加点击监听器,而在Flutter中有两种添加监听的方法:

  • Widget 支持事件监听 ,在处理事件的参数传入具体操作,比如 RaisedButton 的 onPressed 参数;
  • 不支持事件监听 ,可以把 Widget 丢到一个 GestureDetector 中并向它的 onTap 参数传递具体操作;
Dart 复制代码
class SampleTapApp extends StatelessWidget {
    const SampleTapApp({super.key});

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: GestureDetector(
            onTap: () {
              developer.log('tap');
            },
            child: const FlutterLogo(
              size: 200,
            ),
          ),
        ),
      );
    }
  }

除了OnTap还,GestureDetector 还可以监听非常多的手势:

2.9. 如何使用Canvas进行绘制

在Android中可以使用 Canvas 和 Drawable 将图片和形状绘制到屏幕上。而在Flutter 中也有类似 Canvas 的 API,因为它基于相同的底层渲染引擎 Skia 。Flutter 提供了两个类供我们绘制 → CustomPaintCustomPainter (支持自定义绘制算法),官方Demo实现了一个简易绘制签名的功能,核心代码如下:

Dart 复制代码
class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset>[];
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      /// 当用户触摸屏幕并拖动时触发,该回调会接收一个details参数,包含触摸事件的详细信息
      onPanUpdate: (details) {
        setState(() {
          /// RederBox对象表示组件在屏幕上的几何形状和位置信息
          RenderBox? referenceBox = context.findRenderObject() as RenderBox;
          /// 将全局坐标转换为局部坐标,以便在SignaturePainter中绘制
          Offset localPosition =
              referenceBox.globalToLocal(details.globalPosition);
          /// 存储用户绘制的点
          _points = List.from(_points)..add(localPosition);
        });
      },
      /// 用户松开手指时触发,将null添加到点列表中,表示绘制结束。
      onPanEnd: (details) => _points.add(null),
      /// 使用 CustomPaint 组件来绘制签名,传入自定义的Painter
      /// 并设置组件的大小为无限大,即占满整个屏幕
      child: CustomPaint(
        painter: SignaturePainter(_points),
        size: Size.infinite,
      ),
    );
  }
}

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List<Offset?> points;
  @override
  void paint(Canvas canvas, Size size) {
    // 创建Paint对象,设置绘制签名时所需的样式和属性
    var paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    /// 遍历点列表,当前点和下一个点都不为Null,绘制从当前点到下个点的线段
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }

  /// 判断是否需要重新绘制签名,新旧点数组比较
  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}

3. Activity、Fragment、Intent

Fluttre 中并没有 ActivityFragment 的对应概念,毕竟 只是在一个宿主Activity里蹦跶 ,你可以利用 Navigator(管理路径的工具)Route(应用内屏幕和页面的抽象) 进行 压栈(push)弹栈(pop) 操作实现页面的跳转。Flutter提供了两种在页面间导航的方式:

  • 定义一个 route 名字的 Map。(MaterialApp)
  • 直接导航到一个 route。(WidgetApp)

官方代码示例

Dart 复制代码
void main() {
  runApp(MaterialApp(
    home: const MyAppHome(), // Becomes the route named '/'.
    routes: <String, WidgetBuilder>{
      '/a': (context) => const MyPage(title: 'page A'),
      '/b': (context) => const MyPage(title: 'page B'),
      '/c': (context) => const MyPage(title: 'page C'),
    },
  ));
}

/// 通过路由名压栈到Navigator中,来跳转到这个Route
Navigator.of(context).pushNamed('/b');

/// 弹栈并返回结果,类似于startActivityForResult()
Object? coordinates = await Navigator.of(context).pushNamed('/location');
Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});

3.2. 如何处理从外部应用接收到Intent

运行Flutter的原生Activity 在AndroidManifest.xml中添加

Dart 复制代码
<intent-filter>
  <action android:name="android.intent.action.SEND" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:mimeType="text/plain" />
</intent-filter>

在这个原生Activity中处理Intent,提取出所需数据,然后 Flutter 再通过使用 MethodChannel 获取这个数据:

Dart 复制代码
public class MainActivity extends FlutterActivity {
    private String sharedText;
    private static final String CHANNEL = "app.channel.shared.data";
    
    @Override
  	protected void onCreate(Bundle savedInstanceState) {
        // 省略一堆Intent判断
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    }

    // 该方法会在Flutter启用时调用
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      // 插件注册到传入的引擎中
      GeneratedPluginRegistrant.registerWith(flutterEngine);
      // 常见一个MethodChannel用于原生Android和Flutter的通信
      // 获取Flutte引擎的二进制消息传递器,设置一个通信的频道名称
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
              .setMethodCallHandler(
                      (call, result) -> {
                          // 判断Flutter端调用的方法名是否为 getShredText
                          // 是的话,将变量的值作为结果发送回Flutter侧
                          // 将sharedText置空,避免重复使用旧数据
                          if (call.method.contentEquals("getSharedText")) {
                              result.success(sharedText);
                              sharedText = null;
                          }
                      }
              );
    }
}

当Widget渲染时,从Flutter端请求数据:

Dart 复制代码
class _SampleAppPageState extends State<SampleAppPage> {
  /// 获取跨平台通信的通道
  static const platform = MethodChannel('app.channel.shared.data');
  String dataShared = 'No data';

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }

  Future<void> getSharedText() async {
    /// 通过通道调用原生的方法
    var sharedData = await platform.invokeMethod('getSharedText');
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

小疑问

宿主Activity 和 Flutter 间通过 MethodChannel 来进行通信,那要实现 Flutter跳转原生Activity页面,该怎么做?约定一个跳转Activity的方法名,如:jumpToActivity,MethodChannel.MethodCallHandler 的onMethodCall() 里对方法名做判断 if (call.method.equals("jumpToActivity")) ,相等,调用 call.argument("key") 获取Flutter传递过来的参数,然后初始化创建Intent,startActivity跳转即可。

3.3. 如何监听 Activity 的生命周期

FlutterActivity 在内部捕获了几乎所有的 Activity 生命周期事件,并把它们发送给 Flutter 引擎。但大部分都向开发者屏蔽了,只提供了小部分可用的,你需要绑定 WidgetsBinding 观察者并监听 didChangeAppLifecycleState()

Flutter 为你管理引擎的启动和停止,在大部分情况下几乎没有理由要在 Flutter 一端监听 Activity 的生命周期。如果你需要通过监听生命周期来获取或释放原生的资源,无论如何都应该在原生端做这件事。

4. 异步UI

Flutter框架基于 单线程模型的Dart ,通过 Event Loop(事件循环) 实现 异步 ,2333,有Android Handler那味了。不过Dart中有着两个队列:Event Queue(事件队列)Microtask Queue(微任务队列,短时间就能完成的异步任务) ,后者优先级最高,每次的事件循环先查此队列是否有可执行的任务,没有才会去处理事件队列。异步队列很少必须要在事件队列前完成,因此用得不多。I/O、绘制、定时器这些异步事件,都是通过 事件队列 驱动主线程执行的。

4.1. Future-异步任务

Dart 为事件队列的任务提供了一层封装 → Future ,表示 一个在未来时间才会完成的任务 。把一个函数体放入 Future,就完成了 同步任务到异步任务的包装 。Future还提供 链式调用,可在异步任务完成后依次执行链路上的其它函数体。异步函数返回时,内部执行动作未结束,有两种选择:

  • 异步处理 :在返回的 Future 对象上注册一个 then,等Future执行体结束后,再进行异步处理;
  • 同步等待 :同步等待 Future 执行体结束,需在调用处使用 await ,调用处的函数体使用 async ,因为Dart里的await不是堵塞等待,而是 异步等待。

官方代码示例:

Dart 复制代码
import 'package:http/http.dart' as http;

// 异步等待请求加载完毕,注意async和await关键字的位置
Future<void> loadData() async {
  var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = jsonDecode(response.body);
  });
}

运行上述代码要先添加http依赖,直接命令行执行:flutter pub add http

不限定版本默认下载最新,需要限定版本可以这样:flutter pub add http:0.13.4

还可以在 pubspec.yaml 添加依赖,然后再执行 flutter pub get

4.2. Isolate-多线程

Dart 也提供了多线程机制 → Isolate ,每个 Isolate 都有自己的 EventLoopEventQueue ,Isolate 间 不共享任何资源 ,只能依靠 消息机制 (SendPort发送管道) 通信,所以 不存在资源抢占问题 。Isolate的创建非常简单,只要给定一个 函数入口 ,创建时再 传入一个参数 就可以启动了。

简单代码示例如下(将数据分享给主线程来更新UI):

Dart 复制代码
/// 加载数据并更新页面
Future<void> loadData() async {
	/// 创建一个ReceivePort → 用于接收来自Isolate的消息
  ReceivePort receivePort = ReceivePort();
  /// 创建一个新的Isolate,并将dataLoader 和 发送端口传给它
  /// 将在新的Isolate里执行dataLoader函数
  await Isolate.spawn(dataLoader, receivePort.sendPort);
	/// 等待从 Isolate 中获取第一个消息(SendPort),用于与 Isolate 进行通信
  SendPort sendPort = await receivePort.first;
	/// 向SendPort发消息
  List msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  );
	/// 更新UI
  setState(() {
    widgets = msg;
  });
}

/// 在Isolate里执行的具体异步操作
static Future<void> dataLoader(SendPort sendPort) async {
  /// 创建一个ReceivePort → 用来接收主线程的消息
  ReceivePort port = ReceivePort();
  /// 通过 sendPort 向主线程发送当前 Isolate 的 port.sendPort,用于通信
  sendPort.send(port.sendPort);
  /// 等待从主线程接收消息,并使用 for-in 循环来处理接收到的消息
  await for (var msg in port) {
    // 获取数据的URL地址、回复信息的SendPort
    String data = msg[0];
    SendPort replyTo = msg[1];
    String dataURL = data;
    /// 发起HTTP GET请求,并等待响应
    http.Response response = await http.get(Uri.parse(dataURL));
    /// 将解析后的Json发送会主线程
    replyTo.send(jsonDecode(response.body));
  }
}

/// 向指定SendPort发送消息,并返回一个Future对象用于接收响应
Future sendReceive(SendPort port, msg) {
  ReceivePort response = ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

总结一下

执行 I/O任务 ,如存储访问、网络请求,可以放心使用 async/await ,要执行 消耗CPU的计算密集型 工作,可以将其转移到一个 Isolate 上以避免阻塞事件循环。

5. 小结

到此,算是把官方文档中 🐶个人觉得比较重要 的内容过了一遍,相信读者们对Flutter的认知又更深了。剩余部分内容大部分是跟 Flutter插件(库) 有关的没,如:如何使用GPS传感器 → 使用geolocator插件;如何使用相机 → 使用image_picker插件;如何使用 Shared Preferences → Shared_Preferences 插件 等等...;还有一部分和 工程结构资源文件 相关的。抛砖引玉,点到为止,后续会进行相应的专题学习,敬请期待😁~

参考文献:

相关推荐
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
SoraLuna3 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
拭心5 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
AORO_BEIDOU7 小时前
单北斗+鸿蒙系统+国产芯片,遨游防爆手机自主可控“三保险”
华为·智能手机·harmonyos
带电的小王7 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
coder_pig7 小时前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
梦想平凡8 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道8 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
博览鸿蒙8 小时前
鸿蒙操作系统(HarmonyOS)的应用开发入门
华为·harmonyos
阿甘知识库9 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站