Расширяем узкие места Xamarin.Forms +25


При создании мобильных приложений возникает множество различных хотелок в духе «как было бы хорошо, если бы…» И можно подставлять пункты, которые требуют улучшения. У начинающих разработчиков в этот список с высокой вероятностью войдет и пункт «если бы работало быстрее …» В данной статье будут собраны рекомендации, которые помогут начинающим разработчикам Xamarin.Forms обойти узкие места фреймворка и инструментов сборки. А начнем мы с железа.



Передаю слово автору.

1. Настройка окружения


Первое, с чем сталкиваются разработчики Xamarin.Forms — длительная сборка проектов, гораздо дольше комфортного уровня. Это связано с тем, что при сборке создается и обновляется большое количество файлов.



Чтобы ускорить сборку, достаточно перенести папку с исходными кодам на RAM-диск. Это когда часть оперативной памяти используется в качестве локального диска или папки в файловой системе Mac’а. Чтобы ничего не терялось, данные с RAM-диска сохраняются на SSD по таймеру. Если у вас все еще стоит обычный жесткий диск (HDD), то стоит обязательно обновиться на SSD. RAM-диск 4 ГБ + хороший SSD заметно ускоряют сборку. Примерно на порядок.


В сети вы можете найти различные утилиты для работы с RAM-дисками, а также инструкции по работе с ними.

2. Узкие места Xamarin


Как и при работе с любым другим инструментом, стоит понимать особенности Xamarin.Forms при создании приложений. Если задача приложения удивить пользователя большим количеством анимаций и нестандартным интерфейсом, то кроссплатформенные фреймворки — не лучший выбор. А вот для относительно статических бизнес-приложений они подойдут гораздо лучше.


Так как Xamarin.Forms это надстройка над классическими Xamarin.iOS и Xamarin.Android, то стоит понимать их узкие места. Ниже представлена архитектура приложения на Xamarin.iOS и Xamarin.Android.



Xamarin-приложение состоит из двух частей: нативной и кроссплатформенной (среда Mono). Эти части общаются друг с другом через специальный мост (bridge). Вокруг каждого нативного класса и типа создается обертка для окружения Mono. При передаче данных и вызове методов в переходе “нативный”-”кроссплатформенный” происходит небольшое падение производительности, поэтому стоит обязательно это учитывать.


Например, при вычислениях и работе с данными использовать типы и классы из окружение Mono (C#/.NET), а не нативные (nfloat). Также следуем минимизировать управление UI из C#-кода. Например, не осуществлять анимации или интенсивно обновлять UI в цикле из кроссплатформенной части. Не то, чтобы были какие-то тормоза, но при больших вычислениях и массивном управлении UI (большое количество элементов с асинхронно приходящими данными и различные анимации) могут быть заметные пользователю и ненужные задержки.


В полной мере это относится и к Xamarin.Forms, который решает непростую задачу предоставить одновременно гибкий и высокопроизводительный слой работы с пользовательским интерфейсом и платформенной функциональностью для iOS, Android (а также Windows UWP, mac OS, GTK# и Tizen до кучи, но их в расчет не берем). И несмотря на то, что большинство “детских” болезней уже преодолено, в нем все еще остаются узкие места, которые следует обязательно учитывать при разработке.


3. XAML


В первых версиях Xamarin.Forms были явные проблемы с производительностью XAML-страниц. Можно было описать тот же пользовательский интерфейс на C#-коде — это уменьшало время создания (вызов конструктора!) Page или View более чем на 300 мс на устройствах средней производительности. Для сложных компонентов это время было еще больше. Свою лепту в увеличение времени холодного запуска приложения вносил App.xaml. Если в нем было много стилей и других ресурсов, то запуск приложения мог увеличиться на заметные секунды. Опять же на пустом месте. Особенно эта проблема была актуальна для Android, где простой уход от использования App.xaml и стартовых Page (например, MainPage.xaml и MenuPage.xaml) в пользу C#-кода сокращал время запуска приложения иногда в два раза.


Добавление компиляции XAML заметно уменьшило это время. В Xamarin.Forms 2.5.0 получились такие показатели для страницы Hello World:


  • XAML без компиляции 441 мс
  • XAML с компиляцией 188 мс
  • Код на C# 182 мс.



Как видим, в каких-то редких случаях, например для ячеек ListView, где важна максимальная производительность, можно использовать описание интерфейса на C#. А для всех остальных сценариев можно заметно ускорить работу UI за счет добавления строки [assembly: XamlCompilation(XamlCompilationOptions.Compile)] в код вашего проекта.





Также стоит учитывать, что для вложенных XAML (XAML-страница использует внешние View, также описанные за XAML) со сложной версткой и большим количеством элементов время инициализации будет выше приведенного примера Hello World. Компиляция XAML может заметно ускорить создание Page и View, а мы переходим к следующему пункту.


4. Layout


Чтобы обеспечить кроссплатформенную верстку пользовательского интерфейса для iOS, Android (и других платформ) в Xamarin.Forms реализованы свои механизмы компоновки (Layout) интерфейсных элементов. В нативном iOS и Android есть свои механизмы Layout, но они не подходят напрямую для кроссплатформенных приложений, так как имеют специфичную для каждой платформы логику.





Чтобы правильно скомпоновать страницы, фреймворк должен знать размеры вложенных на страницу элементов, а это запускает обход всего дерева дочерних контролов. В будущих релизах Xamarin.Forms обещают множество оптимизаций в том числе механизмах Layout. Но в настоящее время стоит помнить о простом правиле — чем больше точных цифр будет указано у UI-элементов и чем меньше будет уровень вложенности, тем быстрее будет отрабатывать компоновка. Например, можно указать AbsoluteLayout.LayoutBounds="0.1,0.1,0.3,0.3" или WidthRequest=”300” в свойствах UI-элемента.


Также иногда возникает потребность в реализации своих Layout. В них вы можете для каждого UI-элемента вычислить точные размеры, например, относительно размеров родительского View. Это уберет долгий пересчет координат и поможет, опять же, ускорить тяжелые ячейки в ListView.



Подробнее про оптимизацию Layout


Подробнее про создание своих Layout



5. Обновление UI и правильные ViewModel


Еще одной болью всех UI-подсистем, включая Xamarin.Forms, является пересчет размеров при обновлении данных. Это пресловутая проблема обновления дерева визуальных объектов, для решения которой в Web-разработке и появился ReactJS с аналогами. В mobile эта проблема есть, хотя и не такая острая.


В Xamarin.Forms используется архитектура MVVM, в которой при обновлении свойства ViewModel вызывается событие PropertyChanged, новые данные приходят в UI и View может запросить пересчет размеров и координат у всей страницы целиком.





Если вы обновляете 10 разных полей, то произойдет 10 циклов компоновки по всему дереву элементов на странице, а это заметные глазу пользователя 10 циклов. Для того, чтобы вместо 10 циклов запускать 1, следует использовать 1 модель с нужными 10 полями. Теперь модель и View будут обновляться целиком — одно событие PropertyChanged вместо 10.





Итак, в нашей сегодняшней статье мы рассмотрели основные узкие места Xamarin.Forms и подходы, которые нивелируют их эффект. И это еще не все на сегодня!


Приглашение на Xamarin Day


Если у вас есть вопросы по Xamarin, и вам интересна разработка кроссплатформенных приложений, то напоминаю о предстоящем Xamarin Day, который пройдет 31 января в Москве. На нем вы сможете не только узнать о best practices, но и получить живую консультацию по вашему коду! Показываете куски вашего кода, по которым у вас есть вопросы (проблемы, баги, архитектура, низкая производительность и т.д.) и получаете бесплатную консультацию от экспертов прямо на месте. Чтобы подать заявку на индивидуальную консультацию по вашему проекту — напишите краткую информацию о нем и проблеме на events-techdays@microsoft.com.


Подробнее о мероприятии здесь.

Вступайте в наш уютный чат Xamarin Developers в Telegram.



Оставайтесь на связи и обязательно приходите на Xamarin Day!



Об авторе


Вячеслав Черников — руководитель отдела разработки компании Binwell, Microsoft MVP и Xamarin Certified Developer. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone. Статьи Вячеслава вы также можете прочитать в блоге на Medium.

Другие статьи автора вы можете найти в нашей колонке #xamarincolumn.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (5):

  1. Juels
    /#10641066

    Очень здорово! За Layout отдельное спасибо, всегда не с первого раза попадал правильно.
    Жаль, что все самые интересные доклады проходят только в Москве :(

  2. andreyverbin
    /#10642704

    В Xamarin.Forms 2.5.0 получились такие показатели для страницы Hello World:
    XAML без компиляции 441 мс
    XAML с компиляцией 188 мс
    Код на C# 182 мс.

    182 мс это 5 кадров в секунду и это hello world. Если так для каждой незакешированной ячейки, то Xamarin.Forms есть куда еще расти (мягко говоря). Мне кажется вы могли замерить время jit (на android), либо время подгрузки каких-либо библиотек.


    Я как-то мерял скорость маршалинга из C# в ObjC и назад и получалось, что разницы практически нет. Затраты на p/Invoke теряются на фоне типичных операций типа выставления текста (label.Text) и составляют доли %. То есть сам Xamarin оказывает незначительное влияние и точно не медленее ObjC/Swift. Forms другое дело, про них я ничего не знаю, всегда работал только с Native.

    • devious
      /#10643540

      182 мс это не «5 кадров в секунду» (откуда вообще fps взялись?), а просто 182 мс (~1/5 секунды) задержки перед открытием экрана или первого в приложении создания контрола — дальше все кешируется. Перфекционизм это хорошо, конечно :)

      В плане роста и развития — да, в XF еще много всякого будет дорабатываться и улучшаться.

  3. ruma46
    /#10643792

    Прочитав Ваш совет, установил утилиту для RAM disk'а, ту, что выдается первым номером по ссылке в статье. Произвёл замеры (правда, не для Xamarin, а для C++/MFC проекта). Получилась не такая уж и большая выгода в моём случае. На полную перекомпиляцию ушло 4:40 на RAM диске против 4:55 на SSD. 15 секунд — это 5% разницы всего, а заморочки с диском и возможная потеря данных в случае непредсказуемого выключения, на мой взгляд, не стоят того.

  4. devious
    /#10644548

    > правда, не для Xamarin, а для C++/MFC проекта
    Статья не про MFC, а про Xamarin.Forms :)