Из-за прихода новых сотрудников в компании недавно, ранее мало работавших с кросс-платформенной разработкой мобильных приложений, было решено подготовить базовое руководство по Flutter для их знакомства. В конце будет представлен интересный пример.
Перед тем как углубиться в Flutter, стоит отметить один важный момент — большинство кросс-платформенных фреймворков мобильной разработки являются одностраничными приложениями.
Что такое одностраничное приложение? Это значит, что для нативных Android и iOS, вся кросс-платформенная пользовательская интерфейсная система обычно работает внутри одного Activity
/ ViewController
, по умолчанию существует всего один Activity
/ ViewController
. Такие фреймворки, как Flutter, React Native, Weex и Ionic, также работают таким образом. Поэтому маршруты фреймворка обычно не связаны напрямую с маршрутами нативных систем.
Например, рассмотрим следующий пример:
FlutterA
и FlutterB
;Activity
/ ViewController
, запускается нативная страница X, которая затем добавляется в стек маршрутов нативной системы, закрывая FlutterActivity
/ FlutterViewController
, то есть скрывая FlutterA
и FlutterB
;FlutterC
, она тоже будет скрыта за счет нативной страницы X;Из этого можно сделать вывод, что по умолчанию кросс-платформенные приложения являются одностраничными, и их стек маршрутов несовместим с маршрутом нативной системы.
Конечно, слово "по умолчанию" используется несколько раз, так как действительно возможно создание пользовательских смешанных стеков маршрутов, таких как официальный
FlutterEngineGroup
, а также сторонние фреймворкиflutter_boost
,mix_stack
,flutter_thrio
и т.д.
После того как мы рассмотрели различия между одностраничными приложениями, давайте поговорим о различиях в логике рендера Flutter.
На уровне рендера Flutter значительно отличается от других кросс-платформенных фреймворков. Ниже приведена сравнительная диаграмма современных моделей рендера: - Для нативной Android системы процесс рендеринга состоит из нативного кода, прошедшего через Skia, до оконечной отрисовки на GPU, так как сама система Android включает в себя библиотеку Skia;
Skia в Android может использовать либо
OpenGL
, либоVulkan
в зависимости от ситуации, а в iOS — если есть поддержкаMetal
, то используетсяMetal
для ускоренного рендера.Из вышеописанного можно сделать вывод:
ReactNative/Weex
такие кросс-платформенные технологии имеют значительную связь с нативными платформами:
Например: стили, отлаженные на iOS, могут работать некорректно на Android; стили, работающие на Android, могут не поддерживаться на iOS; эффекты компонентов на iOS могут отличаться от аналогичных на Android, например, при реализации плавающего меню или кнопки "Обновления".
Flutter
отличается тем, что использует Skia и GPU для рендера, создавая платформенно-независимые компоненты как на Android, так и на iOS. Другими словами, большинство widgets
в Flutter не связано ни с Android, ни с iOS.
Основная идея заключается в том, что нативные платформы предоставляют поверхность для рендера, а Flutter берёт на себя задачу создания соответствующих компонентов.
Обычно
FlutterView
используется для рендера, который внутри Android может быть представлен черезSurfaceView
,TextureView
илиFlutterImageView
; в iOS этоUIView
, использующийlayer
для рендера.Поэтому компоненты Flutter демонстрируют единое поведение на различных платформах, но объединение их с нативными компонентами требует значительных усилий. Flutter предлагает механизмPlatformView
для интеграции с нативными компонентами, однако сам этот механизм может вызывать проблемы с использованием памяти и клавиатурой, что увеличивает затраты на интеграцию.
Как показано на рисунке выше, по умолчанию структура проекта Flutter выглядит следующим образом:
android
каталог проекта для native Android, где можно конфигурировать имя приложения (appName
), логотип (logo
), splash screen, AndroidManifest
, и т.д.;ios
каталог проекта для iOS, где можно конфигурировать splash screen, логотип (logo
), название приложения, файл Info.plist
, и т.д.;build
каталог, который создается после компиляции, обычно игнорируется в .gitignore
. Процесс сборки и выходные данные находятся здесь, а также вывод процесса сборки для native Android;lib
каталог, используемый для написания кода на Dart, основной файл — это main.dart
;pubspec.yaml
файл, один из ключевых файлов Flutter-проекта, где указываются ссылки на статические ресурсы (картинки, шрифты), зависимости от сторонних библиотек и версия Dart. Ниже приведена структура файла pubspec.yaml
.> Важно отметить, что при изменении этого файла необходимо выполнить команду
flutter pub get
, а также остановить приложение и запустить проект заново, вместо использования hotload
.
Ниже представлен пример плагина Flutter. В Flutter различаются два типа проектов:
Package
относится к пакетам Flutter и не включает в себя native код;Plugin
относится к плагинам Flutter и включает в себя код для Android и iOS.Перед запуском Flutter необходимо выполнить команду flutter pub get
для синхронизации и загрузки сторонних зависимостей. Сторонние зависимости обычно находятся в директории (Mac
) /Users/ваш_пользователь/.pub-cache
.
После успешной загрузки зависимостей можно запустить проект Flutter через команду flutter run
или нажав кнопку запуска в среде разработки (IDE). Этот процесс требует синхронизации некоторых данных на уровне native проекта, таких как:
pod install
;Если вы хотите следить за прогрессом синхронизации:
./gradlew assembleDebug
в директории android/
;pod install
в директории ios/
;Если ваш проект использует плагины, содержащие native логику, то в корневой директории проекта будут находиться файлы .flutter_plugins
и .flutter-plugins-dependencies
. Эти файлы игнорируются системой контроля версий Git, но используются Android и iOS для ссылок на локальные пути плагинов. При запуске Flutter эти пути используются для динамической загрузки зависимостей.По умолчанию Flutter работает в режиме JIT во время отладки, поэтому производительность ниже, но позволяет использовать горячую перезагрузку
.
В режиме выпуска Flutter работает в режиме AOT, что значительно повышает скорость работы. Кроме того, Flutter по умолчанию использует CPU для эмуляций и GPU для реальных устройств, что влияет на производительность.
Также стоит отметить, что на реальном устройстве iOS 14 запуск приложения в режиме отладки после разрыва соединения невозможен.
Если в проекте возникают проблемы с кэшированием, можно очистить кэш, выполнив команду flutter clean
. Почему Flutter не поддерживает горячую перезагрузку?
Ранее мы говорили о том, что React Native и Weex используют JavaScript для преобразования компонентов в нативные контролы, поэтому фактически JavaScript является просто текстом, который можно отправлять с помощью code-push
, что не противоречит требованиям платформ.
А вот Flutter после сборки генерирует двоичные файлы, а отправка таких файлов явно нарушает требования платформ.
При сборке для Android создаются два динамических библиотеки —
app.so
иflutter.so
; при сборке для iOS создаются два файла —App.framework
иFlutter.framework
.
Widgets
) и управление состоянием.### Реактивное программированиеРеактивное программирование также известно как декларативное программирование, это современный подход к фронтенд-разработке, который также становится популярным в мобильной разработке, например, Jetpack Compose
и SwiftUI
.
Jetpack Compose и SwiftUI имеют много общего на поверхности.
Основная идея реактивного программирования заключается в том, чтобы не выполнять обновление интерфейса вручную, а объявлять его через код, связывая данные и представление таким образом, что при изменении данных интерфейс автоматически обновится.
На уровне кода нет XML-разметки или storyboard, вся разметка создаётся полностью через код, то есть "что видишь, то и получаешь". Также нет необходимости работать с объектами интерфейса для присваивания значений или обновления; достаточно настроить связи между данными и представлением.
Основное отличие реактивного программирования от связывания данных или MVVM состоит в том, что каждый раз происходит полное перестроение дерева отрисовки, а не просто управление видимостью UI.
Widget
— это базовая концепция в Flutter, с которой вы будете взаимодействовать напрямую при написании кода. **Все в Flutter — это Widgets, они являются неизменяемыми (immutable) и каждое состояние Widget представляет собой кадр.**Поэтому Widget
как неизменяемый объект не может быть настоящим объектом UI. Настоящими объектами уровня View в Flutter являются Element
и RenderObject
, где абстракция Element
представляет собой часто используемый BuildContext
.
Пример:
// Пример использования одного Text в трех местах
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Hello World'),
Text('Hello World'),
Text('Hello World')
],
);
}
}
Если бы Text
был реальным объектом View, он не смог бы использоваться одновременно в нескольких местах внутри одного экрана.
Поэтому в Flutter Widget
больше играет роль конфигурационного файла, который используется для описания конфигурации интерфейса. Конкретные реализации логики, взаимосвязи и классификации этих компонентов можно найти в третьей и четвертой главах моей книги «Flutter Разработка На Практике».
Как реактивная платформа, Flutter в принципе не стремится к какому-либо конкретному паттерну проектирования, такому как MVC, MVP или MVVVM. Вместо этого он сосредоточен на управлении состоянием интерфейса.
Это значит отказ от подхода, когда требуется получить объект
View
, чтобы затем выполнять настройку его внешнего вида.
В Flutter важнее всего управлять потоками данных, например:- Откуда берутся данные и куда они направляются;
Интерфейсу достаточно просто реагировать на изменения данных, поэтому в Flutter существует множество фреймворков для управления и совместного использования данных, такие как provider
, getx
, flutter_redex
, flutter_mobx
и другие.
Наконец, хочу затронуть один интересный вопрос, который часто возникает — передаются ли значения или ссылки в Flutter? Этот вопрос вызывает много путаницы в интернете, но на самом деле всё довольно просто:
В Flutter все являются объектами, даже типы данных int
, double
, bool
. Вы думаете, что передаются при работе с объектами?
Однако есть различия в работе с этими объектами. Например, операции над числами типа int
и double
, такие как +
, -
, *
, /
, выполняют методы класса operator
, а затем возвращают новый объект типа num
.
Для понимания того, что происходит внутри этих операций, достаточно посмотреть на то, что делает объект Double
при выполнении операций сложения, вычитания, умножения и деления. Вот пример такого поведения:
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )