Android学Flutter学习笔记 第一节 Android视角认知Flutter(View,intent,Async UI)

序言

前面经过30多篇dart学习笔记文章的学习,我们已经基本掌握了dart的语法,当然如果后续有需要我也会继续去补充,接下来我们开始flutter的学习。

flutter配置是相对复杂的,第一目标可以先运行flutter项目到浏览器。Androidsdk 对于java sdk,gradle sdk的版本都有要求,flutter对于Androidsdk也有要求,我建议全部使用最新版本。

同时也少不了用到一些科学上网的方法,祝你顺利。

Flutter for Android developers

在使用 Flutter 进行开发时,你的 Android 知识和技能非常有价值,因为 Flutter 在很多功能和配置上都依赖于移动操作系统。Flutter 是一种构建移动用户界面的新方式 ,但它有一个插件系统,用于与 Android(以及 iOS)进行非界面任务的通信 。

(你应该意识到上面的话非常重要)

View

Flutter 中的 View 对应的是什么?

在安卓系统中,View(视图)是屏幕上所有显示内容的基础。按钮、工具栏和输入框等所有内容都是 View。在 Flutter 中,与 View 大致相当的是 Widget(组件)。Widget 与安卓的 View 并非完全对应,但在你熟悉 Flutter 的工作原理时,可以把它们看作是 "声明和构建用户界面的方式"。

然而,这些与视图存在一些差异。首先,Widget有着不同的生命周期:它们是不可变的,仅存在到需要被更改时为止。每当Widget或其状态发生变化时,Flutter 的框架就会创建一个新的Widget实例树。相比之下,安卓视图一旦绘制完成,在调用 invalidate(失效)方法之前不会重新绘制。

Flutter 的Widget很轻量,部分原因在于它们的不可变性。因为它们本身并非视图,也不直接绘制任何内容,而更像是对用户界面及其语义的一种描述,这些描述会在底层 "膨胀" 为实际的视图对象。

Flutter 包含 Material Components 库。这些是实现 Material Design 设计规范的组件。Material Design 是一个灵活的设计系统,针对包括 iOS 在内的所有平台进行了优化。

但 Flutter 具有足够的灵活性和表现力,能够实现任何设计语言。例如,在 iOS 系统上,你可以使用 Cupertino 组件来制作一个看起来符合苹果 iOS 设计语言的界面。

我该如何更新Widget

在安卓系统中,你可以通过直接修改视图来更新它们。然而,在 Flutter 中,组件是不可变的,不能直接更新,相反,你必须操作组件的状态。

这就是**有状态(Stateful)和无状态(Stateless)**组件概念的由来。无状态组件(StatelessWidget)顾名思义,就是没有状态信息的组件。

当你所描述的用户界面部分除了对象中的配置信息外不依赖任何其他内容时,无状态组件(StatelessWidgets)会非常有用。

例如,在 Android 系统中,这类似于放置一个带有logo图标的 ImageView。该图标在运行期间不会发生变化,所以在 Flutter 中使用无状态组件(StatelessWidget)即可。

如果你想基于发起 HTTP 请求后收到的数据或用户交互来动态更改用户界面,那么你必须使用 StatefulWidget,并告知 Flutter 框架该组件的状态已更新,以便框架能更新该组件。

这里需要注意的重要一点是,无状态组件和有状态组件在核心上的表现是相同的。它们都会在每一帧重新构建,不同之处在于有状态组件(StatefulWidget)拥有一个状态对象,该对象会跨帧存储状态数据并进行恢复。

如果你有疑问,那么请始终记住这条规则:如果一个weight发生了变化(例如,由于用户交互),那它就是有状态的。但是,如果一个小部件对变化做出反应,而包含它的父部件自身不对变化做出反应,那么父部件仍然可以是无状态的。(是否有状态,只考虑这个控件本身,无关父子控件)

下面的示例展示了如何使用无状态组件(StatelessWidget)。一个常见的无状态组件是文本组件(Text widget)。如果你查看文本组件的实现,会发现它是无状态组件的子类。

dart 复制代码
Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

如你所见,文本部件(Text Widget)没有与之关联的状态信息,它仅渲染在其构造函数中传入的内容,仅此而已。

但是,如果你想让 "I like Flutter!" 动态变化,比如在点击悬浮按钮时发生变化,该怎么办呢?

