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

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

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

Как пятая статья в серии, эта статья рассматривает некоторые интересные принципы работы с Flutter, чтобы помочь нам лучше понять и использовать его.

Ссылка на полную серию статей:

Полная серия статей по Flutter

Специальные статьи о мире Flutter

1. Mixins

Миксины ( ̄. ̄)! Да, Flutter использует миксины, которые поддерживаются Dart. Миксины могут более эффективно решать проблемы, связанные с множественным наследованием, такие как: непредсказуемые порядки методов, конфликты параметров, усложденная структура классов и т.д.

Определение миксинов может быть сложным, поэтому давайте рассмотрим это через пример кода. В следующем коде видно, что в Dart with используется для миксинов. Из этого можно заключить, что если есть несколько миксинов, то методы одного миксина могут перезаписывать методы другого миксина, причём последний миксин в списке будет иметь приоритет. Например, в классе G, который расширяет B и использует миксины A и A2, вызов методов a, b и c выводит строки A2.a(), A.b() и B.c() соответственно.

class A {
  void a() {
    print("A.a()");
  }

  void b() {
    print("A.b()");
  }
}

class A2 {
  void a() {
    print("A2.a()");
  }
}

class B {
  void a() {
    print("B.a()");
  }

  void b() {
    print("B.b()");
  }

  void c() {
    print("B.c()");
  }
}

class G extends B with A, A2 {}

void testMixins() {
  var g = G();
  g.a();
  g.b();
  g.c();
}
// *************************Выход*************************
// I/flutter (13627): A2.a()
// I/flutter (13627): A.b()
// I/flutter (13627): B.c()
```Далее мы продолжаем модифицировать этот код. В следующем примере мы определяем абстрактный класс `Base`, от которого наследуются все остальные классы (`A`, `A2`, `B`). Мы также выполняем операцию `super()` после каждого `print`.

Из последнего вывода видно, что **все методы всех классов были выполнены один раз**, а порядок выполнения зависит от порядка миксинов в `with`. Если вы удалите `super.a();` в методе `a()` класса `A`, то вы не увидите вывод `B.a()` и `base a()`.

```dart
abstract class Base {
  void a() {
    print("base a()");
  }

  void b() {
    print("base b()");
  }

  void c() {
    print("base c()");
  }
}

class A extends Base {
  @override
  void a() {
    print("A.a()");
    super.a();
  }

  @override
  void b() {
    print("A.b()");
    super.b();
  }
}

class A2 extends Base {
  @override
  void a() {
    print("A2.a()");
    super.a();
  }
}

2. WidgetsFlutterBinding

Но зачем нужны Mixins в Flutter? Для ответа на этот вопрос нам следует обратиться к "клеящим" классам Flutter: WidgetsFlutterBinding.

Класс WidgetsFlutterBinding вызывается при запуске метода runApp, который служит входной точкой для приложения. В этом случае он выполняет различные инициализации и конфигурации функциональности, и здесь проявляется полезность Mixins.

Как видно из приведённых выше схем, сам класс WidgetsFlutterBinding практически пустой и состоит главным образом из наследования от BindingBase. Далее через with к нему "приклеиваются" различные Binding, которые также наследуются от BindingBase.Обратите внимание, каждый Binding может использоваться отдельно или "подключаться" к WidgetsFlutterBinding. Такой подход делает структуру более понятной по сравнению с многоуровневым наследованием.

Итоговый порядок выполнения можно увидеть ниже, как и ожидалось.

2. InheritedWidget

Класс InheritedWidget является абстрактным и играет важную роль в Flutter, даже если вы непосредственно его не используете, скорее всего, вы уже работали с его упаковками.

Как показано на приведённой выше схеме, InheritedWidget реализует два основных метода:

  • Создание InheritedElement, который представляет собой специальный тип элемента. Этот элемент добавляет себя в таблицу соответствия _inheritedWidgets [Примечание 1], что позволяет потомкам этого элемента получать доступ к нему; а также использовать метод notifyClients для обновления зависимостей.

  • Реализация метода updateShouldNotify, который обновляет зависимости, когда метод возвращает значение true.

Следовательно, мы можем просто понять: InheritedWidget использует InheritedElement, чтобы обеспечить поддержку поиска снизу вверх (потому что сам он добавлен в _inheritedWidgets), а также имеет возможность обновлять своих потомков.

Примечание 1: У каждого Element есть _inheritedWidgets, это HashMap<Type, InheritedElement>, который хранит отображение между InheritedWidget верхних уровней и соответствующими элементами.image

Затем рассмотрим BuildContext, как показано на приведённой выше:

BuildContext фактически является интерфейсом, а Element реализует его. InheritedElement является подклассом Element, поэтому каждый экземпляр InheritedElement является экземпляром BuildContext. В то же время все передаваемые нами BuildContext также являются экземплярами Element.

Поэтому когда нам требуется разделить состояние, если передавать состояние по уровням будет слишком сложно, то после того, как мы узнали о InheritedWidget, можно ли положить всё нужное для совместного использования состояние в одном InheritedWidget и затем использовать его прямо в используемых виджетах? Ответ — да!

Итак, вот пример такого кода: обычно такие вещи, как фокус, тема, многоязычность, информация пользователя и т. д., относятся к глобальным данным приложения, они получают доступ через BuildContext (InheritedElement).

/// Скрыть клавиатуру
FocusScope.of(context).requestFocus(new FocusNode());

/// Тема
Theme.of(context).primaryColor;

/// Многоязычность
Localizations.of(context, GSYLocalizations);

