Инфраструктура разработки приложения на PhoneGap для iOS и Android

Вступление

Сегодня я расскажу вам о нашем опыте разработки кросс-платформенных мобильных приложений на основе PhoneGap/Cordova. В проекте используются такие технологии как HTML5, CSS3 и Angular.js. Grunt применяется в качестве task manager’а, позволяющего упростить и ускорить выполнение рутинных задач. Все сборки компилируются локально во время процесса разработки.

Когда версия готова к этапу тестирования, она компилируется с помощью PhoneGap Build и загружается на Ubertesters — сервис дистрибуции мобильных приложений, который позволяет проводить тестирование быстро и эффективно на всех подключённых устройствах. Приложение поддерживает минимум iOS 6 и Android 4.1 (87.5 % доля рынка).

Настройка локальной сборки и структура проекта

Для локального тестирования приложения необходимо установить инструменты для PhoneGap/Cordova. Важно убедиться, что NodeJS уже установлен, открыть командную строку или Cygwin и выполнить следующее:

npm install -g phonegap

После завершения установки пустой проект может быть создан и скомпилирован для требуемой платформы:

phonegap create my-app
cd my-app
phonegap platform add android
phonegap run android

Внимание: phonegap = cordova. Можно использовать cordova вместо phonegap при написании команд:

cordova run android

Сборка для iOS может осуществляться только под Mac OS X (согласно условиям лицензионного соглашения Apple), не существует способа запустить сборку для iOS под Windows.

Платформа Android требует наличия SDK Tools, его можно скачать по ссылке (пакет SDK Tools Only внизу страницы). Сразу после установки необходимо проверить в Android SDK Manager, что Android 5.0 (API level 21) был установлен успешно. Android 5.0 используется как target для текущей версии Phonegap/Cordova при локальной сборке.

Структура приложения

Файлы и папки

  • platforms — включает в себя раздельный код под каждую из используемых платформ, компилируется каждый раз, когда выполняется сборка;
  • plugins — это плагины, используемые в приложении. Плагины описываются подробнее далее;
  • www — веб-приложение на основе HTML5/Angular.js, PhoneGap/Cordova отображает при помощи системного компонента Web View;
  • config.xml — файл должен находиться в корневой папке проекта при локальной сборке. PhoneGap Build требует наличия файла config.xml в /www. Файл настроек является важной частью проекта на основе PhoneGap. Он включает в себя ссылки на ресурсы приложения, устанавливает необходимые разрешения и настраивает параметры для каждой из платформ (например, поведение status bar’а). Application (bundle) id и информация об издателе тоже должна быть указана в config.xml

Plugins

Плагин — это пакет, который позволяет автоматически внедрить native код в приложение и управлять native методами из Cordova Web View. Все основные функции PhoneGap/Cordova API реализованы при помощи плагинов, которые предоставляют доступ к возможностям и функциям устройства и платформы, которые недоступны обычному веб-приложению: сканирование QR-кода, NFC, push-уведомления и даже Touch ID для iOS.

Существует реестр PhoneGap плагинов. Очень важно использовать плагины, совместимые с PhoneGap Build, иначе сборка будет возможна только локально.

Для добавления плагина в проект при локальной сборке используют команду plugin add из корневой папки проекта. Аргументом к этой команде является URL Git-репозитория, содержащего код плагина:

cordova plugin add https://github.com/phonegap-build/PushPlugin.git

PhoneGap Build требует указания id в файле config.xml для каждого плагина (что-то вроде зависимостей). Указывать конкретную версию не обязательно, но желательно, так как различные сборки плагина могут быть несовместимы, и придётся долго выяснять причину внезапно появившихся ошибок. Пример со страницы плагина:

<gap:plugin name="com.phonegap.plugins.pushplugin" version="2.4.0" />

Соответствующие .js-файлы должны быть импортированы на HTML-странице:

<script type="text/javascript" charset="utf-8" src="PushNotification.js"></script> 

Все плагины обычно имеют документацию (пускай и не всегда подробную) на GitHub.

Поскольку мы используем Angular.js framework, а не чистый JavaScript, дополнительный компонент требуется для того, чтобы «обернуть» плагины. Он называется ngCordova.

ngCordova

ngCordova — это коллекция из более чем 60 Angular.js-расширений на основе Cordova API, которые позволяют упростить создание, тестирование и сборку мобильных приложений PhoneGap/Cordova. Проект поддерживается и развивается командой Ionic Framework. ngCordova предоставляет простые Angular.js wrapper’ы для самых популярных и часто используемых плагинов PhoneGap/Cordova и позволяет сделать фото, отсканировать QR-код, загрузить файл, включить вспышку, получить текущую локацию, а также многое другое с помощью нескольких строк кода.