要实现这一点,请将 Text 组件包装在一个 StatefulWidget 中,并在用户点击按钮时对其进行更新。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text.
  String textToShow = 'I Like Flutter';

  void _updateText() {
    setState(() {
      // Update the text.
      textToShow = 'Flutter is Awesome!';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
我该如何布局我的组件?我的 XML 布局文件在哪里?

在 Android 中,你用 XML 编写布局,但在 Flutter 中,你用组件树(widget tree)编写布局。

以下示例展示了如何显示一个带有内边距的简单小部件:

dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: Center(
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          padding: const EdgeInsets.only(left: 20, right: 30),
        ),
        onPressed: () {},
        child: const Text('Hello'),
      ),
    ),
  );
}
我该如何在我的布局中添加或移除组件?

在安卓系统中,你可以在父视图上调用 addChild () 或 removeChild () 来动态添加或移除子视图。而在 Flutter 中,由于 widgets 是不可变的,所以没有与 addChild () 直接对应的方法。相反,你可以向父组件传递一个返回 widget 的函数,并通过一个布尔标志来控制该子组件的创建。

例如,以下是当你点击悬浮操作按钮(FloatingActionButton)时,如何在两个小部件之间切换的方法:

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle.
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  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()),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
如何为一个小部件制作动画?

在安卓系统中,你要么使用 XML 创建动画,要么在视图上调用 animate () 方法。在 Flutter 中,通过将 widgets 包裹在动画 widget 内部,使用动画库来为 widgets 设置动画

在 Flutter 中,使用 AnimationController,它是一个 Animation,能够暂停、查找、停止和反转动画。它需要一个 Ticker 来在 vsync 发生时发出信号,并且在运行时会在每一帧上生成 0 到 1 之间的线性插值。然后,你可以创建一个或多个动画,并将它们附加到该控制器上。

例如,你可以使用 CurvedAnimation 来实现沿插值曲线的动画。从这个意义上来说,控制器是动画进度的 "主" 源,而 CurvedAnimation 会计算曲线,以替代控制器默认的线性运动。与 widgets 一样,Flutter 中的动画也采用组合方式工作。

在构建小部件树时,你要将动画(Animation)分配给小部件的动画属性,例如淡入淡出过渡(FadeTransition)的不透明度,然后让控制器启动该动画。

下面的示例展示了如何编写一个 FadeTransition,当你按下悬浮按钮(FloatingActionButton)时,它会将小部件淡入为一个图标:

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const FadeAppTest());
}

class FadeAppTest extends StatelessWidget {
  const FadeAppTest({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fade Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyFadeTest(title: 'Fade Demo'),
    );
  }
}

class MyFadeTest extends StatefulWidget {
  const MyFadeTest({super.key, required this.title});

  final String title;
  @override
  State<MyFadeTest> createState() => _MyFadeTest();
}

class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FadeTransition(
          opacity: curve,
          child: const FlutterLogo(size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Fade',
        onPressed: () {
          controller.forward();
        },
        child: const Icon(Icons.brush),
      ),
    );
  }
}
我该如何使用 Canvas 进行绘制 / 绘画?

在安卓系统中,你会使用 Canvas 和 Drawable 在屏幕上绘制图像和形状。Flutter 也有一个类似的 Canvas API,因为它基于相同的底层渲染引擎 Skia。因此,对于安卓开发者来说,在 Flutter 中绘制画布是一项非常熟悉的任务。

Flutter 有两个类可帮助你在画布上绘图:CustomPaint 和 CustomPainter,其中后者用于实现你在画布上绘图的算法。

下面的代码实现了一个画图器

dart 复制代码
import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: DemoApp()));

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) => const Scaffold(body: Signature());
}

class Signature extends StatefulWidget {
  const Signature({super.key});

  @override
  SignatureState createState() => SignatureState();
}

class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset>[];
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          RenderBox? referenceBox = context.findRenderObject() as RenderBox;
          Offset localPosition = referenceBox.globalToLocal(
            details.globalPosition,
          );
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (details) => _points.add(null),
      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) {
    var paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    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;
}
我该如何构建自定义小部件?

在 Android 中,你通常会继承 View 类,或者使用已有的视图,通过重写并实现方法来实现期望的行为。

在 Flutter 中,通过组合更小的组件(而非继承它们)来构建自定义组件。这在某种程度上类似于在 Android 中实现自定义 ViewGroup,其中所有的构建块都是现成的,但你要提供不同的行为 ------ 例如,自定义布局逻辑。

例如,如何构建一个在构造函数中接收标签的 CustomButton?创建一个组合了带标签的 ElevatedButton 的 CustomButton,而不是通过继承 ElevatedButton:

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));
  }
}

然后使用 CustomButton,就像使用任何其他 Flutter 小部件一样:

dart 复制代码
@override
Widget build(BuildContext context) {
  return const Center(child: CustomButton('Hello'));
}

Intents

Flutter 中与 Intent 对应的是什么?

在 Android 中,意图(Intents)有两个主要用例:在活动(Activities)之间导航,以及与组件通信。另一方面,Flutter 没有意图(intents)这一概念,不过你仍然可以通过原生集成(使用插件)来启动意图。

Flutter 并没有与活动(activities)和片段(fragments)直接对应的概念;相反,在 Flutter 中,你是在同一个活动内使用导航器(Navigator)和路由(Routes)在不同屏幕之间进行导航的。

路由是应用程序中 "屏幕" 或 "页面" 的抽象概念,而导航器是管理路由的组件。路由大致对应于 Activity,但含义并不完全相同。导航器可以通过推入和弹出路由来实现屏幕之间的切换。导航器的工作方式类似于栈,你可以使用 push () 方法将想要导航到的新路由推入栈中,也可以在想要 "返回" 时使用 pop () 方法将路由从栈中弹出。

在安卓系统中,你需要在应用的 AndroidManifest.xml 文件中声明你的活动。

在 Flutter 中,你有几种在页面之间导航的选项:

・指定路由名称的映射。(使用 MaterialApp)

・直接导航到一个路由。(使用 WidgetsApp)

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'),
      },
    ),
  );
}

通过将路由名称推送到导航器来导航到该路由。

dart 复制代码
Navigator.of(context).pushNamed('/b');

Intents 的另一个常见用例是调用外部组件,例如相机或文件选择器。为此,你需要创建一个原生平台集成(或使用现有的插件)。

这一点暂不考虑。

在 Flutter 中,我该如何处理来自外部应用程序的传入意图?

Flutter 可以通过直接与 Android 层通信并请求共享的数据来处理来自 Android 的传入意图。

下面的示例在运行我们 Flutter 代码的原生活动上注册了一个文本共享意图过滤器,这样其他应用就可以与我们的 Flutter 应用共享文本了。

基本流程意味着我们首先在 Android 原生端(在我们的 Activity 中)处理共享的文本数据,然后等待 Flutter 请求该数据,再通过 MethodChannel 提供它。

首先,在 AndroidManifest.xml 中注册所有意图的意图过滤器:

dart 复制代码
<activity
  android:name=".MainActivity"
  android:launchMode="singleTop"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize">
  <!-- ... -->
  <intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
  </intent-filter>
</activity>

然后在 MainActivity 中,处理该意图,从意图中提取被分享的文本,并将其保存。当 Flutter 准备好进行处理时,它会使用平台通道请求数据,然后数据会从原生端发送过来:

dart 复制代码
package com.example.shared;

import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.NonNull;

import io.flutter.plugin.common.MethodChannel;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

  private String sharedText;
  private static final String CHANNEL = "app.channel.shared.data";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
      if ("text/plain".equals(type)) {
        handleSendText(intent); // Handle text being sent
      }
    }
  }

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      GeneratedPluginRegistrant.registerWith(flutterEngine);

      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
              .setMethodCallHandler(
                      (call, result) -> {
                          if (call.method.contentEquals("getSharedText")) {
                              result.success(sharedText);
                              sharedText = null;
                          }
                      }
              );
  }

  void handleSendText(Intent intent) {
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
  }
}

最后,在渲染小部件时从 Flutter 端请求数据:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

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 as String;
      });
    }
  }
}
startActivityForResult () 的等效方法是什么?

Navigator 类负责处理 Flutter 中的路由,用于从已推入栈中的路由获取返回结果。这是通过等待 push () 方法返回的 Future 来实现的。

例如,要启动一个允许用户选择其位置的定位路线,你可以按以下方式操作:

dart 复制代码
Object? coordinates = await Navigator.of(context).pushNamed('/location');

然后,在你的位置路由内部,一旦用户选择了他们的位置,你就可以用结果弹出栈

dart 复制代码
Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});

Async UI

Flutter 中与 runOnUiThread () 等效的方法是什么?

Dart 采用单线程执行模型,支持隔离区(Isolates,一种在另一个线程上运行 Dart 代码的方式)、事件循环和异步编程。除非你生成一个隔离区,否则你的 Dart 代码会在主 UI 线程中运行,并由事件循环驱动。Flutter 的事件循环相当于 Android 的主 Looper------ 也就是附加到主线程的 Looper。

Dart 的单线程模型并不意味着你需要将所有操作都作为阻塞操作来运行,那样会导致用户界面冻结。与 Android 不同(Android 要求主线程始终保持空闲),在 Flutter 中,要使用 Dart 语言提供的异步工具,比如 async/await,来执行异步工作。如果你在 C#、Javascript 中使用过 async/await 模式,或者使用过 Kotlin 的协程,可能会对这种模式很熟悉。

例如,你可以通过使用 async/await 并让 Dart 来承担繁重的工作,从而运行网络代码而不会导致用户界面卡顿:

dart 复制代码
Future<void> loadData() async {
  final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final response = await http.get(dataURL);
  setState(() {
    widgets = (jsonDecode(response.body) as List)
        .cast<Map<String, Object?>>();
  });
}

一旦期待的网络请求完成,就通过调用 setState () 来更新用户界面,这会触发小部件子树的重建并更新数据。

以下示例异步加载数据并在 ListView 中显示:

dart 复制代码
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];

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

  @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 Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }

  Future<void> loadData() async {
    final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final response = await http.get(dataURL);
    setState(() {
      widgets = (jsonDecode(response.body) as List)
          .cast<Map<String, Object?>>();
    });
  }

}
如何将工作转移到后台线程?

在安卓系统中,当你想要访问网络资源时,通常需要切换到后台线程来执行相关操作,这样做是为了不阻塞主线程,从而避免出现应用无响应(ANR)的情况。例如,你可能会使用 AsyncTask、LiveData、IntentService、JobScheduler 任务,或者是带有在后台线程上运行的调度器的 RxJava 管道。

由于 Flutter 是单线程的,并且运行着一个事件循环(类似于 Node.js),所以你无需担心线程管理或生成后台线程的问题。如果你要执行 I/O 密集型工作,比如磁盘访问或网络调用,那么你可以安全地使用 async/await,一切就都准备好了。另一方面,如果你需要执行会让 CPU 一直处于忙碌状态的计算密集型工作,你则希望将其移至隔离区(Isolate),以避免阻塞事件循环,就像在 Android 中要把任何类型的工作都放在主线程之外一样。

对于 I/O 密集型工作,将函数声明为异步函数,并在函数内部等待长时间运行的任务:

dart 复制代码
Future<void> loadData() async {
  final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final response = await http.get(dataURL);
  setState(() {
    widgets = (jsonDecode(response.body) as List)
        .cast<Map<String, Object?>>();
  });
}

这就是你通常进行网络或数据库调用的方式,这两种都是输入 / 输出操作。

在安卓系统上,当你继承 AsyncTask 时,通常会重写 3 个方法,即 onPreExecute ()、doInBackground () 和 onPostExecute ()。而在 Flutter 中没有与之对应的内容,因为你会等待一个长时间运行的函数,剩下的工作则由 Dart 的事件循环来处理。

然而,有时你可能在处理大量数据时,用户界面会出现卡顿。在 Flutter 中,可以使用 Isolates 来利用多个 CPU 核心执行耗时较长或计算密集型的任务。

隔离区是独立的执行线程,它们不与主执行内存堆共享任何内存。这意味着你无法访问主线程中的变量,也不能通过调用 setState () 来更新用户界面。与安卓线程不同,隔离区名副其实,不能共享内存(例如,以静态字段的形式)。

下面的示例在一个简单的隔离区中展示了如何将数据共享回主线程以更新用户界面。

dart 复制代码
Future<void> loadData() async {
  ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);

  // The 'echo' isolate sends its SendPort as the first message.
  SendPort sendPort = await receivePort.first as SendPort;

  final msg =
      await sendReceive(
            sendPort,
            'https://jsonplaceholder.typicode.com/posts',
          )
          as List<Object?>;
  final posts = msg.cast<Map<String, Object?>>();

  setState(() {
    widgets = posts;
  });
}

// The entry point for the isolate.
static Future<void> dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  ReceivePort port = ReceivePort();

  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);

  await for (var msg in port) {
    String dataUrl = msg[0] as String;
    SendPort replyTo = msg[1] as SendPort;

    http.Response response = await http.get(Uri.parse(dataUrl));
    // Lots of JSON to parse
    replyTo.send(jsonDecode(response.body));
  }
}

Future<Object?> sendReceive(SendPort port, Object? msg) {
  ReceivePort response = ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

在这里,dataLoader () 是在其自身独立执行线程中运行的隔离区。在这个隔离区中,你可以执行更多 CPU 密集型处理(例如解析大型 JSON),或者进行计算密集型数学运算,如加密或信号处理。

你可以运行下面的完整示例

dart 复制代码
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];

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

  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }

  ListView getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }

  Future<void> loadData() async {
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    // The 'echo' isolate sends its SendPort as the first message.
    SendPort sendPort = await receivePort.first as SendPort;

    final msg =
        await sendReceive(
              sendPort,
              'https://jsonplaceholder.typicode.com/posts',
            )
            as List<Object?>;
    final posts = msg.cast<Map<String, Object?>>();

    setState(() {
      widgets = posts;
    });
  }

  // The entry point for the isolate.
  static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    ReceivePort port = ReceivePort();

    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);

    await for (var msg in port) {
      String dataUrl = msg[0] as String;
      SendPort replyTo = msg[1] as SendPort;

      http.Response response = await http.get(Uri.parse(dataUrl));
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body));
    }
  }

  Future<Object?> sendReceive(SendPort port, Object? msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }

}
Flutter 中与 OkHttp 等效的是什么?

在 Flutter 中,使用广受欢迎的 http 包进行网络请求非常简单。

虽然 http 包并不具备 OkHttp 中的所有功能,但它抽象掉了许多你通常需要自己实现的网络相关内容,使其成为一种进行网络调用的简便方式。

要将 http 包添加为依赖项,请运行 flutter pub add:

bash 复制代码
flutter pub add http

要进行网络调用,请在异步函数 http.get () 上调用 await:

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

Future<void> loadData() async {
  var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  http.Response response = await http.get(dataURL);
  developer.log(response.body);
}
如何显示一个长时间运行任务的进度?

在安卓系统中,当你在后台线程上执行长时间运行的任务时,通常会在用户界面中显示一个进度条(ProgressBar)视图

在 Flutter 中,使用 ProgressIndicator 组件。通过一个布尔标志控制其何时渲染,以编程方式显示进度。让 Flutter 在你的长时间运行任务开始前更新状态,并在任务结束后隐藏它。

在下面的示例中,build 函数被拆分成了三个不同的函数。如果 showLoadingDialog 为 true(当 widgets 为空时),则渲染 ProgressIndicator。否则,使用从网络请求返回的数据渲染 ListView。

bash 复制代码
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];

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

  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }

  ListView getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }

  Future<void> loadData() async {
    final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final response = await http.get(dataURL);
    setState(() {
      widgets = (jsonDecode(response.body) as List)
          .cast<Map<String, Object?>>();
    });
  }
}
相关推荐
蒸鱼Yuzheng7 小时前
设备端性能工件可靠导出:断点续传、哈希、manifest 与失败恢复
android·自动化测试·python·adb·数据完整性
差不多的周周8 小时前
《骨架上下文:基于零镜头骨架的动作识别的骨架侧上下文提示学习》论文核心部分详解
学习
Century_Dragon9 小时前
从挂图到VR:汽修专业的一堂新能源原理课可以怎么上~
学习
美狐美颜sdk9 小时前
直播APP接入美颜SDK后出现卡顿怎么办?从性能瓶颈寻找解决方案
android·人工智能·音视频·美颜sdk·直播美颜sdk
知识分享小能手9 小时前
C++ 学习教程,从入门到精通,C++对象和类 — 详细知识点总结(10)
开发语言·c++·学习
美狐美颜SDK开放平台9 小时前
直播APP开发实战:从摄像头调用到视频美颜sdk集成
android·人工智能·计算机视觉·音视频·直播美颜sdk
New农民工10 小时前
倒立摆训练秘籍:理解训练参数
学习
alexhilton10 小时前
生命周期感知的多模块启动模式
android·kotlin·android jetpack
L@ncor10 小时前
第五章 基于低代码平台的智能体搭建 · 学习笔记(Coze / Dify / FastGPT / n8n)
笔记·学习·低代码·agent·prompt工程
迪丽热爱11 小时前
多媒体应用20-903(补)
学习