Делаем кросс-платформенное приложение на Cordova

Данная статья не претендует на истину в последней инстанци, мы лишь расскажем о своем опыте разработки кросс-платформенного приложения.

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

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

Данный набор инструментов позволил нам довольно легко переключить команду frontend разработки на создание мобильного приложения под платформы Android и iOS, с минимальными затратами с нашей стороны. Также Cordova не требует использования специальных средств для разработки и сборки приложения, кроме SDK необходимой платформы и командной строки, что позволяет использовать любую удобную IDE, а также широкое сообщество разработчиков и большое количество доступных плагинов.

Выбор фреймворков для разработки

Основным языком программирования в проекте был выбран CoffeeScript. Это легковесный язык программирования, который транслируется в JavaScript и добавляет «синтаксический сахар» для упрощения использования ряда часто встречающихся конструкций, таких как перебор массивов и создание классов. CoffeeScript позволяет существенно уменьшить количество исходного кода и повысить его читаемость, что отлично сказывается на скорости разработки и поддержке конечного программного продукта.

Учитывая современные веяния web разработки и специфику разработки кроссплатформенных приложений, было принято решение разрабатывать наше приложение по архитектуре single page application. Данная архитектура подразумевает, что всё приложение представляет собой единственную html страницу, а все переключения экранов, форм и обработка данных происходят силами JavaScript без перезагрузки страницы. Для решения данной задачи был выбран фреймворк ChaplinJs. Данный фреймворк представляет собой надстройку над другим популярным программным решением для организации SPA приложений BackboneJS, позволяя организовывать весь код по популярной архитектуре MVC. Также ChaplinJS добавляет собственную гибкую структуру приложения и взаимодействия модулей, которая позволяет обходить ограничения оригинала, сохраняя при этом его гибкость и расширяемость. Кроме того, ChaplinJS имеет отличную поддержку CoffeeScript.

Саму вёрстку мы осуществляли с помощью предпроцессора SASS, который позволил упростить таблицы стилей и разбить их на отдельные функциональные модули, которые легко редактировать и поддерживать. Перед публикацией SASS собирает из модулей один стандартный css файл, который используется приложением.

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

Поскольку ChaplinJs подразумевает разбиение приложения на отдельные функциональные модули, и количество таких модулей растёт в зависимости от сложности приложения, необходимо средство для их организации и своевременной загрузки. Данные задачи можно решить благодаря использованию подхода Asynchronous Module Definition(AMD), для чего была использована библиотека RequireJS. Данная библиотека позволяет описать модули приложения по методологии AMD и описать зависимости между модулями, благодаря чему на основной странице приложения достаточно подключить только один скрипт с настройкой библиотеки. Все остальные модули будут подгружаться только тогда, когда они необходимы. Кроме того библиотека следить за коллизиями в зависимостях между модулями, и необходимый модуль уже был загружен, то повторно загружаться он уже не будет.

На серверной стороне у нас уже был реализован RESTAPI интерфейс для работы нашего сервиса. Используя его, мы смогли довольно легко настроить синхронизацию данных с сервером, благодаря встроенному механизму синхронизации моделей и коллекций в BackboneJS. Фреймворк позволяет указать путь до метода на сервере, через который осуществляется работа с моделью, при описании класса данной модели. Далее об отправке необходимых запросов и синхронизации данных модели с сервером позаботиться сам фреймворк, достаточно только вызвать необходимый соответствующий метод. Дополнительные запросы до сервера, которые нельзя было решить с помощью механизма синхронизации данных фреймворка, были реализованы с помощью стандартных Ajax запросов библиотеки JQuery.

Одним из требований к нашему приложению было то, что оно должно не только позволять пользователю оставить заказ в ресторане, но и информировать его о том, что заказ готов. Поэтому не только приложение должно было отправлять запросы на сервер, но и сервер должен был иметь возможность уведомлять приложение о статусе заказа. Для решения данной проблемы было принято решение использовать механизм PUSH уведомлений. PUSH уведомления – это краткие всплывающие уведомления, которые появляются на экране мобильного устройства и сообщают о важных событиях и обновлениях. Но кроме этого их можно использовать для передачи дополнительных данных в приложение, даже если приложение было свёрнуто или закрыто. Для отправки и приёма таких уведомлений мы использовали довольно известный сервис Google Cloud Messaging(GCM). Сам принцип довольно прост:

  • Приложение на устройстве при запуске регистрируется в системе GCM и получает специальный идентификатор, который сохраняет в памяти и отправляет на сервер с каждым заказом, а также устанавливает функцию для обработки полученных уведомлений
  • Сервер при изменении статуса заказа отправляет запрос на сервер GCM, указывая в параметрах идентификатор, полученный от приложения, и дополнительные данные
  • Сервер GCM формирует PUSH уведомление о отправляет его на конкретное устройство

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

Весь код приложения храниться в едином репозитории и работать с ним одновременно могут несколько разработчиков. Для хранения и синхронизации изменений используется система контроля версий Git, которая позволяет вести параллельную разработку одного приложения в нескольких ветках и синхронизировать изменения в один репозитарий. Учитывая большой набор используемых при разработке инструментов и библиотек, разумно автоматизировать процесс их установки. Для этой цели существуют специальные инструменты, называемые пакетными менеджерами. В нашем приложении мы использовали два самых популярных пакетных менеджера: npm для загрузки основных инструментов разработки, таких как Cordova и средства сборки приложения, а также bower для загрузки клиентских JavaScript библиотек.

NPM — это пакетный менеджер с самой большой коллекцией библиотек с открытым исходным кодом в мире. Поскольку данный менеджер является частью проекта NodeJs, дистрибутив данного проекта должен быть установлен на компьютер разработчика. Установка всех пакетов через npm происходит через командную строку по имени пакета:

$npm install <имя пакета>

Также npm позволяет сохранить список всех необходимых компонентов в один файл конфигурации и установить их все одной командой:

$npm install

Bower полностью аналогичен npm, но более ориентирован на подключение клиентских JavaScript библиотек. Он также позволяет объединять список библиотек в один файл конфигурации и устанавливать их разом с помощью команды

$bower install

Сам bower устанавливается через npm с помощью команды:

$npm install bower -g

Набор инструментов Cordova устанавливается также через npm командой

$npm install cordova -g

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

$cordova plugin add <имя плагина>

Сборка

Такие инструменты как CoffeeScript и Sass требуют компиляции в понятные для браузера форматы js и css. Кроме того перед сборкой для устройства файлы проекта имеет смысл оптимизировать проект объединив и минимизировав файлы стилей и скриптов, оптимизировать графические ресурсы приложения, очистить проект от лишних файлов, созданных для разработки и скаченных менеджерами пакетов. Проводить данные операции необходимо постоянно, поэтому проводить каждую операцию вручную непростительная трата ресурсов. Для решения данной проблемы существуют специальные инструменты для сборки JavaScript проектов, которые позволяют автоматизировать каждый этап за счёт использования специальных задач. В нашем проекте мы использовали сборщик проектов под названием Grunt.

Grunt устанавливается с помощью npm и может расширять свою функциональность с помощью плагинов, которые также подключаются через npm. Данный сборщик позволяет задать каждый этап сборки в виде задач, которые также можно объединить в одну общую задачу и выполнить одной командой из консоли, полностью автоматизировав весь процесс сборки. Сами задачи описываются внутри файла конфигурации gruntfile.js, который лежит внутри рабочего каталога.

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

  • Компиляции CoffeeScript файлов
  • Компиляция SASS файлов
  • Объединение js и css исходников в единые файлы
  • Минификация объединённых файлов
  • Оптимизация RequireJs скриптов
  • Перенос готовых файлов проекта из рабочей директории в отдельную директорию для продакшена

Кроме того Grunt позволяет не только собирать проект, но и запускать любые команды для командной строки, что позволяет с помощью одной команды не только собрать файлы проекта но и запустить сборку приложения под мобильные платформы.

Сборка приложения под мобильные платформы производится с помощью набора инструментов Cordova через командную строку. Также для сборки под Android необходимо, чтобы на компьютере было установлено Android SDK. Сборку и отладку приложения под iOS можно проводить только на MacOS, также должна быть установлена последняя версия xcode.

После установки инструментов Cordova через npm, необходимо скачать и установить необходимую платформу для сборки. Установка платформы производится командой:

$cordova platform add <имя платформы>

Далее необходимо установить и подключить необходимые плагины. Если плагины уже были установлены, то при установке платформы они подключаться автоматически. Собрать готовое приложение можно командой:

$cordova build <имя платформы>

Облачная сборка приложений

Альтернативой ручной сборки на рабочем компьютере служат облачные сервисы сборки приложений. Благодаря данным сервисам можно не заботиться о наличии необходимого SDK для сборки и собирать приложение одновременно под несколько платформ. Однако скорость сборки будет небольшой, особенно на бесплатных сервисах.

Одним из наиболее популярных сервисов является Adobe PhoneGap Build. Данный сервис может производить сборку под Android, iOS и Windows Phone 8 и имеет бесплатный тариф. Стоит также заметить, что хотя для сборки приложения под iOS через данный сервис не требуется MacOS, для подписи приложения необходим сертификат разработчика, генерация которого возможна только на данной операционной системе.

Для корректной сборки приложения через сервис PhoneGap Build все ресурсы приложения должны быть упакованы в zip архив, в корне которого должен находиться файл настройки config.xml. В данном файле должны быть описаны основные параметры приложения, такие как название, глобальный идентификатор, разрешения и т.д. Кроме того там должны быть прописаны все плагины и их параметры, которые используются в приложении. Плагины подключаются по глобальному имени и должны быть зарегистрированы под ним в глобальном репозитории PhoneGap Build или быть публично доступны через npm. На данный момент репозиторий PhoneGap Build считается устаревшим и рекомендуется использовать исключительно npm. Кроме того можно использовать плагины из открытого git репозитария, но для этого необходимо иметь платный аккаунт.

После того как архив с исходными ресурсами приложения будет загружен в личном кабинете PhoneGap Build, достаточно дождаться пока закончиться сборка и можно скачивать установочные файлы приложения. Если во время сборки произойдёт ошибка, то сервис укажет на это и позволит просмотреть лог файл процесса для определения проблемы. Готовые инсталляционные файлы приложения можно использовать для установки на проверочные устройства или для публикации в магазине приложении.

Деплой и логгирование

Перед публикацией любое приложение необходимо протестировать. Для рассылки готовой сборки на устройства тестеров мы использовали удобный сервис от Microsoft — HockeyApp. Данный сервис позволяет распространять различные сборки и версии приложения между конкретными группами пользователей. Для этого необходимо зарегистрироваться на сайте сервиса и добавить пользователей и тестовые устройства. Далее при загрузке новой сборки приложения в сервис можно указать группу пользователей, которым будет доступна данная версия, и выбрать дополнительные настройки. На тестовых устройствах должно быть установлено приложение-клиент сервиса HockeyApp, через которое осуществляется загрузка и установка сборок на устройство.

Другой замечательной особенностью сервиса является возможность отправлять отчёты о сбоях и отслеживать пользовательские события в приложении. Для этого в само приложение необходимо интегрировать инструменты тестирования сервиса. Кроме того инструменты тестирования позволяют отображать пользовательский интерфейс для сбора обратной связи от тестеров. Все отзывы и метрики затем можно просмотреть в личном кабинете сервиса.

Альтернативным решением для сбора статистики по ошибкам, сбоям и пользовательским событиям в приложении является сервис Crashlytics, который входит в состав набора SDK Fabric. Для работы также требуется завести учётную запись сервиса и встроить в приложение соответствующие инструменты тестирования. Для добавления данного функционала в приложение можно использовать специальный плагин cordova-fabric-plugin.

Данные решения позволяют в автоматическом режиме собирать и отправлять информацию о сбоях приложения и ошибках внутри native кода приложения. Однако Cordova приложения представляют собой упрошенный браузер, внутри которого исполняется JavaScript код. Появление ошибок внутри JavaScript воспринимается приложением и инструментами тестирования как стандартное поведение и не логируются системой в автоматическом режиме, что затрудняет сбор информации о сбоях внутри бизнес логики приложения.

Лучшим вариантом для полноценного сбора статистики по сбоям внутри Cordova приложения является совмещение инструментов логирования сбоев внутри native кода приложения с библиотеками логирования ошибок внутри JavaScript приложений. Одной из таких библиотек является TraceKit, поддерживающая все современные браузеры, включая мобильные. Для работы необходимо подключить библиотеку к приложению и установить обработчик на возникновение ошибки через глобальный объект TraceKit. Внутри данного обработчика можно организовать логирование ошибки, например, с помощью плагина cordova-fabric-plugin, тем самым объединив всё логирование в единую систему.

order-app-small

Подводные камни

Внешние запросы

Начиная с версии платформы 4.0.0, Cordova блокирует все запросы из приложения кроме протокола “file://”. Для того, чтобы разрешить запросы к внешним ресурсам, необходимо добавить в приложение плагин cordova-plugin-whitelist. Данный плагин позволяет задать список доменов, до которых приложение имеет право отправлять запросы. Сам список задаётся в файле конфигурации приложения config.xml и по умолчанию представляет собой тег, который разрешает запросы до любого домена.

Разные форматы push-сообщений

Для приема и обработки PUSH сообщений на клиенте был задействован замечательный плагин phonegap-plugin-push для Apache Cordova. Однако в процессе работы была неприятная особенность — для корректной обработки сообщений необходимо отправлять сообщения для Android и iOS через GCM в различном формате. Для Android все дополнительные данные для сообщения должны содержаться в объекте data, отправляемом к серверу GCM, под iOS в объекте notification. GCM поддерживает также гибридный набор данных в сообщении, но корректная обработка таких уведомлений плагином возможна только если приложение запущено и открыто на устройстве. В противном случае ОС отобразит уведомление в панели уведомлений на устройстве, но не передаст данные в плагин. Для решения данной проблемы мы решили указывать ОС устройства при отправке заказа из приложения и формировать PUSH сообщение для данного устройства на основе данной информации.

Кэширование изображений

Для оптимизации обращений к серверу мы использовали библиотеку imagecache.js (для кеширования изображений на устройстве). Однако «сходу» она не заработала, выяснилось, что для корректной работы библиотеки на мобильных устройствах необходимо также использовать 2 плагина для Cordova: cordova-plugin-file и cordova-plugin-file-transfer.

Открытие окон браузера

В процессе разработки появилась задача принимать платежи в нашем приложении. Выбранная нами платёжная система позволяла принимать платежи через собственный web-интерфейс, открываемый через POST запрос к их серверу. К сожалению, открытие внешних страниц не укладывается в формат SPA приложения. Поэтому для отображения формы платёжной системы мы использовали модифицированную версию плагина ChildBrowser, который позволяет открывать дополнительное окно браузера внутри приложения и подписываться на основные события данного окна. Основываясь на событии смены url внутри платёжного окна, наше приложение обрабатывает базовый ответ платёжной системы при переходе на конечную точку нашего сервера.

В процессе тестирования выяснился любопытный момент в работе ChildBrowser под iOS 9 – если ответ от сервера с кодом 200 не возвращает внутри контент, то переход на данный серверный метод не происходит и событие перехода в плагине не срабатывает. Поэтому если необходимо отлавливать переход внутри ChildBrowser под iOS 9, удостоверьтесь, что серверные методы всегда возвращают контент.

Проблема кроссбраузерности

Пожалуй, самая главная проблема — Cordova приложения исполняются в среде стандартного браузера системы, т.о. внешний вид приложения полностью зависит от возможностей браузерат. Движок браузера в устройствах на iOS поддерживает практически все современные технологии. К сожалению, на Android устройствах браузер начал полноценно поддерживать CSS3 только начиная с версии операционной системы 4.4. На устройствах с более старой версией операционной системы внешний вид приложения мог очень сильно различаться. К счастью существует проект Crosswalk, который позволяет запускать приложение в среде собственного браузера, построенного на движке Google Chromium поддерживающем все современные технологии. Таким образом, мы можем быть уверенны, что на всех устройствах наше приложение будет выглядеть одинаково, и верстать формы, ориентируясь под определённый браузер. Платой за универсальность служить увеличение объёма приложения от 17 мб и выше. Кроме того Crosswalk доступен только на Android устройствах с версией операционной системы не ниже 4.0.

Заключение

Итак, попробуем выделить плюсы и минусы кроссплатформенной разработки. Основными преимуществами кроссплатформенной разработки являются:

  • Возможность использования распространённых инструментов и технологий web программирования. Если в компании уже есть команда для frontend разработки, то она может довольно быстро переключиться на создание мобильного приложения. Нет необходимости изучать Objective C, Swift или Java.
  • Единый код для всех платформ снижает стоимость затрат на разработку и поддержку.
  • Единый дизайн для всех устройств. Нет необходимости создавать отдельные дизайны под каждую платформу, т.к., в большинстве случаев, создаваемый интерфейс почти не зависит от программных особенностей  конкретной платформы.

Минусы:

  • Единый дизайн не учитывает особенности платформы, что может привести к трудностям с освоением приложения пользователями, которые привыкли к особенностям интерфейса нативных приложений.
  • Возможны сложности прохождения модерации в App Store, т.к. у Apple достаточно жесткие требования по внешнему виду приложения.
  • Бесконечные проблемы совместимости библиотек и плагинов