Для локальной сборки всё так же необходимо добавить плагин с помощью команды:

cordova plugin add https://github.com/phonegap-build/PushPlugin.git

Для PhoneGap Build требуется ссылка на id плагина в config.xml:

<gap:plugin name="com.phonegap.plugins.pushplugin" version="2.4.0" />

ng-cordova.js импортируется из пакета ngCordova, script-тег должен быть расположен после импорта angular.js, иначе появятся ошибки, не имеющие отношения к проекту:

<script src="scripts/angular.js"></script>
<script src="scripts/ng-cordova.js"></script>

Важно не забыть добавить зависимость ngCordova к модулю:

angular.module('application', [
    'ngCordova'
])

Каждый ngCordova wrapper имеет свою документацию. Плагин для push-уведомлений и QR-сканер были использованы и протестированы в нашем проекте. Сканер QR-кодов (Barcode Scanner) был интегрирован без особых проблем, можно даже сказать «out-of-the-box», но настройка push-плагина заняла довольно длительное время в связи с недостатком документации — информацию приходилось собирать, просматривая огромный список issues на GitHub’е.

Push-уведомления для Android (Google Cloud Messaging)

Краткая документация доступна на странице плагина ngCordova.

Важно! Payload-часть push-уведомления, отправляемая с сервера, должна следовать определённому формату, иначе возможны проблемы с автоматическим появлением уведомлений в системном трее.

Было обнаружено, что объект data должен обязательно иметь поле «message» (в качестве текста уведомления) и «title» (если отсутствует, то заголовок уведомления будет пустым). Поле «uri» необходимо для того, чтобы указать действие, которое совершается при открытии уведомления.

{
    "data": {
        "message": "New message. You have 5 unread messages",
        "title": "Your Application",
        "uri": "http://www.yourapp.com/messages"
    }
}

Также важно отметить, что notificationReceived listener срабатывает тогда, когда приложение находится в foreground, а также в случае, когда пользователь открывает уведомление (переход из background’а в foreground). Push-уведомления появляются автоматически в background’е и продолжают приходить даже после перезагрузки системы.

Uri может быть получен в условии ‘message’ структуры switch case listener’а.

$rootScope.$on('$cordovaPush:notificationReceived', function(event, notification) {
    console.log(notification);
    switch (notification.event) {
        case 'registered':
            if (notification.regid.length > 0) {
                registerOnServer(notification.regid);
            }
            break;
        case 'message':
            console.log('message!');
            utils.goToApiUrl(notification.payload.uri);
            break;
        case 'error':
            console.log('GCM error: ' + notification.msg);
            break;
        default:
            console.log('An unknown GCM event has occurred: ' + notification.event);
            break;
    }
});

Push-уведомления на iOS

Краткая документация доступна на странице плагина ngCordova.

В момент, когда разрабатывалось приложение, часть бэкенда, которая отвечает за отправку Apple Push-уведомлений (APNS) была не готова. Решили использовать временное решение для тестирования и отладки. Node Push Server был выбран первоначально (т.к. по нему имелись рекомендации в некоторых мануалах по Phonegap/Cordova), но в ходе тестирования выяснилось, что в нём отсутствует поддержка кастомных полей для push-уведомлений на iOS (поддерживаются только badge, alert и sound, а нам нужно передавать uri для открытия определённой страницы), и из-за проблем с форматом некорректно работает на iOS 6 и iOS 7 (счётчик на иконке не отображается).

Существует несколько распространённых решений на рынке, но мы выбрали платформу Parse.com для наших тестовых уведомлений на iOS, т.к. условия Free Plan вполне достаточны для процесса разработки и тестирования.

Чтобы настроить уведомления: первым делом необходимо зарегистрироваться и зайти в настройки, раздел “Settings”, затем перейти в секцию “Push” для того, чтобы включить обработку push-уведомлений и загрузить требуемый сертификат (*.p12). Вы можете узнать больше о работе Apple Push Notification Service здесь.

Пример кода на основе документации ngCordova

