Я начинаю публиковать новую серию постов о Qt на Android.
Первая часть о том, как всё начиналось, как это работает, текущий статус, чего стоит ждать от Qt 5.2 и мои планы на Qt 5.3. В следующей части я расскажу подробнее о разработке для Android.
Давайте начнём:
Как всё начиналось?
В июне 2009 года я присоединился к ROUTE 66 в качестве старшего разработчика для Linux. Моим первым заданием было портирование навигационного движка на Android. Google тогда ещё официально не выпустила NDK, поэтому мне пришлось написать его самому, используя исходный код Android.
Вскоре мне удалось заставить движок работать. Я полюбил Android, но мне стало чего-то не хватать, чего-то, что было для меня очень важным. Это был Qt, мой любимый фреймворк. Вот чего не хватало! И я сказал себе, что я должен изменить положение вещей.
В октябре 2009 года Nokia (да, Qt тогда принадлежал Nokia...) анонсировала проект Lighthouse. Этот проект позволял разработчикам легко портировать Qt на (почти) любую платформу.
Позже, в декабре 2009 года (я думаю, это было после Рождества) у меня появилось достаточно свободного времени, чтобы начать портирование. Я решил использовать Lighthouse несмотря на то, что это был совсем молодой исследовательский проект. Насколько мне известно, мой порт для Android является первым проектом, который начал использовать Lighthouse. Спустя всего месяц (в январе 2010 года) я увидел первую отрисованную Qt графику на своём телефоне. Чувства были потрясающие!
Спустя несколько месяцев после того, как я привёл Qt к рабочему виду, началась работа над плагином для Qt Creator и Ministro. Плагин для Qt Creator позволял пользователю очень легко разрабатывать, развёртывать, запускать и отлаживать Qt-приложения на устройстве с Android или на эмуляторе.
Даже несмотря на то, что выглядело это неплохо, многие отказывались использовать его, потому что сначала они должны были откомпилировать всё вручную. Я решил что-нибудь с этим сделать.
Итак, в 2011 году, спустя несколько недель после того, как Nokia объявила о больших изменениях в стратегии компании, я выпустил первый пригодный для использования SDK для Android. После того, как проект Necessitas стал успешным, я решил продолжить разработку под покровительством KDE. Почему KDE? Потому что у нас одна цель: сохранить Qt мощным и доступным для каждого. Кроме того, я мог воспользоваться их огромной инфраструктурой для распространения библиотек Qt.
Самый первый SDK был выпущен только для Linux. Позже Рей Доннели связался со мной и портировал SDK на Windows и Mac. Если вы пользуетесь Necessitas (и Qt 5 Android SDK) на этих платформах, то это он — тот парень, которому стоит сказать спасибо :)
Вместе с Реем и остальными мы выпускали всё больше и больше новых релизов Necessitas SDK.
В ноябре 2012 года я передал порт на Android проекту Qt (Qt Project) для интеграции в Qt 5. Здесь я хотел бы сделать пояснение: только версия для Qt 5 разрабатывается под эгидой Qt Project. Версия для Qt 4 остаётся под покровительством KDE в качестве проекта Necessitas.
Давайте посмотрим, как в целом работает Qt на Android. Я не собираюсь вдаваться глубоко в детали, но опишу достаточно для того, чтобы вы имели представление о том, как это работает.
Как говорилось выше, порт основан на проекте Lighthouse. Lightouse (известный после ребрендинга как QPA) это «слой платформенной абстракции» (the platform abstraction layer) для Qt. Этот слой находится между Qt и целевой платформой, что делает портирование очень простым. Поскольку приложения для Android написаны на Java и невозможно соединить цикл обработки событий (event loop) Qt с циклом обработки событий Android, мне пришлось перенести цикл обработки событий Qt в другой поток. При разработке приложений учитывайте этот факт: цикл обработки событий Qt и Java UI находятся в разных потоках. Даже после того, как Google выпустила NativeActivity, не представляется возможным использовать его, в основном потому, что он не раскрывает все функции, которые необходимы в Qt.
Приложение на Qt для Android состоит из двух частей:
- Native-часть, которая содержит одну или несколько .so-библиотек, в которые компилируется ваше приложение. Если вы хотите, чтобы у приложения не было внешних зависимостей, здесь также будут находиться библиотеки Qt.
- Android-часть. Она включает в себя несколько разделов:
- Android manifest. Это точка входа вашего приложения. Android использует этот файл, чтобы определить, какие Приложения или Activity запустить, какие вашему приложению установлены разрешения (permissions), определяет минимальную версию Android API, необходимую для приложения и так далее.
- Два Java-класса, которые загружают зависимости и ваше приложение.
- Файлы .aidl сервиса Ministro. Эти два интерфейса используются для обмена сообщениями с сервисом Ministro. Этот сервис является одним из решений для развёртывания (о нём будет сказано позже).
- Остальные ресурсы (строки, изображения и прочее).
Все эти части собираются в один пакет, который представляет собой ваше приложение. Теперь давайте рассмотрим, как различные части взаимодействуют между собой.
При открытии приложения, Android использует manifest-файл для запуска activity. Эта кастомная activity представляет собой кусочек магии в мире Java вашего приложения. Для того, чтобы была возможность обновления библиотек Qt без переустановки существующего приложения, Java-часть разделена надвое: очень маленькая часть, которая хранится вместе с приложеним (как его часть) и другая часть (библиотека Java, файл .jar), которая содержит всю логику для плагина QPA.
Первая Java-часть, которая поставляется вместе с приложением, отвечает за поиск необходимых библиотек (Qt и Java) и загружает их. Кроме этого, она пересылает все события (нажатия, изменения состояния приложения, изменения экрана и так далее) во вторую Java-часть.
Вторая Java-часть ответственна за связь с Qt. Она содержит всю необходимую логику QPA-плагина для Android, например, создание поверхности для рисования и управление ею, обработку виртуальной клавиатуры и так далее.
Таким образом, первая (Java) часть находит и загружает все необходимые библиотеки и ваше приложение, после чего перенаправляет события второй части, но кто же тогда вызывает метод «main»? Это делает QPA плагин. Нет, я не сумасшедший! И да, плагин QPA загружается до запуска вашего приложения (вообще, он загружается даже до того, как ваше приложение будет загружено).
Позвольте мне объяснить, чем вызван такой безумный дизайн.
Моей мечтой было найти способ, при котором разработчики смогли бы всего лишь откомпилировать их существующие приложения для Android, так что я был вынужден каким-то образом вызывать метод «main» (я не хочу заставлять вас создавать другую входную точку в приложение, наподобие «WinMain»).
Проблема заключается в том, что для того, чтобы вызвать метод из Java, кто-то должен сперва этот метод зарегистрировать, иначе вызвать его будет нельзя. Здесь на сцене и появляется QPA. После загрузки, плагин QPA регистрирует несколько нативных функций, которые используются Java-частью для вызовов в нативной среде. После завершения загрузки всех библиотек и вашего приложения, Java-часть вызывает метод «startQtApplication», зарегистрированный ранее плагином QPA. Этот метод ищет символ метода «main», после чего создаёт поток и вызывает в нём выполнение метода «main». Создание параллельного потока здесь необходимо потому, что вызов «main» блокирует остальные вызовы, пока продолжает существовать, а мы должны сохранять UI-поток Android свободным для нормального функционирования цикла обработки событий Android.
В следующей статье я расскажу о том, как использовать JNI для вызовов в/из Java в/из нативную (C/C++) среду.
В заключение, взглянем на текущее состояние Qt на Android, что вы получите в Qt 5.2 и каковы планы на Qt 5.3 для Android.
Статус Qt Essentials:
Статус Qt Add-Ons:
Спасибо за ваше время.
Увидимся в следующий раз, когда мы будем обсуждать разработку для Android.
Автор: epicfailguy93