/// Получение информации пользователя через Redux
StoreProvider.of(context).userInfo;

В заключение, мы начнём с Theme.

Как показано ниже, установка темы данных для MaterialApp позволяет получить эти данные через Theme.of(context) и использовать их. Когда тема данных MaterialApp меняется, цвет соответствующего виджета тоже изменяется. Почему так происходит?


Перевод выполнен согласно указанным правилам, сохранены структура и форматирование исходного текста.```dart /// Добавление темы new MaterialApp( theme: ThemeData.dark(), ); /// Использование темы new Container(color: Theme.of(context).primaryColor),


Просматривая исходный код слой за слоем, можно заметить следующую вложенность: `MaterialApp -> AnimatedTheme -> Theme -> _InheritedTheme extends InheritedWidget`. Таким образом, используя `MaterialApp` как вход, мы фактически работаем внутри **InheritedWidget**. Как показано на приведённой выше图为所示,通过 `Theme.of(context)` 获取到的主题数据,实际上是通过 `context.inheritFromWidgetOfExactType(_InheritedTheme)` 获取的,而在 **Элементе**(Element)中实现了 **BuildContext** 的 `inheritFromWidgetOfExactType` 方法,具体如下所示:

![插图](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image7)

Таким образом, помните ли вы упомянутое ранее **`_inheritedWidgets`**? Поскольку `InheritedElement` уже существует в `_inheritedWidgets`, его можно использовать напрямую.

> **Примечание:** Внутри `InheritedWidget` находится `InheritedElement`, который представляет собой специальный элемент, увеличивающийся за счет добавления себя в карту отношений `_inheritedWidgets`.

Наконец, как показано на следующей图为所示,在 **InheritedElement** 中,`notifyClients` 通过 `InheritedWidget` 的 `updateShouldNotify` 方法来判断是否需要更新,例如在 **Theme** 的 `_InheritedTheme` 中是这样的:

```dart
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;

插图

По сути, ядром всех этих компонентов — Theme, Redux, Scope Model и Localizations — является InheritedWidget.

Три: память

Недавно командой технологий Xianyu был опубликован «Flutter-цветок: оптимизация памяти», где подробно рассматриваются вопросы памяти в Flutter. Одним из очень интересных явлений является то:

  • В Flutter ImageCache хранит объекты типа ImageStream, то есть кэширует асинхронно загружаемые изображения.
  • До завершения загрузки и декодирования изображения невозможно точно определить, сколько памяти будет занято.
  • Поэтому легко создать большое количество операций ввода-вывода, что приводит к высокому пиковой нагрузке на память.Изображение предоставлено Xianyu Technology

Как показано на приведённом выше изображении, этот процесс связан с кэшированием изображений. Настоящие меры предосторожности включают:

  • Предотвращение отправки лишних запросов на изображения, когда страница невидима;
  • Ограничение количества кэшированных изображений;
  • Выполнение очистки в подходящее время.

Дополнительные детали можно найти в оригинале, причём особый акцент делается именно на ограничении количества кэшированных изображений.

Помните ли вы класс WidgetsFlutterBinding, который содержит PaintingBinding? Как показано на следующем рисунке, эта связь отвечает за кэширование изображений.

Рисунок

Внутри PaintingBinding находится объект ImageCache, являющийся глобальной единицей и используемым при загрузке изображений в ImageProvider. Таким образом, размер кэша изображений можно установить следующим образом:

// Количество кэшированных изображений — 100
PaintingBinding.instance.imageCache.maximumSize = 100;
// Размер кэша — 50 мегабайт
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20;

4. Потоки

В статье Понимание Flutter Platform Channel от команды Xianyu Technology говорится о четырёх основных потоках в Flutter: Platform Task Runner, UI Task Runner, GPU Task Runner и IO Task Runner.

Среди них Platform Task Runner представляет собой главный поток Android и iOS, а UI Task Runner — это поток UI Flutter.

На следующей схеме показано, как через Platform Channel осуществляется взаимодействие между Dart и нативной стороной. В данном контексте важно отметить следующее:

  • Поскольку Platform Task Runner является главным потоком нативной системы, следует избегать выполнения длительных операций в этом потоке.* Поскольку Platform Channel не является поточно-безопасным, необходимо гарантировать, что обратные вызовы выполняются в потоке платформы (то есть главном потоке Android и iOS).

5. Горячий запуск

Необходимый функционал.

    1. Мы знаем, что Flutter всё ещё является проектом для iOS/Android.
    1. Flutter использует добавление шелл-скрипта (xcode_backend.sh) в BuildPhase для генерации и внедрения App.framework и Flutter.framework в iOS.
    1. Flutter использует Gradle для ссылки на flutter.jar и добавляет компилированные бинарные файлы в Android.

Компиляционные бинарные файлы Android находятся в директории data/data/имя_пакета/app_flutter/flutter_assets/. Люди, знакомые с Android, знают, что этот путь легко обновляется, поэтому вы понимаете, о чём идёт речь  ̄ω ̄=.

⚠️ Обратите внимание, начиная с версии 1.7.8, Android-версия Flutter уже компилируется в чистые .so файлы.

IOS? По моему мнению, кажется, что динамическая библиотека framework и т.д. не могут использоваться для горячего запуска, если вы не хотите пройти проверку!

Таким образом, пятая часть была завершена! (///▽///)

Рекомендации по материалам

Рекомендации по полноконтурным открытым проектам:* GSYGithubAppWeex

Снова увидимся?

Опубликовать ( 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