function registerInCordova() {
    var iosConfig = {
        "badge": true,
        "sound": true,
        "alert": true,
    };
    var authHeader = {
        headers: {
            'X-Parse-Application-Id': 'YOUR_APPLICATION_ID',
            'X-Parse-REST-API-Key': 'YOUR_REST_API_KEY',
            'Content-Type': 'application/json'
        }
    };
    $cordovaPush.register(iosConfig).then(function(deviceToken) {
        // Success -- send deviceToken to server, and store for future use
        console.log("deviceToken: " + deviceToken);
        $http.post("https://api.parse.com/1/installations", {
            "deviceType": "ios",
            "deviceToken": deviceToken,
            "channels": ["ALL"]
        });
    }, function(err) {
        console.log("Registration error: " + err);
    });
}
</spoiler>
Важно не забыть поменять 'YOUR_APPLICATION_ID' и ‘YOUR_REST_API_KEY' на значения из секции “Settings” -> “Keys”. Код listener’а выполняется, когда приложение запущено в foreground или переходит из background’а в foreground (после нажатия на push уведомление).
<spoiler title="Код listener'а">
<source lang="javascript">
function registerForegroundListener() {
    $rootScope.$on('$cordovaPush:notificationReceived', function(event, notification) {
        console.log("Notification: " + JSON.stringify(notification));
        if (notification.sound) {
            var snd = new Media(event.sound);
            snd.play();
        }
        if (notification.badge) {
            $cordovaPush.setBadgeNumber(notification.badge).then(function(result) {
                console.log("Badge set successfully");
            }, function(err) {
                console.log("Error on badge setting: " + err);
            });
        }
        if (notification.uri) {
            utils.goToApiUrl(notification.uri);
        }
    });
}

Для того, чтобы отправить push-уведомление через parse.com на все зарегистрированные устройства, необходимо выполнить HTTP POST-запрос

curl -X POST \
  -H "X-Parse-Application-Id: YOUR_APPLICATION_ID" \
  -H "X-Parse-REST-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "channels": [
          ""
        ],
        "data": {
            "alert": "New Messages",
            "badge": 3,
            "uri": "api/messages"
        }      }' \
  https://api.parse.com/1/push  

PhoneGap Build

— Что такое PhoneGap Build?
PhoneGap Build — это бесплатный облачный сервис, построенный на основе PhoneGap/Cordova, который позволяет собирать кросс-платформенные мобильные приложения.

— Зачем нам PhoneGap Build?
PhoneGap Build позволяет делать сборки для iOS, Android и Windows Phone одновременно, без необходимости устанавливать какие-либо SDK tools (конечно, в этом есть доля лукавства — при разработке всё равно лучше делать сборку локально, хотя бы на Android, перед отправкой на тестирование). Но что более важно, этот сервис позволяет делать сборки для iOS в облаке без наличия Mac.

PhoneGap Build имеет собственный REST API, который может использоваться для автоматизации создания и обновления проектов, запуска сборки под iOS, Android и Windows Phone (можно выбирать платформу). Если вы используете GitHub, есть возможность настроить сервис для автоматической сборки после каждого коммита в репозитории. REST API позволяет авторизовать каждый HTTP-запрос к серверу с помощью токена, вместо того чтобы передавать имя пользователя и пароль в header’е.

Для использования PhoneGap Build нужно сделать настройки:
— После регистрации заходим в «create new app». Необходимо загрузить упакованное в zip содержимое папки www, config.xml должен быть внутри. Доступ к приватным GitHub-репозиториям доступен только в платной версии аккаунта).
— Далее нужно перейти в настройки аккаунта: кликнуть по иконке в правом углу, “Edit account” -> “Signing keys”, добавить требуемые сертификаты и ключи (сборка для iOS прервётся при отсутствии сертификата *.p12 и профиля, сборка для Android будет выполнена в режиме AdHoc, если ключ отсутствует).

После того как установочные пакеты для всех требуемых платформ сгенерированы с помощью PhoneGap Build, можно вручную загрузить их на сервис дистрибуции. Существует множество альтернативных вариантов: HockeyApp, TestFlight, TestFairy, но далее мы рассмотрим Ubertesters и изучим, как произвести автоматизацию сборки и дистрибуции с возможностью интеграции Ubertesters Upload API.

Ubertesters

— Почему Ubertesters?
Особенности и преимущества этого сервиса обсуждаются в этой статье.

Сервис Ubertesters предоставляет SDK для всех основных платформ (даже Phonegap/Cordova). Upload API, как уже было сказано, позволяет использовать continuous integration и автоматизировать дистрибуцию.

Чтобы открыть доступ к Upload API, необходимо перейти на страницу профиля.

Документация доступна по ссылке.

Установочные пакеты (ipa/apk) могут быть загружены с помощью curl

curl
    -X POST
    http://beta.ubertesters.com/api/client/upload_build.json
    -H "X-UbertestersApiKey:PERSONAL_API_KEY"
    -F "file=@upload.ipa"
    -F "title=build title"
    -F "notes=build notes"
    -F "status=in_progress"
    -F "stop_previous=true"

Чтобы разрешить автоматическую дистрибуцию на устройства команды тестировщиков и разработчиков, необходимо создать группу дистрибуции по умолчанию: “Administration” -> “Distribution Groups” -> “Add distribution group”, выбрать чекбокс “Default group”. Так как в нашем случае поддерживаются 2 платформы, нужно не забыть совершить операцию для каждого из проектов (iOS и Android).

Автоматизация задач

