После Google I/O конференции ко мне обратились многие с вопросами о Flutter, среди которых было много связанных с собеседованиями. Сейчас некоторые вакансии начинают включать требования к Flutter, поэтому решил сделать небольшую подборку знаний.
⚠️ Полностью пройденное обучение обязательно, здесь можно лишь подвести некоторые итоги. Для более подробной информации рекомендую посетить официальные сайты Dart и Flutter.
Эта статья представляет собой подборку ключевых моментов. В случае возникновения вопросов вы можете кликнуть по ссылкам на соответствующие статьи или посмотреть мою колонку на Juejin.
Если вы уже знакомы с языками программирования, такими как JavaScript или Java/Kotlin, то вам будет легко освоить Dart. Dart объединяет черты динамических и статических языков, но здесь мы сосредоточимся на некоторых уникальных концепциях.
var
для объявления переменных, при этом Dart автоматически определяет тип данных. dynamic
указывает на динамический тип, который после компиляции становится объектом типа Object
. Во время компиляции нет проверки типов, а проверка происходит во время выполнения программы.if
, else
) поддерживают только значения типа bool
, а switch
поддерживает значения типа String
.- 3. В Dart массивы и List
являются эквивалентными.Runes
представляют символы Unicode, это строки, закодированные в UTF-32, используемые для представления символов, таких как Runes input = new Runes('\u{1f596} \u{1f44d}')
.int
и double
, но отсутствует тип float
.event
..id = 1
..type = ""
..actor = "";
Интересные операторы присваивания включают следующее:
AA ?? "999" // Если AA пустое, верните "999"
AA ??= "999" // Если AA пустое, установите его значение равным "999"
AA ~/ 999 // AA разделить на 999 целочисленно
Методы Dart могут иметь параметры со значением по умолчанию и именованные параметры.
Например, метод getDetail(String userName, reposName, {branch = "master"}) {}
имеет параметр branch
, который по умолчанию установлен как "master"
. При вызове метода можно явно указывать имя параметра: getRepositoryDetailDao("aaa", "bbbb", branch: "dev")
.
В Dart
нет ключевых слов public
, private
и других модификаторов доступа. Вместо этого _
перед названием метода или свойства указывает на то, что это приватное свойство или метод. Также есть аннотация @protected
.
Множественные конструкторы в Dart
могут быть реализованы с помощью именованных конструкторов.По умолчанию может быть только один конструктор, но можно создать пустой конструктор через вызов, например Model.empty()
. Вы можете использовать любое имя для такого конструктора. При этом значения аргументов должны быть указаны через this.name
:
class ModelA {
String name;
String tag;
ModelA(this.name, this.tag);
ModelA.empty() {}
ModelA.forName(this.name) {}
}
Все базовые типы данных и классы в Dart
наследуются от класса Object
, который имеет значение по умолчанию null
. Каждый объект имеет свои геттеры
и сеттеры
. Если же поле объявлено как final
или const
, то у него будет только геттер
.
Переопределять геттеры
и сеттеры
можно следующим образом:
@override
Size get preferredSize {
return Size.fromHeight(kTabHeight + indicatorWeight);
}
Ключевое слово assert
работает только в режиме проверки кода. Например, assert(unicorn == null);
выполнится только если условие истинно; в противном случае будет выброшено исключение. Это используется для проверки состояния приложения во время разработки.
При переопределении операторов, таких как +
и -
, они будут работать со всеми экземплярами данного класса:
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
} assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
Поддерживаемые для переопределения операторы:
В Dart нет отдельного понятия "интерфейса". Любой класс может использоваться как интерфейс путём использования ключевого слова implements
и переопределения методов родительского класса.
Dart также поддерживает mixins
, которые следует использовать после extends
и до implements
в объявлении класса.
В Dart можно использовать Zone
для создания окружения выполнения кода, которое можно рассматривать как песочницу. В Flutter C++ запускает Dart код внутри _runMainZoned
метода, используя runZoned
. Мы можем использовать Zone
для отлавливания глобальных ошибок и получения информации об окружении выполнения.
runZoned(() {
runApp(FlutterReduxApp());
}, onError: (Object obj, StackTrace stack) {
print(obj);
print(stack);
});
Вы можете зарегистрировать метод в runZoned
, чтобы выполнить обратный вызов при необходимости, как показано ниже. В любом месте внутри зоны можно получить доступ к onData
через ZoneUnaryCallback
и использовать его для вызова handleData
.
/// Обработчик данных
void handleData(dynamic result) {
print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
print(result);
}
/// Получение объекта типа ZoneUnaryCallback
final ZoneUnaryCallback<dynamic, int> onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
```/// Выполнение метода UnaryCallback и получение результата
int result = Zone.current.runUnary<int>(onData, 2);
Асинхронная логика может быть внедрена с помощью `scheduleMicrotask`:
```dart
Zone.current.scheduleMicrotask(() {
// Здесь выполняется асинхронная операция
});
Дополнительную информацию можно найти здесь: «Flutter полное руководство по разработке (XI. Полное понимание потока)»
Future
представляет собой использование и упаковку Zone
.
Например, Future.microtask
выполняет scheduleMicrotask
зоны, а result._complete
использует _zone.runUnary
и т.д.
factory Future.microtask(FutureOr<T> computation()) {
final _Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
В Dart вы можете определять асинхронные операции с использованием async
/await
или Future
. На самом деле, async
/await
являются просто синтаксическим сахаром, который компилятор преобразует в Future
.
Для более подробной информации посмотрите здесь:
Flutter полное руководство по разработке (XI. Полное понимание потока)
Stream
также является другим способом использования и упаковки Zone
.В Dart существует ещё один способ определения асинхронных операций — это использование async*
/ yield
или Stream
. На самом деле, async*
/ yield
являются просто синтаксическим сахаром, который компилятор преобразует в Stream
.
Stream также поддерживает синхронные операции.
Stream
состоит из четырёх ключевых объектов: Stream
, StreamController
, StreamSink
и StreamSubscription
. Можно сделать следующий вывод:- StreamController
: Как следует из названия класса, используется для управления всем процессом Stream
, предоставляя различные интерфейсы для создания различных потоков событий.StreamSink
: Обычно используется как вход для событий, предоставляя методы, такие как add
и addStream
.
Stream
: Сам источник событий, который обычно используется для прослушивания событий или их преобразования, таких как listen
и where
.
StreamSubscription
: Объект после подписки на события, используемый для управления подписками и другими операциями, такими как cancel
и pause
. Внутри он также является ключевым звеном в передаче событий.
Stream
создаётся с помощью StreamController
; события добавляются через StreamSink
; события прослушиваются через Stream
; управление подписками осуществляется через StreamSubscription
.
Stream
поддерживает различные изменения, такие как map
, expand
, where
, take
и другие операции, а также позволяет преобразовать его в Future
.
Дополнительно можно посмотреть: "Flutter полное руководство разработки (XI. Полное понимание Stream)".
Flutter отличается от React Native тем, что UI Flutter рендерится напрямую через Skia, в то время как React Native конвертирует контролы из JavaScript в native контролы и рендерит их через native. Дополнительную информацию можно найти здесь: "Глубокое понимание мобильной кросс-платформенной разработки".- В Flutter существует четыре дерева: Widget
, Element
, RenderObject
и Layer
. Между Widget
и Element
существует отношение один ко многим,
Element
хранит Widget
и RenderObject
, а между Element
и RenderObject
существует отношение один к одному (за исключением случаев, когда Element
не имеет RenderObject
, например, ComponentElement
не имеет RenderObject
),
Когда значение свойства isRepaintBoundary
у RenderObject
равно true
, это область становится Layer
. Поэтому не каждый RenderObject
имеет Layer
, поскольку это зависит от значения isRepaintBoundary
.
Для получения дополнительной информации см. «Flutter полное руководство разработки (IX. Глубокое понимание принципов рендера)».
В Flutter Widget
является неизменяемым, и каждое изменение сохраняется между кадрами с помощью State
. Реальная работа по созданию макета и рендеринга выполняется RenderObject
, а Element
служит мостом между ними. State
хранится внутри Element
.
BuildContext
в Flutter — это всего лишь интерфейс, а Element
реализует его.
В Flutter метод setState
фактически вызывает markNeedsBuild
, который помечает текущий Element
как Dirty
. Этот Element
будет перерисован только в следующую кадровую фрейм, что можно заметить из метода WidgetsBinding.drawFrame
. Это показывает, что setState
не применяется сразу же.
В Flutter после того, как RenderObject
проходит через attach/layout
, он использует markNeedsPaint()
для перерисовки страницы. Процесс примерно такой:
Область обновления определяется с помощью isRepaintBoundary
, после чего происходит обновление снизу путем вызова метода requestVisualUpdate
.
RenderObject
, выглядит так: layout
-> performResize
-> performLayout
-> markNeedsPaint
, однако пользователи обычно не вызывают layout
напрямую, а используют markNeedsLayout
. Конкретный процесс представлен ниже:В Flutter преобразование данных JSON из строки (String
) в объект (Object
) обычно осуществляется через промежуточное использование типа Map
.
В Flutter компонент InheritedWidget
используется для поддержки общего состояния, таких как Theme
, Localizations
, MediaQuery
и т.д., которые обеспечивают доступ к общему состоянию через контекст, например ThemeData theme = Theme.of(context)
.
В методе
inheritFromWidgetOfExactType
классаElement
есть поле_inheritedWidgets
типаMap<Type, InheritedElement>
.
_inheritedWidgets
обычно пустое, но при наличии родительского компонента типаInheritedWidget
или самого компонентаInheritedWidget
это поле инициализируется. Когда родительский компонент являетсяInheritedWidget
, этотMap
передается и объединяется уровнем ниже.Таким образом, когда мы вызываем
inheritFromWidgetOfExactType
черезcontext
, мы можем найти родительский компонентWidget
выше.
runtimeType
и key
:```dart
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
### Жизненный цикл в Flutter
- **Метод `initState()`** указывает, что текущее состояние `State` связано с контекстом `BuildContext`. Однако в данный момент `BuildContext` ещё не полностью загружен. Если вам требуется получить доступ к `BuildContext` в этом методе, вы можете использовать `Future.delayed`:
```dart
new Future.delayed(const Duration(seconds: 0), () => {context});
didChangeDependencies()
вызывается после initState()
, когда меняются зависимости объекта состояния (State
), а также при его инициализации.
deactivate()
вызывается, когда объект состояния временно удаляется из дерева представлений, а также при смене страницы.
dispose()
вызывается перед удалением виджета. Всегда вызывается метод deactivate()
до вызова dispose()
.
didUpdateWidget
вызывается, когда состояние виджета (widget
) изменяется.
С помощью StreamBuilder
и FutureBuilder
можно быстро использовать потоки (Stream
) и будущие значения (Future
) для быстрого создания асинхронных компонентов: «Flutter полное руководство по разработке (XI. Полное понимание Stream)».
В Flutter начальная точка входа для запуска приложения через runApp
является WidgetsFlutterBinding
, который состоит из нескольких подклассов BindingBase
: GestureBinding
, ServicesBinding
, SchedulerBinding
, PaintingBinding
, SemanticsBinding
, RendererBinding
, WidgetsBinding
путём использования миксинов.- В Flutter Dart использует модель «событийного цикла и очередей сообщений», которая включает два типа задач: микротаски (microtasks) и события (events). При этом микротаски имеют более высокий приоритет, чем события.
Из-за того что микротаски имеют более высокий приоритет, они могут заблокировать очередь событий. Поэтому, если количество микротасков слишком велико, это может привести к замедлению отклика на внешние события, такие как касания экрана и отрисовку.
UI Runner
, GPU Runner
, IO Runner
, Platform Runner
(основной поток). В Flutter также возможно выполнять настоящие асинхронные операции между потоками с использованием isolate
или compute
.В Flutter с помощью PlatformView
можно встроить native View
в Flutter
UI. Это достигается с помощью технологии Presentation
+ VirtualDisplay
+ Surface
и так далее. Основная идея заключается в следующем:
Используется технология, аналогичная второму экрану, где VirtualDisplay
представляет собой виртуальный дисплей. Вызов метода createVirtualDisplay()
класса DisplayManager
позволяет отрендерить содержимое виртуального дисплея на контроле Surface
, затем ID этого контрола передаётся в Dart, чтобы движок мог найти соответствующую память для Surface
и отрисовать её. Короче говоря, это технология реального времени отрисовки скриншотов.
Отладочная версия Flutter использует режим JIT, а релизная версия — AOT.- В Flutter можно использовать mixin AutomaticKeepAliveClientMixin
, затем переопределяя метод wantKeepAlive
, чтобы сохранять страницу. Убедитесь, что вызываете super.build
в build
сохраняемой страницы (из-за свойства mixin
).
Основные события жестов Flutter основаны на конкурентной проверке:
Сначала hitTest
собирает все необходимые RenderObject
контроллеров от child
до parent
, создавая список, который начинается с внутреннего уровня и заканчивается внешним.
Затем выполняется цикл for
, начиная с первого child
списка, вызывая метод handleEvent
. Процесс выполнения handleEvent
не может быть прерван.
Обычно событие Down
не выигрывает; большинство побед происходит при событиях MOVE
или UP
.
Когда судейство завершено, если есть только один участник, он автоматически объявляется победителем. Если нет победителя, то первый участник в очереди объявляется победителем.
При этом также используется метод didExceedDeadline
для обработки дополнительных действий при длительном нажатии Down
. Обработка жестов обычно осуществляется через подклассы GestureRecognizer
.
Для более подробной информации см.: «Полное руководство по разработке Flutter (часть 13: полное понимание принципов взаимодействия и скольжения)»
ListView
реализуется путём изменения расстановки child
в ViewPort
.- Часто используемые системы управления состоянием включают scope_model
, flutter_redux
, fish_redux
, bloc + Stream
и другие. Подробнее см.: «Полное руководство по разработке Flutter (часть 12: полное понимание дизайна управления состояниями)»
В Flutter каналы платформы позволяют Dart-коде общаться с нативным кодом:
BasicMessageChannel
: используется для передачи строковых данных и полуструктурированных сообщений.MethodChannel
: используется для передачи вызова метода.EventChannel
: используется для передачи потока событий.
Каналы платформы не являются поточно-безопасными, для получения более подробной информации см. статью «Глубокое понимание Flutter Platform Channel» на сайте JianShu.
Основные типы данных имеют следующее соответствие:
В Android при запуске Flutter
по умолчанию в файле FlutterActivityDelegate.java
читаются метаданные (meta-data
) из файла AndroidManifest.xml
. Если значение атрибута io.flutter.app.android.SplashScreenUntilFirstFrame
равно true, то будет отображаться экран Splash (похожий на начальный экран iOS).
При запуске нативный код читает android.R.attr.windowBackground
, чтобы получить указанный Drawable
, который используется для отображения эффекта начальной заливки экрана. После этого с помощью flutterView.addFirstFrameListener
в методе onFirstFrame
заливка экрана удаляется.> Хорошо, пока всё здесь, если будут изменения или дополнения, то добавлю позже.
«Flutter полное руководство по разработке (часть 1, Dart язык и базы Flutter)»
«Flutter полное руководство по разработке (часть 2, быстрое создание приложений)»
«Flutter полное руководство по разработке (часть 3, сборка и решение проблем)»
«Flutter полное руководство по разработке (часть 4, Redux, темы, международизация)»
«Flutter полное руководство по разработке (часть 5, глубокий анализ)»
«Flutter полное руководство по разработке (часть 6, принцип работы виджетов)»
«Flutter полное руководство по разработке (часть 7, принцип работы макетов)»
«Flutter полное руководство по разработке (часть 8, полезные советы и решения проблем)»«Flutter полное руководство по разработке (часть 9, принцип работы рисования)»
«Flutter полное руководство по разработке (часть 10, процесс загрузки изображений)»
«Flutter полное руководство по разработке (часть 11, полное понимание Stream)»
«Flutter полное руководство по разработке (часть 12, полное понимание управления состоянием)»
«Рекомендация открытых проектов для кросс-платформенного программирования»
«Глубокий анализ кросс-платформенной разработки мобильных приложений»
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )