0%

本文基于Flutter 3.0

Flutter App基于Dart语言编写,提供了一套简单易用的API,可以让开发者在Flutter中快速开发出一个精美的APP。那么在Flutter中是如何绘制一个APP呢,runApp是怎么将我们编写的Widget等添加到手机上的呢?本文简单从Widget,Element,RenderObjet三者的关系来梳理一下Flutter的绘制过程。

让我们运行一个“最”简单的Flutter App,分析一下在这个过程中涉及到的Widget、Element、RenderObject这三个tree的关系。

1
2
3
4
5
6
7
8
9
main() {
runApp(const Center(
// Center非必须,为了让文本居中显得更清晰
child: Text(
"Hello center text!",
textDirection: TextDirection.ltr, // 文本方向
),
));
}

上述代码的效果如下:

阅读全文 »

本文基于Dart 2.17


Dart App中所有的代码都在一个isolate中运行(各个isolate之间的代码运行时是隔离的),一个isolate有自己的heap,维持有一个消息队列event_loop,处理两种消息:

  1. event queue 执行用户点击、屏幕刷新、绘制,一般的Future、IO、Stream流等,每次执行完毕都会先检查执行micro task queue中的任务,直到其为空再执行下一个event queue
  2. microTask queue 优先执行,一般执行跑完即弃的小任务,如Dart内部的微任务

上述两种event会在普通的Dart同步方法执行完毕后执行,无论是microTask还是普通的event,他们都是concurrency并行执行(也就是说实际上还是上一个执行完毕,再执行另外一个),所以如果这些event中存在耗时长的方法,依旧会阻塞其他方法的执行,可能导致UI卡顿等情况。

阅读全文 »

Dart读取文件时,先在Dart代码创建File引用,通过与IOServiceIsolate通信(先通过IO Service而发送请求到native端,等到native执行完操作之后再回调结果)从而实现对文件的读写。

实现一个简单的读取文件的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import 'dart:io';

main() {
var filePath =
r"G:/21996.1.210529-1541.co_release_CLIENT_CONSUMER_x64FRE_en-us.iso";

var file = File(filePath);

var startTime = printCurrentTimeMs("start run file.readAsBytes");
file.readAsBytes().then((value) {
printCurrentTimeMs("file.readAsBytes() finish",
lastTimeMs: startTime,
suffix: "\nfile.readAsBytes() result:${value.length}");
});
printCurrentTimeMs("finish run file.readAsBytes");
}

int printCurrentTimeMs(String prefix, {String? suffix, int? lastTimeMs}) {
var currentTimeMs = DateTime.now().millisecondsSinceEpoch;
var timeElapseString =
lastTimeMs == null ? "" : ", time elapse:${currentTimeMs - lastTimeMs}ms ";
print(
"$prefix current time($currentTimeMs)$timeElapseString${suffix ?? ""}");
return currentTimeMs;
}

整个过程如下:

阅读全文 »

本文是对Dart官方VM的介绍的总结摘要,推荐直接阅读官方原文

Dart VM is a collection of components for executing Dart code natively. Notably, it includes the following:

  • Runtime System
    • Object Model
    • Garbage Collection
    • Snapshots
  • Core libraries’ native methods
  • Development Experience components accessible via service protocol * Debugging * Profiling * Hot-reload
  • Just-in-Time (JIT) and Ahead-of-Time (AOT) compilation pipelines
  • Interpreter
  • ARM simulators

下图是runtime执行代码的示意图:

isolate

阅读全文 »

Isolate

💡 本文基于Dart 2.17.1

Isolate, an isolated Dart execution context.

All Dart code runs in an isolate, and code can access classes and values only from the same isolate. Different isolates can communicate by sending values through ports (see ReceivePort, SendPort).

In Dart an isolate has its own event loop, its own global fields, can run in parallel with other isolates and have their own live-cycle.
https://github.com/dart-lang/sdk/issues/36097#issuecomment-746510375

阅读全文 »

Flutter UI 绘制与InheritedWidget解析

Flutter的Widget分为StatefulWidgetStatelessWidget ,二者都继承自Widget

此外还有一种用来传输数据的Widget——InheritedWidget,与上述两者不太一样的是,他的继承关系是:InheritedWidgetProxyWidgetWidget

Flutter的渲染流程如图:

flutter_widget_element_renderobject_relationship

阅读全文 »

在Flutter中,当需要填充容器(Row, Column, or Flex)剩余空间的时候,可以使用ExpandedFlexible,本文对这二者的差异做一分析。

分析

Expanded 比较容易理解,他会强制child改变大小,占据容器的剩余空间,如果有多个Expanded的话,会按照他们的flex占比来分配每个child可以占据的空间大小。

Flexible 稍微特殊一些,有时候看起来似乎他的child占据的大小既不全是父布局的剩余空间,也不全是刚刚包裹child内容的大小。

让我们看一下Flexible的源码:

阅读全文 »

Compose屏幕适配

一种Compose中屏幕适配的解决方案,灵感参考头条屏幕适配AndroidAutoSize等,以设计稿宽度和屏幕水平方法大小为准,等比拉伸控件大小。

后文附有本方案的Kotlin语言实现,使用只需要两个步骤即可:

1
2
3
4
5
6
7
8
9
10
// 1 初始化
class MainApp : Application() {
override fun onCreate() {
super.onCreate()
SizeEtx.init(this, 375) // 375为设计稿宽度
}
}

// 2 使用
size(width = 9.composeDp, height = 16.composeDp)

主要的设计思想

阅读全文 »

说明

FLutter中的错误不会导致应用程序奔溃,只会终止执行出错代码之后的逻辑,在导致Widget.build()返回为null的错误会导致Widget构建失败,并返回红底黄字的错误原因Widget(在Release模式则会显示为灰底区域);一般来说,Flutter中的错误都会被FlutterError.onError捕获并处理;对于异步方法产生异常等Flutter框架没有捕获的情况,会交由当前代码所在的Zone处理(这些异常可以使用runZonedGuarded捕获并处理)。

为什么 flutter 触发异常的时候不会崩溃?
这个和 flutter 的消息循环机制有关,任务分两种,一个是微任务 microtask,一个是事件 event,他们有自己的队列,每个任务是相互独立的,一旦某个任务触发异常,也就是导致这个任务后续代码无法执行,并不会影响其他任务执行

本文基于Flutter (Channel stable, 2.2.3)

详细说明

阅读全文 »

说明

当APP目标版本是Android 10(API 29)及以后时,由于Android引入了分区存储,APP不能直接通过路径访问文件,访问外部存储空间中的媒体文件除了需要READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限之外,需要通过其他APP分享的Uri读写文件,同理要给其余APP分享文件也许要通过FileProvider生成Uri并赋予对应的权限。

本文以从相册中获取图片、请求系统裁剪并返回图片为例展示对应的适配方法。

实际操作

1.从相册中获取图片

阅读全文 »