Существует несколько способов автоматизации процесса сборки для приложений на PhoneGap/Cordova. В качестве task manager’a можно использовать Grunt, Ant, Maven а в качестве среды для continuous integration — Jenkins CI или Team City.

Конфигурация Grunt

Grunt требует наличия двух файлов: package.json (он определяет зависимости и базовые настройки) и gruntfile.js (конфигурация задач).

Для начала нужно установить grunt-cli:

npm install -g grunt-cli

Можно использовать следующие настройки для package.json

{
    "name": "Your application",
    "version":"1.0.0",
    "description": "Your App",
    "main": "index.html",
    "author": "",
    "licenses": "",
    "devDependencies": {
        "form-data": "^0.2.0",
        "fs": "0.0.2",
        "grunt": "0.4.5",
        "grunt-contrib-compress": "0.9.1",
        "grunt-http": "^1.6.0",
        "grunt-magikarp": "^0.2.5",
        "grunt-modify-json": "^0.1.1",
        "grunt-xmlpoke": "^0.8.0",
        "needle": "",
        "read": "~1.0.4"
    }
}

Зависимости, заданные в package.json, могут быть удовлетворены с помощью npm install. Все необходимые модули будут установлены в /node_modules. Но также потребуется установить grunt-phonegap-build вручную:

npm install grunt-phonegap-build

Несколько параметров необходимо установить перед использованием:

  • var PHONEGAP_API_ID = «YOUR_PHONEGAP_API_ID»;
  • var PHONEGAP_TOKEN = «YOUR_PHONEGAP_TOKEN»;
  • var UBERTESTERS_API_KEY = «YOUR_UBERTESTERS_API_KEY»;
  • В конфигурации задачи phonegap-build должен быть указан пароль для сертификата iOS.

Далее мы рассмотрим все используемые в проекте задачи task manager’а Grunt в порядке использования в скрипте.

Список задач Grunt

1) http:phonegap_build_version

Мы получаем текущую версию из сервиса PhoneGap Build с помощью HTTP GET запроса:

http://build.phonegap.com/api/v1/apps/' + PHONEGAP_API_ID + '?auth_token=' + PHONEGAP_TOKEN

Методы и документация PhoneGap Build Read API доступны по ссылке.

2) readCurrentVersion

Результат http:phonegap_build_version возвращается в формате JSON и сохраняется по указанному пути:

ubertesters_response/phonegap_app.json

В данный момент скрипт имеет баг: phonegap_app.json должен быть создан вручную до запуска (можно просто создать пустой JSON file). Текущая версия передаётся как параметр к modify_json.

3) modify_json

Обновляет значение текущей версии в package.json.

4) magikarp

Magikarp инкрементирует текущую версию в package.json. Опции доступны: major, minor и build.

5) readNextVersion

Инкрементированная версия из package.json передаётся в xmlpoke.

6) xmlpoke

Изменяет версию в файле www/config.xml.

7) compress

Сжимает содержимое папки www (не саму папку, это важно!), исключая ненужные файлы. Сжатый zip-файл сохраняется по указанному пути target/phonegapp.zip

8) phonegap-build:debug

В данный момент debug и release ничем не различаются (release-ключ ещё не сгенерирован для Android). Сжатый zip-файл отправляется на сервис PhoneGap Build. Задача завершается только после сборки (iOS/Android) и сохранения установочных пакетов (ipa/apk).

Методы и документация PhoneGap Build Write API methods доступны по ссылке.

9) http:ubertesters_ios

Эта задача загружает ipa-сборку на Ubertesters с помощью Upload API. Здесь можно использовать любой сервис дистрибуции мобильных приложений, если он имеет REST API.

10) http:ubertesters_android

Та же задача, но для Android apk.

11) PROFIT!

Grunt запускается локально с помощью команды ‘grunt’ или на сконфигурированном build-сервере (TeamCity в нашем случае). Также есть возможность настроить TeamCity, чтобы вызывать процесс сборки и дистрибуции при каждом коммите.

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

Полезные ссылки

Вам также может понравиться

Blog Post Разработка системы тестирования SQL запросов. Часть 2
29 сентября, 2021
Продолжение истории о фреймворке, разработанном с целью автоматизации и упрощения процесса тестирования сложных SQL-запросов на крупном проекте.
Blog Post Техники обработки отказов сервиса в микросервисных архитектурах
07 сентября, 2021
Эта статья может быть полезна для тех, кто пострадал от нестабильной работы внешних API: какие бывают стратегии обработки отказов и какой путь избрали мы.
Blog Post Cоздаём безопасное веб-приложение
17 августа, 2021
Эта статья — своего рода ‘cheat sheet’ для веб-разработчика. Она даёт представление о «программе-минимум» для создания веб-приложения, защищённого от самых распространённых угроз.