Как пятая статья в серии, эта статья рассматривает некоторые интересные принципы работы с Flutter, чтобы помочь нам лучше понять и использовать его.
Миксины ( ̄. ̄)! Да, 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();
}
}
Но зачем нужны Mixins в Flutter? Для ответа на этот вопрос нам следует обратиться к "клеящим" классам Flutter: WidgetsFlutterBinding
.
Класс WidgetsFlutterBinding вызывается при запуске метода runApp
, который служит входной точкой для приложения. В этом случае он выполняет различные инициализации и конфигурации функциональности, и здесь проявляется полезность Mixins.
Как видно из приведённых выше схем, сам класс WidgetsFlutterBinding практически пустой и состоит главным образом из наследования от BindingBase
. Далее через with
к нему "приклеиваются" различные Binding, которые также наследуются от BindingBase
.Обратите внимание, каждый Binding может использоваться отдельно или "подключаться" к WidgetsFlutterBinding. Такой подход делает структуру более понятной по сравнению с многоуровневым наследованием.
Итоговый порядок выполнения можно увидеть ниже, как и ожидалось.
Класс InheritedWidget является абстрактным и играет важную роль в Flutter, даже если вы непосредственно его не используете, скорее всего, вы уже работали с его упаковками.
Как показано на приведённой выше схеме, InheritedWidget реализует два основных метода:
Создание InheritedElement
, который представляет собой специальный тип элемента. Этот элемент добавляет себя в таблицу соответствия _inheritedWidgets
[Примечание 1], что позволяет потомкам этого элемента получать доступ к нему; а также использовать метод notifyClients
для обновления зависимостей.
Реализация метода updateShouldNotify
, который обновляет зависимости, когда метод возвращает значение true
.
Следовательно, мы можем просто понять: InheritedWidget использует InheritedElement
, чтобы обеспечить поддержку поиска снизу вверх (потому что сам он добавлен в _inheritedWidgets
), а также имеет возможность обновлять своих потомков.
Примечание 1: У каждого Element есть
_inheritedWidgets
, это HashMap<Type, InheritedElement>, который хранит отображение между InheritedWidget верхних уровней и соответствующими элементами.
Затем рассмотрим 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` 方法,具体如下所示:

Таким образом, помните ли вы упомянутое ранее **`_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, то есть кэширует асинхронно загружаемые изображения.
- До завершения загрузки и декодирования изображения невозможно точно определить, сколько памяти будет занято.
- Поэтому легко создать большое количество операций ввода-вывода, что приводит к высокому пиковой нагрузке на память.
![]()
Как показано на приведённом выше изображении, этот процесс связан с кэшированием изображений. Настоящие меры предосторожности включают:
Дополнительные детали можно найти в оригинале, причём особый акцент делается именно на ограничении количества кэшированных изображений.
Помните ли вы класс WidgetsFlutterBinding
, который содержит PaintingBinding
? Как показано на следующем рисунке, эта связь отвечает за кэширование изображений.
Внутри PaintingBinding
находится объект ImageCache
, являющийся глобальной единицей и используемым при загрузке изображений в ImageProvider
. Таким образом, размер кэша изображений можно установить следующим образом:
// Количество кэшированных изображений — 100
PaintingBinding.instance.imageCache.maximumSize = 100;
// Размер кэша — 50 мегабайт
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20;
В статье Понимание 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).Необходимый функционал.
xcode_backend.sh
) в BuildPhase
для генерации и внедрения App.framework и Flutter.framework в iOS.Компиляционные бинарные файлы Android находятся в директории data/data/имя_пакета/app_flutter/flutter_assets/
. Люди, знакомые с Android, знают, что этот путь легко обновляется, поэтому вы понимаете, о чём идёт речь  ̄ω ̄=.
⚠️ Обратите внимание, начиная с версии 1.7.8, Android-версия Flutter уже компилируется в чистые .so файлы.
IOS? По моему мнению, кажется, что динамическая библиотека framework и т.д. не могут использоваться для горячего запуска, если вы не хотите пройти проверку!
Таким образом, пятая часть была завершена! (///▽///)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )