Принцип Flutter и практика Meituan

Android Flutter APK Dart

Flutter — это новый набор кроссплатформенных фреймворков пользовательского интерфейса с открытым исходным кодом, разработанный Google, который поддерживает разработку систем iOS и Android и в будущем станет комплектом разработки по умолчанию для новой операционной системы Fuchsia. Издается с мая 2017 г.первая версияС тех пор Flutter выпустил около 60 версий, а первая вышла в мае 2018 года.«Готовые приложения для производства»Версия Beta 3, первая из которых вышла 20 июня."Предварительный релиз"Версия.

Первое знакомство с Flutter

Цель Flutter — запустить один и тот же набор кода на системах Android и iOS с производительностью, сравнимой с нативными приложениями, Flutter даже предоставляет два набора элементов управления для адаптации к Android и iOS (эффекты прокрутки, шрифты и значки элементов управления и т. д.). ) Чтобы сделать приложение более похожим на нативное в деталях.

До появления Flutter уже существовало множество кроссплатформенных решений для пользовательского интерфейса, таких как Cordova, AppCan и т. д., основанных на WebView, а также React Native, Weex и т. д., которые использовали HTML+JavaScript для отображения собственных элементов управления.

Преимущества фреймворков на базе WebView очевидны, они могут практически полностью унаследовать все достижения современной веб-разработки (гораздо более богатые библиотеки управления, фреймворки страниц, отвечающие различным потребностям, полная динамика, средства автоматизированного тестирования и т.д.), конечно, включая Веб-разработчики могут разрабатывать приложения без особых затрат на обучение и миграцию. В то же время у фреймворка WebView есть и фатальный недостаток (в случае более высоких требований к опыту и производительности), то есть эффективность рендеринга WebView и производительность выполнения JavaScript слишком низки. В сочетании с настройкой различных версий системы Android и производителей устройств трудно гарантировать единообразие работы на всех устройствах.

Чтобы решить проблему низкой производительности WebView, класс фреймворков, представленный React Native, передал системе окончательную работу по рендерингу.Хотя также используется логика построения пользовательского интерфейса, такая как HTML + JS, соответствующие пользовательские элементы управления создан, чтобы в полной мере использовать более высокую эффективность рисования собственных элементов управления по сравнению с WebView. В то же время эта стратегия также привязывает сам фреймворк и разработчиков приложений к системе управления системой, не только самому фреймворку приходится иметь дело с большим количеством логики, связанной с платформой, но и с изменениями в версиях системы и API, разработчикам также может потребоваться иметь дело с различиями между различными платформами, и даже некоторые функции могут быть реализованы только на некоторых платформах, поэтому кроссплатформенные функции фреймворка будут значительно сокращены.

Flutter открыл новый способ мышления, полностью переписав набор кроссплатформенных UI-фреймворков, включая элементы управления UI, логику рендеринга и даже языки разработки. Механизм рендеринга полагается на кросс-платформенную графическую библиотеку Skia для реализации, и только интерфейс, связанный с рисованием графики, зависит от системы, что может в наибольшей степени обеспечить согласованность опыта на разных платформах и устройствах.Логическая обработка использует Язык Dart, поддерживающий AOT, и эффективность выполнения также высока, намного выше, чем у JavaScript.

Flutter поддерживает операционные системы Windows, Linux и macOS в качестве сред разработки и обеспечивает полнофункциональную поддержку как IDE, так и Android Studio и VS Code. Язык Dart, используемый Flutter, поддерживает как режимы работы AOT, так и JIT, а также популярный инструмент разработки «Горячая перезагрузка» в режиме JIT, то есть после редактирования кода Dart в Android Studio нужно только нажать «Сохранить» или «Горячая перезагрузка». Кнопка «Перезагрузить», вы можете сразу обновиться до работающего устройства, не нужно перекомпилировать приложение или даже перезапускать приложение, вы сразу увидите обновленный стиль.

Во Flutter все функции могут быть реализованы путем объединения нескольких виджетов, включая выравнивание, расположение по строкам, расположение по столбцам, расположение сетки и даже обработку событий. Элементы управления Flutter в основном делятся на две категории: StatelessWidget и StatefulWidget.StatelessWidget используется для отображения статического текста или изображений.Если элемент управления необходимо изменить в соответствии с внешними данными или действиями пользователя, необходимо использовать StatefulWidget. Концепция State также заимствована из популярной веб-инфраструктуры Facebook.React, фреймворк в стиле React использует дерево элементов управления и их соответствующие состояния для построения интерфейса.Когда состояние элемента управления изменяется, фреймворк отвечает за сравнение разницы состояний до и после и минимальную стоимость обновления результата рендеринга.

Hot Reload

Измените строку «Hello, World» в файле кода Dart, добавьте восклицательный знак и нажмите кнопку «Сохранить» или «горячее обновление», чтобы обновить интерфейс немедленно, всего за несколько сотен миллисекунд:

Flutter достигает волшебного эффекта горячей перезагрузки, внедряя новый код в работающую DartVM.После того как DartVM обновит структуру классов в программе, Flutter немедленно перестроит все дерево управления для обновления интерфейса. Но горячее обновление также имеет некоторые ограничения: не все изменения кода могут быть обновлены с помощью горячего обновления:

  1. Ошибка компиляции.Если модифицированный код Dart не может быть скомпилирован, Flutter сообщит об ошибке в консоли, и соответствующий код необходимо изменить.
  2. тип управления изStatelessWidgetприбытьStatefulWidgetпреобразование, потому что Flutter сохранит исходное состояние программы при выполнении горячего обновления, а элемент управления из stageless→stateful заставит Flutter воссоздать элемент управления и сообщить об ошибке «myWidget не является подтипом StatelessWidget», а из stateful→ stateless будет Сообщается об ошибке "тип myWidget не является подтипом типа StatefulWidget" из "newWidget".
  3. Глобальные переменные и статические переменные-члены, которые не обновляются при горячем обновлении.
  4. Корневой узел управления, созданный в основной функции, изменен.После горячего обновления Flutter будет воссоздавать дерево управления только на основе исходного корневого узла и не будет изменять корневой узел.
  5. Класс преобразуется из обычного типа в тип перечисления или изменяется список универсальных параметров типа, что приводит к сбою горячего обновления.

Когда горячее обновление не может быть обновлено, вы можете выполнить горячий перезапуск (Hot Restart), чтобы полностью обновить все коды.Также нет необходимости перезапускать приложение.Разница в том, что перезагрузка упакует и синхронизирует все коды Dart с устройство, и все состояния будут сброшены.

