Смартфоны продолжают отвоевывать все больше места под солнцем не только как инструмент потребления фотографий котиков, но и в качестве рабочего инструмента. Поэтому и спрос на мобильную разработку растет. Принято считать, что тру и кул — это Objective-C/Swift для iOS и Java/Kotlin для Android. Спору нет, тру и кул, но существует большое количество реальных сценариев, в которых использование кросс-платформенных фреймворков более предпочтительно в сравнении с нативными инструментами. Подробнее под катом!
Примечание: мы продолжаем серию публикаций полных версий статей из журнала Хакер. Орфография и пунктуация автора сохранены.
Передаю слово автору.
Одни разработчики ждут от кросс-платформенных фреймворков решения всех своих жизненных проблем, другие же воспринимают их в штыки. В обоих «враждующих лагерях» есть свои заблуждения, вызванные непониманием того, как и что работает. Это подливает масла в огонь, так как вместо технических доводов в ход идут эмоции.
Также среди разработчиков, особенно начинающих, существует множество мифов о кросс-платформенных мобильных фреймворках. В нашей статье мы разберем самые популярные из них. Но для начала посмотрим на мобильную разработку глазами бизнеса, дающего деньги на весь айтишный блек-джек.
Зачем нужны кросс-платформенные инструменты?
Исторически на рынке компьютеров всегда была конкуренция, и каждый производитель предоставлял оптимальный набор так называемых нативных (родных) инструментов для разработки приложений под свои операционные системы и устройства.
Нативные инструменты = предоставляются владельцем экосистемы.
Все остальные признаки «нативности» ВТОРИЧНЫ — поведение и интерфейс приложений, доступ к возможностям ОС, производительность и прочее.
К тому же практически всегда оказывалось, что нативные инструменты несовместимы друг с другом не только на уровне языков разработки, принятых соглашений и архитектур, но и на уровне механизмов работы с операционной системой и библиотеками. В результате для реализации одних и тех же алгоритмов и интерфейсов требовалось написать приложение для нескольких сред на разных языках программирования, а потом его поддерживать из расчета «одна команда на платформу». При этом возможности и внешний вид приложений на разных платформах практически всегда идентичны на 90%. Сравни ради интереса реализацию любимых программ для iOS и Android.
Второй важный момент — наличие необходимых знаний и опыта внутри команды: если их нет, то потребуется время на обучение.
Для того чтобы решить обе эти проблемы, на рынке уже давно появились инструменты кросс-платформенной разработки (не только mobile), предлагающие:
— максимизировать общую базу кода на едином языке программирования, чтобы продукт было проще разрабатывать и поддерживать;
— использовать существующие компетенции и специалистов для реализации приложений на новых платформах.
Так как языков программирования (и сред) сейчас наплодилось очень много (и специалистов, владеющих этими языками), то и инструментов для кросс-платформенной разработки существует изрядное количество. Мы в качестве примера остановимся на популярных в наших краях PhoneGap, Xamarin, React Native и Qt.
Теперь можно поговорить и о мифах.
Миф 1. Магия
Самый частый миф, будоражащий умы начинающих девелоперов, связан с верой в сверхалгоритмы (и сверхпрограммистов, их создавших), которые волшебным образом превращают кросс-платформенные приложения в нативные. Что-то в духе «преобразования кода JavaScript в Swift и дальнейшая компиляция уже Swift-приложения». Этот миф подогревают и сами разработчики кросс-платформенных инструментов, обещая на выходе создание «нативных приложений». И не то чтобы кто-то здесь лукавил, но богатая фантазия и непонимание базовых механизмов иногда наводят разработчиков на мысли о шаманских приемах.
Главный принцип, лежащий в основе кросс-платформенных решений, — разделение кода на две части:
- кросс-платформенную, живущую в виртуальном окружении и имеющую ограниченный доступ к возможностям целевой платформы через специальный мост;
- нативную, которая обеспечивает инициализацию приложения, управление жизненным циклом ключевых объектов и имеет полный доступ к системным API.
Для того чтобы связывать между собой мир «нативный» и мир «кросс-платформенный», необходимо использовать специальный **мост (bridge)**, именно он и определяет возможности и ограничения кросс-платформенных фреймворков.
При использовании bridge производительность всегда снижается за счет преобразования данных между «мирами», а также конвертации вызовов API и библиотек.
Итак, все кросс-платформенные приложения обязаны иметь нативную часть, иначе операционная система просто не сможет их запустить. Поэтому давай рассмотрим подробнее, какие системные API и механизмы предоставляются самими iOS, Android и Windows. Переходим к следующему мифу.
Миф 2. Ненативно!
Итак, у нас есть кросс-платформенная часть приложения, живущая в виртуальном окружении и взаимодействующая с операционной системой через инфраструктуру фреймворка и мост.
Все операционные системы: iOS, Android и Windows UWP — предоставляют доступ к следующим подсистемам (наборы системных API):
- WebView (встроенный в приложение веб-браузер) используется в гибридных приложениях на базе PhoneGap и фактически выступает средой выполнения локального веб-сайта;
- JavaScript-движки используются в React Native и аналогах для быстрого выполнения JS-кода и обмена данными между Native и JS;
- OpenGL ES (или DirectX) используется в игровых движках и приложениях на Qt/QML или аналогах для отрисовки интерфейса;
- UI-подсистема отвечает за нативный пользовательский интерфейс приложения, что актуально для React Native и Xamarin.
Кросс-платформенные приложения имеют нативную часть и такой же полный доступ к системным API, что и «нативные» приложения. Разница в том, что вызов системного метода идет через мост и инфраструктуру фреймворка:
WebView — приложение живет в своем веб-браузере по аналогии с одностраничным веб-сайтом. Нет доступа к нативным контролам (кнопки, списки и прочее), все основано на HTML/CSS/JavaScript. С другой стороны, веб-разработчик почувствует себя как рыба в воде.
JavaScript-движки стали популярны относительно недавно, так как в iOS подобный механизм был добавлен только в версии 7.0. Из особенностей стоит учитывать необходимость сериализации в JSON сложных структур данных, передаваемых между средами JavaScript и Native. Если коротко описать подобный класс решений, то в JavaScript-среде выполняется JS-код, управляющий нативным приложением.
OpenGL ES и DirectX являются подсистемами низкого уровня и используются для отрисовки пользовательского интерфейса в играх и, например, Qt/QML. То есть при использовании OpenGL/DirectX разработчики сами рисуют контролы и анимации, которые могут быть лишь похожи на нативные. С другой стороны, это подсистема низкого уровня с очень высокой производительностью, поэтому она используется и в кросс-платформенных игровых движках.
Все кросс-платформенные приложения имеют нативную часть, а следовательно, потенциально такой же полный доступ к системным API, что и «нативные». Также кросс-платформенные приложения собираются и упаковываются «нативными» инструментами в «нативные» установочные пакеты. Ключевой вопрос — как организовано взаимодействие между кросс-платформенной частью и нативной. Например, внутри WebView или с помощью Open GL ES / DirectX нет возможности создать пользовательский интерфейс с полностью нативным look’n’feel, но при этом есть полный доступ к GPS, Push-уведомлениям и другой функциональности. А код на JavaScript или C# вполне свободно может управлять нативным приложением и его поведением, обеспечивая полностью нативный look’n’feel.
Если резюмировать — то да, «ненативно» с точки зрения используемых инструментов разработки (не от Apple, Google). Но приложение может быть полностью нативным с точки зрения доступа к системным API и обеспечивать полностью нативный внешний вид и поведение. А мы движемся к следующему мифу.
Миф 3. Костыль на костыле
Здесь стоит понимать, что нативные API по умолчанию костылями не считаются (хотя и здесь есть разные мнения), поэтому все негодование направлено на кросс-платформенную часть. Очевидно, что исполняющую среду (например, WebView, JavaScript-движок или Mono) костылем тоже назвать сложно — взрослые зрелые решения с длительной историей.
Похоже, что костылем называют то, как кросс-платформенная часть интегрируется с нативной. Чтобы лучше понять, как работают различные фреймворки, мы на примере PhoneGap, Xamarin, Qt и React Native рассмотрим те механизмы операционных систем, которые используются для связывания кросс-платформенной и «нативной» частей.
Начнем мы с PhoneGap. Ниже представлена верхнеуровневая архитектура приложения на базе этого фреймворка.
Приложение на PhoneGap — это по факту нативное приложение, которое в качестве единственного UI-контрола отображает WebView. Именно через него и идет взаимодействие с нативной частью. Все стандартные WebView в iOS, Android и Windows UWP поддерживают возможность добавить свои нативные обработчики для JS-свойств и методов. При этом JS-код живет в своей изолированной среде и ничего не знает о нативной части — просто дергает нужные JS-методы или меняет нужные JS-свойства. Все внутри стандартного вебовского DOM, в который просто добавляются новые элементы, связанные с нативной реализацией.
Далее рассмотрим React Native.
При создании приложений на React Native разработчику практически всегда будет необходимо реализовывать нативную часть на Objective-C, Java или C#, а само управление нативным приложением будет идти из JavaScript. По факту JavaScript-движок — это элемент WebView, который доступен отдельно. Взаимодействие идет через такой же JS-мост, как и в случае с PhoneGap. Однако в React Native JS-код управляет не вебовским DOM-деревом, а нативным приложением.
Необходимо учитывать, что из-за ограничений iOS (нет возможности реализовать JIT) код JavaScript на лету интерпретируется, а не компилируется. В целом это не особо сказывается на производительности в реальных приложениях, но помнить об этом стоит.
Теперь рассмотрим классический Xamarin.iOS и Xamarin.Android, так как Xamarin.Forms (поддерживающий Windows UWP) — это надстройка над ними.
Xamarin использует библиотеку Mono для взаимодействия с целевой операционной системой, которая позволяет вызывать нативный код с помощью механизма [P/Invoke](https://en.wikipedia.org/wiki/Platform_Invocation_Services). Он же задействуется и для общения с нативными API в iOS/Android. То есть для всех публичных нативных API-методов создаются обертки на C#, которые, в свою очередь, вызывают системные API. Таким образом, из Xamarin-приложения можно обращаться ко всем системным API.
И в завершение рассмотрим Qt, так как о нем появляется много вопросов от опытных разработчиков.
Qt — «вещь в себе», в этом есть и плюсы, и ограничения. Библиотеки Qt просто подключаются к системным API на C++, которые есть во всех операционных системах. Для отрисовки пользовательского интерфейса используются механизмы низкого уровня, но свой графический движок, поддерживающий стилизации «под нативку». При этом на Android приходится обращаться к Java API через специальный мост (JNI bridge), а для Windows UWP использовать конвертер вызовов Open GL ES в DirectX, так как Open GL недоступен для UWP.
Подведем итог: все кросс-платформенные фреймворки используют стандартные нативные возможности операционных систем, являются зрелыми, создаются опытными командами и сообществом open source при поддержке гигантов IT-индустрии. И наконец, пришло время для самого «сильного» аргумента.
Миф 4. Медленно
Важный козырь, которым любят пользоваться в спорах о кросс-платформенных фреймворках, — низкая производительность. Опять же, смотря что с чем сравнивать и в каких попугаях считать.
Напомним, что особенность кросс-платформенных приложений заключается в параллельном существовании двух миров, связанных мостом:
- PhoneGap: HTML/JS и Native Java / Objective-C / C#;
- React Native: JS и Native Java / Objective-C / C#;
- Xamarin: Mono и Native Java / Objective-C;
- Qt: С++ и Native Java / Objective-C.
Таким образом, при сравнении производительности надо учитывать скорость работы:
- кросс-платформенной части;
- нативной части;
- моста.
Если набрать в поисковике, например, react native vs swift performance, то можно посмотреть множество различных тестов, и во многих из них отмечается, что производительность резко падает при активном использовании моста, включая активное манипулирование UI из кросс-платформенного кода. Для Xamarin ситуация выглядит таким же образом — кросс-платформенная часть очень быстра и сопоставима с нативной в обработке данных, однако при использовании моста может падать производительность. Qt вообще работает на уровне С++, который быстр сам по себе. Если же рассматривать решения на базе PhoneGap, то здесь производительность будет сильно зависеть от WebView, но все же не следует активно менять UI в JavaScript-коде или проводить научные вычисления.
Медленно? Да, возможны падения производительности при неумелом взаимодействии с операционной системой через мост. Однако сами по себе кросс-платформенные миры такие же быстрые, как и нативные.
Заключение
Сегодня мы рассмотрели устройство популярных кросс-платформенных мобильных фреймворков и разобрали основные мифы, которыми они окружены.
Оставайся на связи и пиши вопросы в комментариях!
Об авторе
Вячеслав Черников — руководитель отдела разработки компании Binwell. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone.
Статьи Вячеслава вы также можете прочитать в блоге на Medium.
Напоминаем, что это полная версия статьи из журнала Хакер.
Автор: Schvepsss