Поддержка Android
Для сборки зависимостей зависимостей используется инструмент NDK. Для предварительно собранных зависимостей используется версия 26d.
Базовая версия платформы, поддерживаемая SDK - android-24 (Android 7.0).
Необходимо обратить внимание, что многие проекты используют системные вызовы, требующие android-28: необходимо следить, чтобы флаги целевой платформы были выставлены так, чтобы эти вызовы были заблокированы, если необходимо поддерживать более ранние версии.
При сборке путь к NDK указывается с помощью переменной NDK=<путь>
при запуске make
.
Для работы с WebAssembly и Vulkan необходимо, чтобы средства сборки были установлены на вашем текухем хосте.
Юникод
До версии android-32 пользовтельского устройства функции онвертации регистра и сравнения с использованием юникода используют Java-прослойку, что достаточно медленно. Доступность функций версии android-32 определяются динамически.
С версии android-32 используется предоставляемая NDK версия libicuc.
Activity и другие функции Java
SDK предоставляет вспомогательную Java-библиотеку appsupport, раскрывающую для кода SDK больше необходимых функций, чем предоставляет NativeActivity.
Для корректного использования необходимо использовать в качестве базового класса предоставляемый библиотекой класс AppSupportActivity
вместо NativeActivity
. Однако, SDK автоматически определяет доступность функци и может работать с чистой NativeActivity
, это ограничит возможности текстового ввода с экранной клавиатурой и отслеживание статуса сети.
Функции Google Play Services и Firebase
Для функций Google Play Services и Firebase создана вспомогательная библиотека firebase. Она служит, в первую очередь, для получения соотвествующих push-уведомлений.
Создание приложения на Android
Application.mk и Android.mk
Для сборки прикладных средств для Android используется ndk-build. Для его работы нужно ознакомиться с двумя ключевыми концепциями: Android.mk и Application.mk.
Для работы необходимо создать Application.mk и Android.mk уровня приложения, после чего указать их для сборки проекта Stappler SDK (в Makefile проекта):
LOCAL_ANDROID_MK := proj.android/app/Android.mk # path to root Android.mk
LOCAL_APPLICATION_MK := proj.android/app/Application.mk # path to Application.mk
Пример Application.mk
# Используем статическую версию libc++ из NDK
APP_STL := c++_static
# Минимальная версия платформы для приложения (24 - минимум, поддерживаемый SDK)
APP_PLATFORM := android-24
# SDK требует минимум -std=gnu++20 и -frtti для работы
# исключения не используются в SDK, потому их отключение выгодно
# -Wno-gnu-string-literal-operator-template -Wno-unqualified-std-cast-call - ошибки кодового стиля clang, не являющиеся ошибками для SDK
APP_CPPFLAGS := -frtti -fno-exceptions -std=gnu++20 -Wno-extern-c-compat -Wno-gnu-string-literal-operator-template
Пример Android.mk
# Стандартный вариант Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Имя собираемого модуля для включения в Android-приложение
LOCAL_MODULE := application
# Подключение модуля, собираемого SDK
LOCAL_WHOLE_STATIC_LIBRARIES := stappler_application_generic
include $(BUILD_SHARED_LIBRARY)
# Добавляем корень проекта в качестве пути для поиска модулей ndk-build
$(call import-add-path,$(LOCAL_PATH)/../..)
# Подключаем создаваемый SDK модуль
$(call import-module,stappler-build/android)
Сборка модуля SDK для ndk-build
Сборка файла модуля (экспорт проекта в ndk-build) выполняется командой:
make NDK=<путь к NDK> android export
Это создаёт файл stappler-build/android/Android.mk, который отражает проект из Makefile
. Запуск ndk-build для этого проекта соберёт целевой проект для Android.
Цель android-export
и файл stappler-build/android/Android.mk организованы таким образом, что запуск make по цели обновляет файл только тогда, это обновление реально необходимо. Это позволяет устанавливать файл stappler-build/android/Android.mk в качестве целевого предусловия, и не страдать от постоянной полной перестройки проекта.
Также, системы сборки Stappler SDK может собирать полностью готовую разделяемую библиотеку, самостоятельно оперируя ndk-build. Для этого служат цели android-debug
, android-debug-clean
, android-release
, android-release-clean
.
После сборки, готовую динамическую библиотеку (библиотеки под конкретные архитекутры) можно добавлять в сборку Android-приложения.
Интеграция с gradle
Gradle - стандартная система сборки для Android на текущий момент. Для интеграции просто используйте проект ndk-build из Android.mk и Application.mk. Например, так:
plugins {
id 'com.android.application'
}
// пре-инициализация
...
// Базовое правило для вызова экспорта проекта из Stappler SDK
tasks.register('prepareAndroidMk', Exec) {
workingDir "${buildDir}/../../.."
commandLine 'make', 'android-export'
}
// Устанавливаем правило перед сборкой
preBuild.dependsOn prepareAndroidMk
android {
// конфигурация проекта здесь
...
defaultConfig {
// стандартные параметры сборки здесь
...
// связываем с Application.mk
externalNativeBuild {
ndkBuild {
arguments "NDK_APPLICATION_MK:=Application.mk"
}
}
}
...
// связываем с Android.mk
externalNativeBuild {
ndkBuild {
path "Android.mk"
}
}
...
}
С таким подходом Gradle успешно захватывает весь C++ проект для Android, и может компилировать его инкрементально при изменении. Также, при использовании Android Studio это позволяет увидеть весь C++ проект в редакторе кода и отлаживать его визуально.
NativeActivity
Stappler SDK может использоваться для вспомогательных средств, таких, как ГОСТ-криптография или работа с БД, однако, основная ценность в возможности создавать графические приложения. Для этого необходимо использовать NativeActivity, которая будет загружать код, предоставляемый Stappler SDK.
Для этого в C++ приложении необходимо предоставить функцию ANativeActivity_onCreate
. Обычно мы делаем это дополнительным файлом со стандартным именем native_activity.cpp
. Пример реализации:
// Условная компиляция только для Android
#if ANDROID
// Базовые заголовки
#include <jni.h>
#include <android/native_activity.h>
#include "android/XLPlatformAndroidActivity.h"
#include "android/XLPlatformAndroidMessageInterface.h"
// Ваш заголовок приложения (наследника xenolith::Application)
#include "ExampleApplication.h"
namespace stappler::xenolith::myapp {
// Экспортируем функцию под её основным именем и делаем доступной для JNI
SP_EXTERN_C JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) {
// Создаём С++ адаптер для NativeActivity
auto a = Rc<platform::Activity>::create(activity, platform::ActivityFlags::CaptureInput);
// Читаем предоставляемую системой информацию
auto info = a->getActivityInfo();
// Запускаем NativeActivity
a->run([info = move(info)] (platform::Activity *a, Function<void()> &&initCb) {
// Эта функция будет вызвана при успешном запуске
// Устанавливаем параметры приложения из системной информации Activity
ViewCommandLineData appInfo({
.bundleName = move(info.bundleName),
.applicationName = move(info.applicationName),
.applicationVersion = move(info.applicationVersion),
.userLanguage = move(info.locale),
.userAgent = move(info.systemAgent),
.density = info.density
});
// Создаём и запускаем наше приложение
auto app = Rc<ExampleApplication>::create(move(appInfo), a);
// Передаём функцию пост-инициализации Activity в запуск приложения
app->run(move(initCb));
});
}
}
#endif
Дополнительная Java-библиотека AppSupportActivity
AppSupportActivity, предоставляемая xenolith, расширяет возможности NativeActivity по общению между Java и C++. Это упрощает процесс создания приложения.
Для подключения этой библиотеки:
- В settings.gradle добавить в конец файла:
include ':appsupport'
project(':appsupport').projectDir = new File('<путь к установке Stappler SDK (libstappler-root)>/xenolith/platform/android/appsupport')
- В build.gradle проекта:
// добавляем в блок dependencies
dependencies {
// после других зависимостей
implementation project(path: ":appsupport")
}
После добавления можно наследоваться от AppSupportActivity в своей реализации, или напрямую загружать её в AndroidManifest.xml.
Пример AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity android:name="org.stappler.xenolith.appsupport.AppSupportActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="fullSensor"
android:exported="true">
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="application" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->