Плагин флаттера

Язык Dart, используемый Flutter, не может напрямую вызывать интерфейс Java, предоставляемый системой Android, в этом случае для реализации передачи требуется подключаемый модуль. Flutter официально предоставляет богатую встроенную инкапсуляцию интерфейса:

  • android_alarm_manager, чтобы получить доступ к системе AndroidAlertManager.
  • android_intent, создает объект Android Intent.
  • battery, чтобы получать и отслеживать изменения питания системы.
  • connectivity, получать и контролировать состояние сетевого подключения системы.
  • device infoдля получения такой информации, как модель устройства.
  • image_picker, выберите на своем устройстве или сделайте снимок.
  • package_infoполучить версию установочного пакета Приложения и другую информацию.
  • path_provider, чтобы получить общий путь к файлу.
  • quick_actions, значок приложения добавить ярлык, iOSeponymous conceptи AndroidApp Shortcuts.
  • sensors, чтобы получить доступ к датчикам акселерометра и гироскопа устройства.
  • shared_preferences, Функция хранения App KV.
  • url_launcher, URL-адрес запуска, включая такие функции, как звонки, текстовые сообщения и просмотр веб-страниц.
  • video_player, элементы управления для воспроизведения видеофайлов или сетевых потоков.

Во Flutter пакеты зависимостей определяютсяPubУправление складом, конфигурация зависимостей проекта может быть объявлена ​​в файле pubspec.yaml (аналогично объявлению версии NPM).Pub Versioning Philosophy), для плагинов, не опубликованных в репозитории Pub, вы можете использовать адрес репозитория git или путь к файлу:

dependencies: 
  url_launcher: ">=0.1.2 <0.2.0"
  collection: "^0.1.2"
  plugin1: 
    git: 
      url: "git://github.com/flutter/plugin1.git"
  plugin2: 
    path: ../plugin2/

Возьмем в качестве примера shared_preferences, добавим код в pubspec:

dependencies:
  flutter:
    sdk: flutter

  shared_preferences: "^0.4.1"

Версии, начинающиеся со знака вставки "^", указываютСовместимость с текущей версией интерфейсапоследняя версия,^1.2.3равно>=1.2.3 <2.0.0и^0.1.2равно>=0.1.2 <0.2.0, после добавления зависимостей нажмите кнопку «Получить пакеты», чтобы загрузить подключаемый модуль локально, и добавьте оператор импорта в код, чтобы использовать интерфейс, предоставляемый подключаемым модулем:

import 'package:shared_preferences/shared_preferences.Dart';

class _MyAppState extends State<MyAppCounter> {
  int _count = 0;
  static const String COUNTER_KEY = 'counter';

  _MyAppState() {
    init();
  }

  init() async {
    var pref = await SharedPreferences.getInstance();
    _count = pref.getInt(COUNTER_KEY) ?? 0;
    setState(() {});
  }

  increaseCounter() async {
    SharedPreferences pref = await SharedPreferences.getInstance();
    pref.setInt(COUNTER_KEY, ++_count);
    setState(() {});
  }
...

Dart

DartЭто строго типизированный кроссплатформенный язык разработки на стороне клиента. Он обладает превосходными функциями, такими как оптимизация для клиента, высокая производительность, высокая эффективность, переносимость (совместимость с ARM/x86), простой в освоении стиль объектно-ориентированного программирования и встроенная поддержка реактивного программирования (Stream & Future). Dart в основном разрабатывается и поддерживается Google.2011 10 стартовых проектов, первая версия 2.0-dev была выпущена в сентябре 2017 года.

Сам Dart предусматривает три режима работы:

  1. Компилируется в код JavaScript с помощью Dart2js, работает в обычном браузере (Dart Web).
  2. Запустите код Dart непосредственно из командной строки с помощью DartVM (DartVM).
  3. AOT скомпилирован в машинный код, такой как фреймворк Flutter App (Flutter).

После проверки более 20 языков Flutter, наконец, выбрал Dart в качестве языка разработки по нескольким причинам:

  1. Надежная система типов, поддерживающая как статическую проверку типов, так и проверку типов во время выполнения.
  2. Оптимизация размера кода (Tree Shaking), во время компиляции сохраняется только тот код, который необходимо вызывать во время выполнения (неявные ссылки, такие как отражение, не допускаются), поэтому огромная библиотека виджетов не приведет к тому, что объем выпуска будет слишком большим.
  3. Богатые базовые библиотеки, сам Dart предоставляет множество библиотек.
  4. Сборщик мусора без блокировки нескольких поколений, оптимизированный для создания и уничтожения большого количества объектов виджетов, обычно встречающихся в инфраструктурах пользовательского интерфейса.
  5. Кроссплатформенность, iOS и Android имеют общий набор кодов.
  6. Режим работы JIT & AOT поддерживает быструю итерацию во время разработки и максимизирует производительность оборудования после официального выпуска.

В Dart есть несколько важных базовых понятий:

