1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-msjj.md 32 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

После Google I/O конференции ко мне обратились многие с вопросами о Flutter, среди которых было много связанных с собеседованиями. Сейчас некоторые вакансии начинают включать требования к Flutter, поэтому решил сделать небольшую подборку знаний.

⚠️ Полностью пройденное обучение обязательно, здесь можно лишь подвести некоторые итоги. Для более подробной информации рекомендую посетить официальные сайты Dart и Flutter.

Эта статья представляет собой подборку ключевых моментов. В случае возникновения вопросов вы можете кликнуть по ссылкам на соответствующие статьи или посмотреть мою колонку на Juejin.

Часть Dart

Если вы уже знакомы с языками программирования, такими как JavaScript или Java/Kotlin, то вам будет легко освоить Dart. Dart объединяет черты динамических и статических языков, но здесь мы сосредоточимся на некоторых уникальных концепциях.

    1. Dart является строго типизированным языком, однако можно использовать var для объявления переменных, при этом Dart автоматически определяет тип данных. dynamic указывает на динамический тип, который после компиляции становится объектом типа Object. Во время компиляции нет проверки типов, а проверка происходит во время выполнения программы.
    1. В Dart условные операторы (if, else) поддерживают только значения типа bool, а switch поддерживает значения типа String.- 3. В Dart массивы и List являются эквивалентными.
    1. В Dart Runes представляют символы Unicode, это строки, закодированные в UTF-32, используемые для представления символов, таких как Runes input = new Runes('\u{1f596} \u{1f44d}').
    1. Dart поддерживает замыкания.
    1. В Dart числовой тип данных делится на два подтипа: int и double, но отсутствует тип float.
    1. В Dart оператор цепочки позволяет удобно настраивать логику, как показано ниже:
event
  ..id = 1
  ..type = ""
  ..actor = "";
    1. Операторы присваивания

Интересные операторы присваивания включают следующее:

AA ?? "999"   // Если AA пустое, верните "999"
AA ??= "999"  // Если AA пустое, установите его значение равным "999"
AA ~/ 999     // AA разделить на 999 целочисленно
    1. Дополнительные параметры метода

Методы Dart могут иметь параметры со значением по умолчанию и именованные параметры.

Например, метод getDetail(String userName, reposName, {branch = "master"}) {} имеет параметр branch, который по умолчанию установлен как "master". При вызове метода можно явно указывать имя параметра: getRepositoryDetailDao("aaa", "bbbb", branch: "dev").

    1. Области видимости

В Dart нет ключевых слов public, private и других модификаторов доступа. Вместо этого _ перед названием метода или свойства указывает на то, что это приватное свойство или метод. Также есть аннотация @protected.

    1. Конструкторы

Множественные конструкторы в Dart могут быть реализованы с помощью именованных конструкторов.По умолчанию может быть только один конструктор, но можно создать пустой конструктор через вызов, например Model.empty(). Вы можете использовать любое имя для такого конструктора. При этом значения аргументов должны быть указаны через this.name:

class ModelA {
  String name;
  String tag;

  ModelA(this.name, this.tag);

  ModelA.empty() {}

  ModelA.forName(this.name) {}
}
    1. Переопределение геттеров и сеттеров

Все базовые типы данных и классы в Dart наследуются от класса Object, который имеет значение по умолчанию null. Каждый объект имеет свои геттеры и сеттеры. Если же поле объявлено как final или const, то у него будет только геттер.

Переопределять геттеры и сеттеры можно следующим образом:

  @override
  Size get preferredSize {
    return Size.fromHeight(kTabHeight + indicatorWeight);
  }
    1. Assert (утверждение)

Ключевое слово assert работает только в режиме проверки кода. Например, assert(unicorn == null); выполнится только если условие истинно; в противном случае будет выброшено исключение. Это используется для проверки состояния приложения во время разработки.

    1. Переопределение операторов

При переопределении операторов, таких как + и -, они будут работать со всеми экземплярами данного класса:

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

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.

Для более подробной информации посмотрите здесь:

генераторы

code_generator.dart

Flutter полное руководство по разработке (XI. Полное понимание потока)

Stream

Stream также является другим способом использования и упаковки Zone.В Dart существует ещё один способ определения асинхронных операций — это использование async* / yield или Stream. На самом деле, async* / yield являются просто синтаксическим сахаром, который компилятор преобразует в Stream. Stream также поддерживает синхронные операции.

  1. Stream состоит из четырёх ключевых объектов: Stream, StreamController, StreamSink и StreamSubscription. Можно сделать следующий вывод:- StreamController: Как следует из названия класса, используется для управления всем процессом Stream, предоставляя различные интерфейсы для создания различных потоков событий.
  • StreamSink: Обычно используется как вход для событий, предоставляя методы, такие как add и addStream.

  • Stream: Сам источник событий, который обычно используется для прослушивания событий или их преобразования, таких как listen и where.

  • StreamSubscription: Объект после подписки на события, используемый для управления подписками и другими операциями, такими как cancel и pause. Внутри он также является ключевым звеном в передаче событий.

  1. Stream создаётся с помощью StreamController; события добавляются через StreamSink; события прослушиваются через Stream; управление подписками осуществляется через StreamSubscription.

  2. Stream поддерживает различные изменения, такие как map, expand, where, take и другие операции, а также позволяет преобразовать его в Future.

Дополнительно можно посмотреть: "Flutter полное руководство разработки (XI. Полное понимание Stream)".

Часть Flutter

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 выше.

  • По умолчанию в Flutter основной способ проверки необходимости обновления — это сравнение 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) изменяется.

image4


  • С помощью StreamBuilder и FutureBuilder можно быстро использовать потоки (Stream) и будущие значения (Future) для быстрого создания асинхронных компонентов: «Flutter полное руководство по разработке (XI. Полное понимание Stream)».

  • В Flutter начальная точка входа для запуска приложения через runApp является WidgetsFlutterBinding, который состоит из нескольких подклассов BindingBase: GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding путём использования миксинов.- В Flutter Dart использует модель «событийного цикла и очередей сообщений», которая включает два типа задач: микротаски (microtasks) и события (events). При этом микротаски имеют более высокий приоритет, чем события.

Из-за того что микротаски имеют более высокий приоритет, они могут заблокировать очередь событий. Поэтому, если количество микротасков слишком велико, это может привести к замедлению отклика на внешние события, такие как касания экрана и отрисовку.

  • В Flutter существует четыре основных потока выполнения: UI Runner, GPU Runner, IO Runner, Platform Runner (основной поток). В Flutter также возможно выполнять настоящие асинхронные операции между потоками с использованием isolate или compute.

PlatformView

В 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: полное понимание принципов взаимодействия и скольжения)»

Каналы платформы

В Flutter каналы платформы позволяют Dart-коде общаться с нативным кодом:

  • BasicMessageChannel: используется для передачи строковых данных и полуструктурированных сообщений.
  • MethodChannel: используется для передачи вызова метода.
  • EventChannel: используется для передачи потока событий.

Каналы платформы не являются поточно-безопасными, для получения более подробной информации см. статью «Глубокое понимание Flutter Platform Channel» на сайте JianShu.

Основные типы данных имеют следующее соответствие:

Активность запуска Android

В 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, полное понимание управления состоянием)»

«Полное руководство по разработке приложений с использованием Flutter (часть 13, подробное исследование принципов взаимодействия с экраном и прокруткой)»

«Рекомендация открытых проектов для кросс-платформенного программирования»

«Глубокий анализ кросс-платформенной разработки мобильных приложений»

Снова встретимся?

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master