  • Значения всех переменных являются объектами, то есть экземплярами классов. четные числа, функции иnullтакже являются объектами, оба наследуют отObjectсвоего рода.
  • Хотя Dart является строго типизированным языком, явные объявления типов переменных необязательны, и Dart поддерживает вывод типов. Если вы не хотите использовать вывод типа, вы можете использоватьdynamicтип.
  • Dart поддерживает дженерики,List<int>представляет собой список, содержащий тип int,List<dynamic>означает список любого типа.
  • Dart поддерживает функции верхнего уровня и функции-члены класса, а также вложенные функции и локальные функции.
  • Dart поддерживает переменные верхнего уровня и переменные-члены класса.
  • В Dart нет ключевых слов public, protected и private.Переменные или функции, начинающиеся со знака подчеркивания "_", используются для обозначения того, что они видны только в библиотеке. Ссылаться наБиблиотеки и видимость.

Стратегия выделения памяти в DartVM очень проста: при создании объекта нужно всего лишь переместить указатель на существующую кучу, рост памяти всегда линейный, что избавляет от необходимости искать доступные сегменты памяти:

Потокоподобная концепция в Dart называется Isolate, и память не может быть разделена между каждым Isolate, поэтому эта стратегия выделения позволяет Dart добиться быстрого выделения без блокировок.

Сборка мусора Dart также использует алгоритм с несколькими поколениями. Новое поколение использует алгоритм «полупространства» при освобождении памяти. Когда сборка мусора запускается, Dart копирует «активные» объекты в текущем полупространстве в свободное пространство и затем освободить их целиком.Вся память в текущем пространстве:

Во время всего процесса Dart нужно оперировать только небольшим количеством "активных" объектов, а большое количество "мертвых" объектов без ссылок игнорируется.Также этот алгоритм очень подходит для сцены большого количества реконструкций виджетов в фреймворк флаттера.

Flutter Framework

Каркасная часть Flutter полностью реализована на языке Dart и имеет четкую многоуровневую архитектуру. Многоуровневая архитектура позволяет нам напрямую вызывать или даже модифицировать каждый уровень реализации в дополнение к вызову удобных функций разработки, предоставляемых Flutter (предопределенный набор высококачественных элементов управления Material) (потому что весь фреймворк принадлежит коду «пространства пользователя»). , что дает нам наибольшую степень настройки. Нижний слой фреймворка — это движок Flutter. Движок в основном отвечает за отрисовку графики (Skia), набор текста (libtxt) и обеспечение времени выполнения Dart. Весь движок реализован на C++. Слой Framework позволяет нам использовать Dart языком назвать мощные возможности движка.

Многоуровневая архитектура

Нижний уровень Framework называется Foundation, который определяет большинство самых базовых классов инструментов и методов, предоставляемых всем остальным уровням. Библиотека рисования (Painting) инкапсулирует интерфейс рисования, предоставляемый Flutter Engine, в основном для обеспечения более интуитивно понятного и удобного интерфейса при рисовании графики с фиксированным стилем, такой как элементы управления, такие как рисование масштабированных растровых изображений, рисование текста, интерполяция для создания теней и Нарисуйте границы вокруг коробки и так далее. Animation — это класс, связанный с анимацией, который предоставляет функции, аналогичные ValueAnimator системы Android, и предоставляет множество встроенных интерполяторов. Gesture предоставляет функции, связанные с распознаванием жестов, включая определения классов сенсорных событий и множество встроенных распознавателей жестов. Класс GestureBinding — это абстрактный сервисный класс для обработки жестов во Flutter, наследуемый от класса BindingBase. Классы серии Binding действуют как функции, аналогичные серии SystemService (ActivityManager, PackageManager) в Android во Flutter.Каждый класс Binding предоставляет одноэлементный объект службы, а Binding верхнего уровня приложения будет содержать все связанные абстрактные Bingding. классы. Если вы используете элементы управления, предоставляемые Flutter для разработки, вам необходимо использовать WidgetsFlutterBinding.Если вы не используете какие-либо элементы управления, предоставляемые Flutter, и напрямую вызываете слой рендеринга, вам необходимо использовать RenderingFlutterBinding.

Сам Flutter поддерживает платформы Android и iOS. В дополнение к «родному» языку производительности и разработки он также предоставляет два набора элементов управления языком дизайна для реализации материалов и Купертино, которые могут помочь приложениям лучше работать на разных платформах.

Библиотека визуализации

Дерево управления Flutter будет преобразовано в соответствующий объект рендеринга при его фактическом отображении (RenderObject) для реализации операций компоновки и рисования. Как правило, нам нужно учитывать детали дерева объектов рендеринга только при отладке макета или когда нам нужно использовать пользовательские элементы управления для достижения некоторых специальных эффектов. Основные функциональные классы, предоставляемые библиотекой рендеринга:

abstract class RendererBinding extends BindingBase with ServicesBinding, SchedulerBinding, HitTestable { ... }
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
abstract class RenderBox extends RenderObject { ... }
class RenderParagraph extends RenderBox { ... }
class RenderImage extends RenderBox { ... }
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
                                        RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
                                        DebugOverflowIndicatorMixin { ... }

RendererBindingЭто связующий слой дерева рендеринга и движка Flutter, отвечающий за управление перерисовкой кадров, размером окна и мониторингом изменений параметров, связанных с рендерингом.RenderObjectБазовый класс для всех узлов в дереве рендеринга, определяющий компоновку, рисование и компоновку связанных интерфейсов.RenderBoxи его три часто используемых подклассаRenderParagraph,RenderImage,RenderFlexЭто класс реализации определенного макета и логики рисования.

Процесс рендеринга интерфейса Flutter делится на три этапа: верстка, отрисовка и композиция.Верстка и отрисовка завершаются во Flutter framework, а композиция передается движку.

Каждый элемент управления в дереве элементов управления реализуетсяRenderObjectWidget#createRenderObject(BuildContext context) → RenderObjectметоды для создания соответствующих различных типовRenderObjectОбъекты, составляющие дерево объектов рендеринга. Поскольку Flutter значительно упрощает логику компоновки, во время всего процесса компоновки требуется только один обход глубины:

Каждый объект в дереве объектов рендеринга получает родительскийConstraintsпараметры, определить свой собственный размер, а затем родительский объект может определить положение каждого дочернего объекта в соответствии со своей собственной логикой для завершения процесса компоновки. Дочерний объект не сохраняет свое положение в контейнере, поэтому его не нужно перекомпоновывать или рисовать при изменении его положения. Информация о местоположении дочернего объекта хранится в его собственномparentDataполе, но поле поддерживается его родительским объектом и не заботится о содержимом самого поля. В то же время, из-за этой простой логики макета, Flutter может установить границу макета (граница релейаута) в некоторых узлах, то есть, когда любой объект внутри границы перекомпонуется, это не повлияет на объекты за пределами границы, и наоборот:

После завершения компоновки каждый узел в дереве объектов рендеринга имеет четкий размер и положение, и Flutter отрисовывает все объекты на разных слоях:

Поскольку узлы рисования также являются обходом глубины, вы можете видеть, что второй узел должен рисовать свой фон и передний план на разных слоях, потому что четвертый узел переключает слои (поскольку узел «4» является обязательным эксклюзивным содержимым слоя, например видео), а шестой узел также отрисовывается на красный слой. Это приведет к необходимости перерисовки переднего плана (т.е. "5") части второго узла, а также перерисовки шестого узла, который логически не связан с ним, но находится на том же слое. Чтобы избежать этого, Flutter предоставляет еще одну концепцию «перерисовки границ»:

При входе и выходе из границы перерисовки Flutter принудительно переключает новые слои, чтобы избежать взаимного влияния внутри и снаружи границы. Типичный сценарий приложения — ScrollView.Когда прокручиваемый контент перерисовывается, другой контент обычно не нужно перерисовывать. Хотя границу перерисовки можно установить вручную на любом узле, обычно нам не нужно ее реализовывать, элементы управления, предоставляемые Flutter, будут автоматически установлены там, где они должны быть установлены по умолчанию.

Библиотека виджетов (виджеты)

Библиотека элементов управления Flutter предоставляет очень богатый набор элементов управления, включая самый простой текст, изображения, контейнеры, поля ввода и анимацию. Во Flutter «все есть элемент управления», комбинируя и вкладывая различные типы элементов управления, вы можете построить интерфейс с любой функцией и любой сложностью. Наиболее важные классы, которые он содержит:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding,
			PaintingBinding, RendererBinding, WidgetsBinding { ... }
abstract class Widget extends DiagnosticableTree { ... }
abstract class StatelessWidget extends Widget { ... }
abstract class StatefulWidget extends Widget { ... }
abstract class RenderObjectWidget extends Widget { ... }
abstract class Element extends DiagnosticableTree implements BuildContext { ... }
class StatelessElement extends ComponentElement { ... }
class StatefulElement extends ComponentElement { ... }
abstract class RenderObjectElement extends Element { ... }
...

Все программы, разработанные на основе системы управления Flutter, должны использоватьWidgetsFlutterBinding, который является управляющей структурой Flutter и связующим слоем движка Flutter.WidgetЭто базовый класс всех элементов управления, и все его свойства доступны только для чтения.RenderObjectWidgetВсе классы реализации отвечают за предоставление информации о конфигурации и создание конкретныхRenderObjectElement.ElementЭто средний слой, который Flutter использует для разделения дерева элементов управления и реального объекта рендеринга.Элемент управления используется для описания соответствующего свойства элемента.После перестроения элемента управления один и тот же элемент может использоваться повторно.RenderObjectElementСодержит фактическую компоновку, рисунок и тест на попаданиеRenderObjectобъект.

StatelessWidgetиStatefulWidgetнапрямую не влияетRenderObject, они несут ответственность только за создание соответствующихRenderObjectWidget,StatelessElementиStatefulElementТоже похожая функция.

Отношения между ними следующие:

Если свойства элемента управления изменились (поскольку свойства элемента управления доступны только для чтения, изменение означает, что новое дерево элементов управления создается заново), но когда тип каждого узла в дереве не изменился, дерево элементов и дерево рендеринга Исходный объект можно полностью использовать повторно (поскольку свойства элемента и объекта рендеринга являются изменяемыми):

Однако, если тип узла в дереве управления изменен, соответствующие узлы в дереве элементов и дереве рендеринга также необходимо создать заново:

Практика на всех категориях страниц на вынос

После изучения различных функций и принципов реализации Flutter план выноса будет запущен на странице полной категории версии Flutter в оттенках серого. Для режима интеграции использования страницы Flutter в составе Приложения официальный не предоставляетИдеальная поддержка, поэтому сначала нам нужно понять, как компилируется, упаковывается и запускается Flutter.

Процесс сборки приложения Flutter

Самый простой проект Flutter содержит как минимум два файла:

При запуске программы Flutter требуется хост-проект соответствующей платформы.На Android Flutter генерирует хост, автоматически создавая проект Gradle и выполняя его в каталоге проекта.flutter create ., Flutter создаст два каталога: ios и android и создаст хост-проекты для соответствующих платформ соответственно.Содержимое каталога Android выглядит следующим образом:

В этом проекте Gradle есть только один модуль приложения, а продуктом сборки является основной APK. Flutter по умолчанию использует режим отладки при локальном запуске и выполняет его в каталоге проекта.flutter runЕго можно установить на устройство и запускать автоматически. В режиме отладки Flutter использует JIT для выполнения кода Dart. Весь код Dart будет упакован в каталог активов файла APK, а затем прочитан и выполнен DartVM, предоставленным в libflutter.so:

kernel_blob.bin — это базовый интерфейс движка Flutter и основные функции языка Dart:

third_party/dart/runtime/bin/*.dart
third_party/dart/runtime/lib/*.dart
third_party/dart/sdk/lib/_http/*.dart
third_party/dart/sdk/lib/async/*.dart
third_party/dart/sdk/lib/collection/*.dart
third_party/dart/sdk/lib/convert/*.dart
third_party/dart/sdk/lib/core/*.dart
third_party/dart/sdk/lib/developer/*.dart
third_party/dart/sdk/lib/html/*.dart
third_party/dart/sdk/lib/internal/*.dart
third_party/dart/sdk/lib/io/*.dart
third_party/dart/sdk/lib/isolate/*.dart
third_party/dart/sdk/lib/math/*.dart
third_party/dart/sdk/lib/mirrors/*.dart
third_party/dart/sdk/lib/profiler/*.dart
third_party/dart/sdk/lib/typed_data/*.dart
third_party/dart/sdk/lib/vmservice/*.dart
flutter/lib/ui/*.dart

platform.dill — это код, реализующий логику страницы, а также код Flutter Framework и других библиотек, от которых зависит pub:

flutter_tutorial_2/lib/main.dart
flutter/packages/flutter/lib/src/widgets/*.dart
flutter/packages/flutter/lib/src/services/*.dart
flutter/packages/flutter/lib/src/semantics/*.dart
flutter/packages/flutter/lib/src/scheduler/*.dart
flutter/packages/flutter/lib/src/rendering/*.dart
flutter/packages/flutter/lib/src/physics/*.dart
flutter/packages/flutter/lib/src/painting/*.dart
flutter/packages/flutter/lib/src/gestures/*.dart
flutter/packages/flutter/lib/src/foundation/*.dart
flutter/packages/flutter/lib/src/animation/*.dart
.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.6/lib/*.dart
.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.5/lib/*.dart
.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.4.2/*.dart

И kernel_blob.bin, и platform.dill взяты из flutter_tools.bundle.dartвызыватьKernelCompilerгенерировать.

В режиме выпуска (flutter run --release), Flutter будет использовать режим работы Dart AOT и преобразовывать код Dart в инструкции ARM при компиляции:

В упакованном APK нет ни kernel_blob.bin, ни platform.dill, а четыре файла (isolate/vm)_snapshot_(data/instr) заменяют их функции. Файл моментального снимка создается Flutter SDK.flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshotГенерация команд, vm_snapshot_* — это данные и инструкции кода, необходимые для запуска виртуальной машины Dart, isolate_snapshot_* — это данные и инструкции кода, необходимые для запуска каждого изолята.

Механизм работы приложения Flutter

APK, созданный Flutter, распаковывает все файлы ресурсов в каталоге assets в каталог flutter в каталоге личных файлов приложения во время выполнения, в основном включая icudtl.dat для кодировки символов и kernel_blob.bin, файлы снимка платформы. режим. По умолчанию флаттерApplication#onCreateкогда звонятFlutterMain#startInitializationдля запуска задачи декомпрессии, затем вFlutterActivityDelegate#onCreateвызыватьFlutterMain#ensureInitializationCompleteдождаться завершения задачи распаковки.

Flutter использует выполнение JIT в режиме отладки, в основном для поддержки популярной функции горячего обновления:

Когда запускается горячее обновление, Flutter обнаружит измененный файл Dart, синхронизирует его с каталогом частного кеша приложения, а DartVM загрузит и изменяет соответствующий класс или метод.После перестройки дерева управления вы можете сразу увидеть эффект на устройстве. .

В режиме Release Flutter напрямую сопоставит файл моментального снимка с памятью, чтобы выполнить содержащиеся в нем инструкции:

В режиме выпуска,FlutterActivityDelegate#onCreateвызыватьFlutterMain#ensureInitializationCompleteМетод установит имя файла моментального снимка (если не задано, используйте значение по умолчанию, указанное выше) и другие рабочие параметры, установленные в AndroidManifest, в соответствующий объект класса C++ с тем же именем, построитьFlutterNativeViewвызывается, когда экземплярnativeAttachдля инициализации DartVM и запуска скомпилированного кода Dart.

Упаковка библиотеки Android

Поняв структуру и механизм работы проекта Flutter, мы можем упаковать его в AAR в соответствии с его требованиями и интегрировать в существующее нативное приложение. Сначала измените в andorid/app/build.gradle:

APK AAR
Изменить тип плагина Android apply plugin: 'com.android.application' apply plugin: 'com.android.library'
Удалить поле applicationId applicationId "com.example.fluttertutorial" applicationId "com.example.fluttertutorial"
Рекомендуется добавить и опубликовать все функции конфигурации для облегчения отладки. - defaultPublishConfig 'release'
publishNonDefault true

После простой модификации мы можем использовать инструмент командной строки Android Studio или Gradle для упаковки кода Flutter в файл aar. Ресурсы, необходимые для запуска Flutter, будут включены в aar, и после публикации на сервере maven или в локальном репозитории maven на них можно будет ссылаться в собственном проекте приложения.

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

Повторное использование ресурса изображения

Flutter по умолчанию упаковывает все файлы ресурсов изображений в каталог assets, но мы не используем Flutter для разработки новой страницы. Ресурсы изображений будут размещены в каждом каталоге drawable в соответствии со спецификацией Android. Даже новая страница будет иметь много ресурсов изображений. , Это повторно используемая сцена, поэтому не следует добавлять ресурсы изображения в каталог ресурсов.

Flutter официально не предоставляет способ прямого вызова ресурсов изображения в каталоге drawable, ведь обработка файлов drawable будет включать в себя множество логики, связанной с платформой Android (разрешение экрана, версия системы, язык и т. д.), и чтение файлов каталога ресурсов.Операция также реализована на C++ внутри движка, и сложно реализовать функцию чтения рисуемых файлов на уровне Dart. Flutter также поддерживает добавление при обработке файлов в каталог активов.Ресурсы изображений с несколькими увеличениями, и может использоваться, когдадобровольно выбрать, но Flutter требует, чтобы каждое изображение было 1-кратным, и тогда он распознает изображения в соответствующих других каталогах увеличения:

flutter:
  assets:
    - images/cat.png
    - images/2x/cat.png
    - images/3.5x/cat.png
new Image.asset('images/cat.png');

После такой настройки изображения с соответствующей плотностью можно корректно использовать на устройствах с разным разрешением. Однако, чтобы уменьшить размер пакета APK, наши растровые ресурсы обычно обеспечивают только обычно используемое 2-кратное разрешение, а устройства с другими разрешениями будут автоматически масштабироваться до соответствующего размера во время выполнения. Для этой особой ситуации мы также предоставляем те же возможности, что и нативные приложения, без увеличения размера пакета:

  1. Масштабируйте указанный ресурс изображения в соответствии с плотностью экрана устройства перед вызовом страницы Flutter и сохраняйте его в частном каталоге приложения.
  2. При использовании во Flutter по индивидуальному заказуWMImageЗагружается элемент управления, который фактически загружается путем преобразования в FileImage и автоматической установки масштаба в devicePixelRatio.

Таким образом, проблема размера пакета APK и ресурса изображения, отсутствующего в 1x изображении, может быть решена одновременно.

Связь между Flutter и нативным кодом

Мы реализовали только одну страницу с Flutter, а большая часть существующей логики реализована на Java.Во время выполнения есть много сценариев, которые должны использовать логику и функции нативного приложения, такие как сетевые запросы.Будет использоваться наша унифицированная сетевая библиотека в каждом к сетевому запросу добавляется много общих параметров, а также он отвечает за мониторинг таких показателей, как показатель успешности, а также за отчет об исключениях.Нам нужно сообщать о его стеке и информации об окружении на сервер при обнаружении ключевых исключений . Эти функции вряд ли будут сразу реализованы в Dart, поэтому нам нужно использовать функцию Platform Channel, предоставляемую Dart, для реализации взаимных вызовов между Dart и Java.

Взяв за пример сетевые запросы, мы определяемMethodChannelОбъект:

import 'dart:async';
import 'package:flutter/services.dart';
const MethodChannel _channel = const MethodChannel('com.sankuai.waimai/network');
Future<Map<String, dynamic>> post(String path, [Map<String, dynamic> form]) async {
  return _channel.invokeMethod("post", {'path': path, 'body': form}).then((result) {
    return new Map<String, dynamic>.from(result);
  }).catchError((_) => null);
}

Затем реализуйте MethodChannel с тем же именем на стороне Java:

public class FlutterNetworkPlugin implements MethodChannel.MethodCallHandler {

    private static final String CHANNEL_NAME = "com.sankuai.waimai/network";

    @Override
    public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
        switch (methodCall.method) {
            case "post":
                RetrofitManager.performRequest(post((String) methodCall.argument("path"), (Map) methodCall.argument("body")),
                        new DefaultSubscriber<Map>() {
                            @Override
                            public void onError(Throwable e) {
                                result.error(e.getClass().getCanonicalName(), e.getMessage(), null);
                            }

                            @Override
                            public void onNext(Map stringBaseResponse) {
                                result.success(stringBaseResponse);
                            }
                        }, tag);
                break;

            default:
                result.notImplemented();
                break;
        }
    }
}

После регистрации на странице Flutter вызовите метод post для вызова соответствующей реализации Java:

loadData: (callback) async {
    Map<String, dynamic> data = await post("home/groups");
    if (data == null) {
      callback(false);
      return;
    }
    _data = AllCategoryResponse.fromJson(data);
    if (_data == null || _data.code != 0) {
      callback(false);
      return;
    }
    callback(true);
  }),

Совместимость с библиотекой SO

Flutter официально предоставляет библиотеки SO только для четырех архитектур ЦП: armeabi-v7a, arm64-v8a, x86 и x86-64, из которых серия x86 поддерживает только режим отладки, но большое количество SDK, используемых на вынос, предоставляют только библиотеки архитектуры armeabi. . Хотя мы можем модифицировать двигатель,srcкорневой каталог иthird_party/dartПод содержаниемbuild/config/arm.gni,third_party/skiaв каталогеBUILD.gnПодождите, пока файл конфигурации скомпилирует версию движка Flutter для armeabi, но на самом деле большинство устройств на рынке уже поддерживают armeabi-v7a, и предоставленные им инструкции по операциям с плавающей запятой с аппаратным ускорением могут значительно повысить скорость работы. Flutter: Вы можете активно блокировать устройства, которые не поддерживают armeabi-v7a, и напрямую использовать версию движка armeabi-v7a. Для этого нам сначала нужно изменить движок, предоставленный Flutter, в каталоге установки Flutter.bin/cache/artifacts/engineFlutter загружает движки для всех платформ по адресу:

Нам нужно только изменить flutter.jar под android-arm, android-arm-profile и android-arm-release и переместить lib/armeabi-v7a/libflutter.so в lib/armeabi/libflutter.so:

cd $FLUTTER_ROOT/bin/cache/artifacts/engine
for arch in android-arm android-arm-profile android-arm-release; do
  pushd $arch
  cp flutter.jar flutter-armeabi-v7a.jar # 备份
  unzip flutter.jar lib/armeabi-v7a/libflutter.so
  mv lib/armeabi-v7a lib/armeabi
  zip -d flutter.jar lib/armeabi-v7a/libflutter.so
  zip flutter.jar lib/armeabi/libflutter.so
  popd
done

Это будет играть в каталоге apk lib / armeabi после такой библиотеки пакета. Если устройство не поддерживает ARMEABI-V7A во время выполнения, нам нужно активно идентифицировать и блокировать этот тип устройства. Также очень просто определить, поддерживает ли устройство ArmeAbi-V7A на Android:

public static boolean isARMv7Compatible() {
    try {
        if (SDK_INT >= LOLLIPOP) {
            for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
                if (abi.equals("armeabi-v7a")) {
                    return true;
                }
            }
        } else {
            if (CPU_ABI.equals("armeabi-v7a") || CPU_ABI.equals("arm64-v8a")) {
                return true;
            }
        }
    } catch (Throwable e) {
        L.wtf(e);
    }
    return false;
}

Оттенки серого и стратегия автоматического перехода на более раннюю версию

Horn — это кроссплатформенный SDK для распространения конфигурации внутри Meituan. С помощью Horn можно легко указать переключатель оттенков серого:

Определите ряд условий на странице конфигурации условий, а затем добавьте новое поле flutter на странице конфигурации параметров:

Поскольку восходящая стратегия ABI выполняется на стороне клиента, определенные здесь правила ABI не включены.

Flutter все еще находится на стадии бета-тестирования, и сбои во время процесса в градациях серого неизбежны. После обнаружения сбоя понижение модели или идентификатора устройства может минимизировать влияние, но мы можем сделать это быстрее. SDK для сбора сбоев на вынос также поддерживает сбор сбоев JNI. Мы специально зарегистрировали прослушиватель сбоев для Flutter. После сбора сбоев JNI, связанных с Flutter, функция Flutter устройства будет немедленно остановлена. Перед запуском Flutter он будет быть судимым в первую очередь.FLUTTER_NATIVE_CRASH_FLAGСуществует ли файл, если он существует, это означает, что на устройстве произошел сбой, связанный с Flutter, который, вероятно, вызван несовместимостью, и функция Flutter больше не будет использоваться на устройстве в текущем цикле версий.

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

import 'package:wm_app/plugins/wm_metrics.dart';

void main() {
  runZoned(() => runApp(WaimaiApp()), onError: (Object obj, StackTrace stack) {
    uploadException("$obj\n$stack");
  });
}

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

Анализ стека сбоев и данных об исключениях

Вся двигательная часть Flutter реализована на C/C++, Чтобы уменьшить размер пакета, все библиотеки SO удалят информацию о таблице символов при их выпуске. Как и другие аварийные стеки JNI, мы можем видеть только информацию, такую ​​​​как смещения адресов памяти, в информации о стеке, которую мы сообщаем:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys'
Revision: '0'
Author: collect by 'libunwind'
ABI: 'arm64-v8a'
pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
 
backtrace:
    r0 00000000  r1 ffffffff  r2 c0e7cb2c  r3 c15affcc
    r4 c15aff88  r5 c0e7cb2c  r6 c15aff90  r7 bf567800
    r8 c0e7cc58  r9 00000000  sl c15aff0c  fp 00000001
    ip 80000000  sp c0e7cb28  lr c11a03f9  pc c1254088  cpsr 200c0030
    #00 pc 002d7088  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #01 pc 002d5a23  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #02 pc 002d95b5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #03 pc 002d9f33  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #04 pc 00068e6d  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #05 pc 00067da5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #06 pc 00067d5f  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #07 pc 003b1877  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #08 pc 003b1db5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
    #09 pc 0000241c  /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

Только по этой информации трудно найти проблему, поэтому нам нужно использовать ndk-stack, предоставленный NDK, чтобы проанализировать конкретное место кода:

ndk-stack -sym PATH [-dump PATH]
Symbolizes the stack trace from an Android native crash.
  -sym PATH   sets the root directory for symbols
  -dump PATH  sets the file containing the crash dump (default stdin)

Если используется пользовательский движок, он должен использоватьсяengine/src/out/android-releaseФайл libflutter.so, скомпилированный под. В общем, мы используем официальную версию движка, которую можно найти вflutter_infraСтраница напрямую загружает SO-файл с таблицей символов и загружает соответствующий файл в соответствии с версией инструмента Flutter, используемой во время упаковки. Например, бета-версия 0.4.4:

$ flutter --version # version命令可以看到Engine对应的版本 06afdfe54e
Flutter 0.4.4 • channel beta • https://github.com/flutter/flutter.git
Framework • revision f9bb4289e9 (5 weeks ago) • 2018-05-11 21:44:54 -0700
Engine • revision 06afdfe54e
Tools • Dart 2.0.0-dev.54.0.flutter-46ab040e58
$ cat flutter/bin/internal/engine.version # flutter安装目录下的engine.version文件也可以看到完整的版本信息 06afdfe54ebef9168a90ca00a6721c2d36e6aafa
06afdfe54ebef9168a90ca00a6721c2d36e6aafa

Получив номер версии двигателя,console.cloud.Google.com/storage/like о…Чтобы просмотреть все продукты сборки, соответствующие этой версии, загрузите файл symbols.zip в каталоги android-arm-release, android-arm64-release и android-x86 и сохраните их в соответствующем каталоге:

Выполните ndk-stack, чтобы увидеть фактический код сбоя и конкретную информацию о номере строки:

ndk-stack -sym flutter-production-syms/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/armeabi-v7a -dump flutter_jni_crash.txt 
********** Crash dump: **********
Build fingerprint: 'Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys'
pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame #00 pc 002d7088  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::WordBreaker::setText(unsigned short const*, unsigned int) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/minikin/WordBreaker.cpp:55
Stack frame #01 pc 002d5a23  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::LineBreaker::setText() at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/minikin/LineBreaker.cpp:74
Stack frame #02 pc 002d95b5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::ComputeLineBreaks() at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/txt/paragraph.cc:273
Stack frame #03 pc 002d9f33  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::Layout(double, bool) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/txt/paragraph.cc:428
Stack frame #04 pc 00068e6d  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine blink::ParagraphImplTxt::layout(double) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/lib/ui/text/paragraph_impl_txt.cc:54
Stack frame #05 pc 00067da5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine tonic::DartDispatcher<tonic::IndicesHolder<0u>, void (blink::Paragraph::*)(double)>::Dispatch(void (blink::Paragraph::*)(double)) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../topaz/lib/tonic/dart_args.h:150
Stack frame #06 pc 00067d5f  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine void tonic::DartCall<void (blink::Paragraph::*)(double)>(void (blink::Paragraph::*)(double), _Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../topaz/lib/tonic/dart_args.h:198
Stack frame #07 pc 003b1877  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::AutoScopeNativeCallWrapperNoStackCheck(_Dart_NativeArguments*, void (*)(_Dart_NativeArguments*)) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../third_party/dart/runtime/vm/native_entry.cc:198
Stack frame #08 pc 003b1db5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::LinkNativeCall(_Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../third_party/dart/runtime/vm/native_entry.cc:348
Stack frame #09 pc 0000241c  /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

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

FlutterException: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'num' in type cast
#0      _$CategoryGroupFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:29)
#1      new CategoryGroup.fromJson (package:wm_app/all_category/model/category_model.dart:51)
#2      _$CategoryListDataFromJson.<anonymous closure> (package:wm_app/lib/all_category/model/category_model.g.dart:5)
#3      MappedListIterable.elementAt (dart:_internal/iterable.dart:414)
#4      ListIterable.toList (dart:_internal/iterable.dart:219)
#5      _$CategoryListDataFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:6)
#6      new CategoryListData.fromJson (package:wm_app/all_category/model/category_model.dart:19)
#7      _$AllCategoryResponseFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:19)
#8      new AllCategoryResponse.fromJson (package:wm_app/all_category/model/category_model.dart:29)
#9      AllCategoryPage.build.<anonymous closure> (package:wm_app/all_category/category_page.dart:46)
<asynchronous suspension>
#10     _WaimaiLoadingState.build (package:wm_app/all_category/widgets/progressive_loading_page.dart:51)
#11     StatefulElement.build (package:flutter/src/widgets/framework.dart:3730)
#12     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642)
#13     Element.rebuild (package:flutter/src/widgets/framework.dart:3495)
#14     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242)
#15     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:626)
#16     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208)
#17     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990)
#18     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930)
#19     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842)
#20     _rootRun (dart:async/zone.dart:1126)
#21     _CustomZone.run (dart:async/zone.dart:1023)
#22     _CustomZone.runGuarded (dart:async/zone.dart:925)
#23     _invoke (dart:ui/hooks.dart:122)
#24     _drawFrame (dart:ui/hooks.dart:109)

Сравнение производительности Flutter и нативной

Хотя полные страницы категорий, использующие нативную реализацию (слева) и реализацию Flutter (справа), практически неотличимы в реальном использовании:

Но нам также нужно более четкое сравнение данных с точки зрения производительности.

Две метрики производительности страницы, о которых мы больше всего заботимся, — это время загрузки страницы и скорость рендеринга страницы. Чтобы проверить скорость загрузки страницы, вы можете напрямую использовать инструмент тестирования производительности Metrics внутри Meituan.Мы создаем объект Activity страницы в качестве времени начала загрузки страницы и возвращаем данные API страницы в качестве времени окончания загрузки страницы. Из данных видно, что страницы двух реализаций запускаются более 400 раз, среднее время загрузки нативной реализации (AllCategoryActivity) составляет 210 мс, а среднее время загрузки реализации Flutter (FlutterCategoryActivity) — 231 мс. Учитывая, что мы еще не кэшировали и не использовали FlutterView повторно, каждый раз при создании FlutterView необходимо инициализировать всю среду Flutter и загрузить соответствующий код, а дополнительные 20 мс все еще находятся в ожидаемом диапазоне:

Поскольку логика пользовательского интерфейса Flutter и код рисования не выполняются в основном потоке, исходная функция FPS Metrics не может учитывать реальную ситуацию на странице Flutter.Нам нужно использовать специальный метод для сравнения эффективности рендеринга двух реализаций. Рендеринг интерфейса, реализованный Android, требует времени и использует предоставленную систему.FrameMetricsИнтерфейс для мониторинга:

public class AllCategoryActivity extends WmBaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener() {
                List<Integer> frameDurations = new ArrayList<>(100);
                @Override
                public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                    frameDurations.add((int) (frameMetrics.getMetric(TOTAL_DURATION) / 1000000));
                    if (frameDurations.size() == 100) {
                        getWindow().removeOnFrameMetricsAvailableListener(this);
                        L.w("AllCategory", Arrays.toString(frameDurations.toArray()));
                    }
                }
            }, new Handler(Looper.getMainLooper()));
        }
        super.onCreate(savedInstanceState);
        // ...
    }
}

Flutter может получить только отнимающие у процессора время операции пользовательского интерфейса в каждом кадре на уровне Framework, а операции с графическим процессором реализованы внутри механизма Flutter, поэтому необходимо изменить механизм, чтобы отслеживать полное время рендеринга.src/flutter/shell/common/rasterizer.ccДобавьте в файл:

void Rasterizer::DoDraw(std::unique_ptr<flow::LayerTree> layer_tree) {
  if (!layer_tree || !surface_) {
    return;
  }

  if (DrawToSurface(*layer_tree)) {
    last_layer_tree_ = std::move(layer_tree);
#if defined(OS_ANDROID)
    if (compositor_context_->frame_count().count() == 101) {
      std::ostringstream os;
      os << "[";
      const std::vector<TimeDelta> &engine_laps = compositor_context_->engine_time().Laps();
      const std::vector<TimeDelta> &frame_laps = compositor_context_->frame_time().Laps();
      size_t i = 1;
      for (auto engine_iter = engine_laps.begin() + 1, frame_iter = frame_laps.begin() + 1;
           i < 101 && engine_iter != engine_laps.end(); i++, engine_iter++, frame_iter++) {
        os << (*engine_iter + *frame_iter).ToMilliseconds() << ",";
      }
      os << "]";
      __android_log_write(ANDROID_LOG_WARN, "AllCategory", os.str().c_str());
    }
#endif
  }
}

Вы можете получить реальное время, потребляемое при рисовании каждого кадра. Во время теста мы открывали страницы двух реализаций 100 раз и выполняли две операции прокрутки после каждого открытия, чтобы отрисовать 100 кадров, и записывали время, затраченное на каждый кадр из 100 кадров:

for (( i = 0; i < 100; i++ )); do
	openWMPage allcategory
	sleep 1
	adb shell input swipe 500 1000 500 300 900
	adb shell input swipe 500 1000 500 300 900
	adb shell input keyevent 4
done

Возьмите среднее время, затрачиваемое на каждый кадр в 100 запусках результатов теста, чтобы получить среднее время, затрачиваемое на каждый кадр (горизонтальная ось — это последовательность кадров, а вертикальная ось — время, затрачиваемое на кадр, в миллисекундах). ):

Как нативная реализация Android, так и версия Flutter будут превышать 16 мс в первых 5 кадрах открытия страницы.При первом открытии страницы нативная реализация должна создать большое количество представлений, и Flutter также необходимо создать большое количество виджетов.Большинство элементов управления и узлов рендеринга можно повторно использовать в последующих кадрах (собственный RenderNode и RenderObject Flutter), поэтому операции компоновки и рендеринга при запуске занимают больше всего времени.

В 10 000 кадров (100 раз × 100 кадров каждый раз) собственное общее среднее значение Android составляет 10,21 мс, а общее среднее значение Flutter — 12,28 мс.Общее количество кадров, потерянных собственной реализацией Android, составляет 851 кадр, 8,51%. , а общее количество потерянных Flutter кадров составляет 987 кадров и 9,87%. Исходя из того, что собственная реализация обработки сенсорных событий и перерисовки полностью оптимизирована, Flutter может полностью соответствовать собственной производительности.

Суммировать

Flutter все еще находится на ранней стадии и еще не выпустил официальную версию Release, но мы видим, что команда Flutter работает над достижением этой цели. Хотя экология разработки Flutter не так зрела, как у нативных приложений для Android и iOS, многие часто используемые сложные элементы управления по-прежнему необходимо реализовывать сами по себе, а некоторые еще сложнее (например, те, которые еще официально не предоставлены).ListView.scrollTo(index)функция), но Flutter по-прежнему имеет большие преимущества во многих средах пользовательского интерфейса с точки зрения высокой производительности и кросс-платформенности.

Для разработки приложений Flutter можно использовать только язык Dart.Сам Dart имеет как возможности статического языка, так и некоторые особенности динамических языков.Для разработчиков Java и JavaScript порог невысок.Начать можно быстро за 3-5 дней,около 1 - 2 недели на освоение. При разработке версии всех категорий страниц для Flutter мы также глубоко осознали прелесть языка Dart.Функции языка Dart делают процесс создания интерфейса Flutter более интуитивным, чем нативный XML+JAVA для Android, а объем кода также сокращено с исходных 900 строк до более чем 500 строк (исключая упомянутые общие компоненты). После того, как страница Flutter будет интегрирована в приложение, объем APK увеличится как минимум на 5,5 МБ, включая файл библиотеки SO на 3,3 МБ и файл данных ICU на 2,2 МБ. код около 2MB.

Собственные характеристики Flutter подходят для сценариев, которые обеспечивают согласованное кроссплатформенное взаимодействие между iOS и Android, а также высокопроизводительные эффекты взаимодействия с пользовательским интерфейсом, но не подходят для сценариев, предусматривающих динамическое развертывание. Flutter уже может обеспечить динамическое развертывание на Android, но из-за ограничений Apple добиться динамического развертывания на iOS очень сложно, и команда Flutter также активно общается с Apple.

Команда внешнего интерфейса Meituan Takeaway продолжит использовать Flutter в других сценариях в будущем и будет активно сообщать о проблемах, обнаруженных и устраненных в процессе практики, сообществу открытого исходного кода, чтобы помочь Flutter лучше развиваться. Если вы также заинтересованы во Flutter, присоединяйтесь.

использованная литература

  1. Официальный китайский сайт Flutter

  2. Обзор технологии фреймворка Flutter

  3. Репозиторий плагинов Flutter

  4. A Tour of the Dart Language

  5. A Tour of the Dart Libraries

  6. Why Flutter Uses Dart

  7. Введение в механизм компоновки Flutter

  8. Flutter's Layered Design

  9. Flutter's Rendering Pipeline

  10. Flutter: The Best Way to Build for Mobile?@GOTO conf

  11. Flutter Engine

  12. Writing custom platform-specific code with platform channels

  13. Flutter Engine Operation in AOT Mode

  14. Flutter's modes

  15. Symbolicating-production-crash-stacks

об авторе

Шаоцзе, старший инженер Meituan, присоединился к Meituan в 2017 году и в настоящее время в основном отвечает за строительство инфраструктуры, например, за мониторинг приложений для доставки еды.

Набор персонала

Meituan Takeaway ищет старших / старших инженеров и технических экспертов для Android, iOS, FE, База Пекин, Шанхай, Чэнду Заинтересованные студенты могут отправить свои резюме на wukai05 #meituan.com.