You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

969 lines
36 KiB

/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
namespace Magnum {
/** @page platforms-android Android
@brief Building and deploying Android projects
@tableofcontents
@m_footernavigation
7 years ago
@m_keywords{android_create_apk()}
@todoc code coverage
@todoc static plugins
The following guide explains how to build Android projects using minimal
command-line tools, without Android Studio involved.
At the very least you need to have Android SDK and Android NDK installed.
Running console utilities and tests on the device don't need much more, in case
you want to develop actual applications, you need also the SDK and a platform +
SDK platform build tools for version of your choice.
For APK building it's possible to use either an experimental support in CMake
or the official way with Gradle. Gradle is able to download all the
dependencies on its own, however it's also possible to install system packages
for a cleaner setup.
@note On ArchLinux it's the `gradle` package and the following AUR packages,
adapt the version numbers as necessary:
@note
- [android-sdk](https://aur.archlinux.org/packages/android-sdk/)
- [android-ndk](https://aur.archlinux.org/packages/android-ndk/)
- [android-sdk-build-tools](https://aur.archlinux.org/packages/android-sdk-build-tools/)
- [android-sdk-platform-tools](https://aur.archlinux.org/packages/android-sdk-platform-tools/)
- [android-platform-22](https://aur.archlinux.org/packages/android-platform-22/)
- [android-sdk-cmake](https://aur.archlinux.org/packages/android-sdk-cmake/)
Gradle requires Android SDK version of CMake, which is currently at version
3.6. See below for an experimental way to @ref platforms-android-system-cmake "use the system CMake"
instead.
@section platforms-android-console Building and running console applications
Android allows to run arbitrary console utilities and tests via ADB. Assuming
you have Magnum installed in the NDK path as described in @ref building-cross-android,
build your project simply as this (adapt version numbers and ABIs as needed):
@code{.sh}
mkdir build-android-arm64 && cd build-android-arm64
cmake .. \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=22 \
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DCMAKE_BUILD_TYPE=Release
cmake --build .
@endcode
After that you can use ADB to upload your executable to the device and run it
there. The global temporary directory is `/data/local/tmp` and while the parent
directories often don't have permissions, it's possible to @cb{.sh} cd @ce into
it and create arbitrary files there. Assuming you built an executable in
`build-android-arm64/src/my-application`, the workflow would be like this:
@code{.sh}
adb push build-android-arm64/src/my-application /data/local/tmp
adb shell /data/local/tmp/my-application
@endcode
You can also use @cb{.sh} adb shell @ce to enter the device shell directly and
continue from there. Besides plain command-line apps it's also possible to
create an EGL context without any extra setup using
@ref Platform::WindowlessEglApplication. See also @ref GL::OpenGLTester for
information about OpenGL testing.
@section platforms-android-apps Building and installing graphics apps
In case you don't have an OpenGL ES build set up yet, you need to copy
`FindEGL.cmake` and `FindOpenGLES2.cmake` (or `FindOpenGLES3.cmake`) from the
[modules/](https://github.com/mosra/magnum/tree/master/modules) directory in
Magnum source to the `modules/` dir in your project so it is able to find EGL
and OpenGL ES libraries.
Magnum provides Android application wrapper in @ref Platform::AndroidApplication.
See its documentation for more information about general usage. You can also
use the Android Native Activity directly or any other way.
8 years ago
@note @ref Platform::AndroidApplication also contains a fully configured
bootstrap project that's ready to build and deploy. Check its documentation
for details.
If you plan to use @ref Platform::AndroidApplication, be sure to request it
only on Android, for example:
@code{.cmake}
if(CORRADE_TARGET_ANDROID)
find_package(Magnum REQUIRED AndroidApplication)
else()
find_package(Magnum REQUIRED Sdl2Application)
endif()
@endcode
Compared to building an app for other platforms, you need to create a shared
library instead of an executable:
@code{.cmake}
if(NOT CORRADE_TARGET_ANDROID)
add_executable(my-application MyApplication.cpp)
else()
add_library(my-application SHARED MyApplication.cpp)
endif()
@endcode
@subsection platforms-android-apps-manifest Android manifest file
For Android you additionally need an `AndroidManifest.xml` file, which
describes various properties of the Android package. A minimal stripped-down
version is:
@code{.xml-jinja}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{ package }}" android:versionCode="1" android:versionName="1.0">
<uses-feature android:glEsVersion="0x00020000" />
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11" />
<application android:label="{{ app_name }}" android:hasCode="false">
<activity
android:name="android.app.NativeActivity"
android:label="{{ app_name }}"
android:configChanges="orientation|screenSize">
<meta-data android:name="android.app.lib_name" android:value="{{ lib_name }}" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@endcode
Replace @cb{.jinja} {{ package }} @ce with Java-like package name for your app
(in this case it could be e.g. @cpp "cz.mosra.magnum.my_application" @ce, for
example), @cb{.jinja} {{ app_name }} @ce with human-readable app name that's
displayed in the system (so e.g. @cpp "My Application" @ce) and finally the
@cb{.jinja} {{ lib_name }} @ce is name of the library that you compiled with
CMake, which in this case would be @cpp "my-application" @ce.
The @cb{.xml} <uses-feature android:glEsVersion="0x00020000" /> @ce line says
that the minimal OpenGL ES version is 2.0, change it in case you require a
different version.
@anchor platforms-android-apps-manifest-screen-compatibility-mode
The @cb{.xml} <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11" /> @ce
line requests the minimal and target Android SDK version. Setting the version
lower than this (or accidentally omitting this element) will enable the
[Screen Compatibility Mode](http://www.androiddocs.com/guide/practices/screen-compat-mode.html),
which doesn't report events in coordinates that match underlying framebuffer
pixel size. That is not supported by @ref Platform::AndroidApplication since
there's [no reliable way](https://stackoverflow.com/q/17481341) to get the
actual size used for events in that case.
@anchor platforms-android-apps-manifest-screen-resize
The @cb{.xml} <activity … android:configChanges="orientation|screenSize"> @ce
attribute is needed in order to make the application properly receive a
viewport event. By default it's being outright killed and recreated when device
orientation changes for questionable "performance reasons". If you really want
to handle orientation changes that way, remove `screenSize` from the set.
Among other options is restricting the app to only portrait or landscape screen
orientation.
Consult [the Android developer documentation](https://developer.android.com/guide/topics/manifest/manifest-intro.html)
for further information about the manifest file.
With this set up, you have two options how to build the final APK, either using
plain CMake or using Gradle.
@subsection platforms-android-apps-cmake Using CMake
The `toolchains` repository contains an `UseAndroid.cmake` module that allows
you to create an APK in a significantly faster and simpler way than when using
Gradle.
@attention This feature is in an experimental stage and at this time it doesn't
support compilation of any resources (such as icons) or Java sources. It
also builds only the ABI and configuration that corresponds to the
particular CMake build directory. It's thus recommended to employ this
approach for fast iteration during development alongside the classic Gradle
build @ref platforms-android-apps-gradle "described below" that will get
used for the final release builds.
Download contents of the toolchains repository from https://github.com/mosra/toolchains
or add it as a Git submodule, add its module path to `CMAKE_MODULE_PATH` and
include the `UseAndroid` module. Wrapping it in a check for presence of
`CMAKE_ANDROID_NDK` will make it possible to have the pure CMake and a Gradle
build coexist --- because Gradle internally uses CMake 3.6 which doesn't know
about `CMAKE_ANDROID_NDK` yet.
@code{.cmake}
if(CORRADE_TARGET_ANDROID AND CMAKE_ANDROID_NDK)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/toolchains/modules/")
include(UseAndroid)
endif()
@endcode
On the first run, the macro will attempt to detect SDK location, Android Build
Tools version and Android Platfrom version and it prints them to the output
like this:
@code{.shell-session}
$ cmake .
-- ANDROID_SDK not set, detected /opt/android-sdk
-- ANDROID_BUILD_TOOLS_VERSION not set, detected 27.0.3
-- ANDROID_PLATFORM_VERSION not set, detected 27
@endcode
If you don't like what it detected (or the detection failed), edit these values
in CMake cache. After that, pass your library target name and location of the
`AndroidManifest.xml` file to @cmake android_create_apk() @ce. Continuing from
the above:
@code{.cmake}
add_library(my-application SHARED MyApplication.cpp)
android_create_apk(my-application AndroidManifest.xml)
@endcode
Note that even thought it doesn't make any sense, the `aapt` tool *demands* the
manifest file to be called exactly `AndroidManifest.xml`. It can be, however,
in any location. This will create an APK named `my-application.apk` and
additionally also provide a new target, `my-application-deploy`, that will use
`adb install -r` to install the built APK on the device. Building and uploading
the application can be then done in a single step:
@code{.shell-session}
$ ninja my-application-deploy
[5/5] Installing my-application.apk
Success
@endcode
(Or e.g. `cmake --build . --target my-application-deploy` if you don't use
Ninja.)
For full compatibility with the Gradle build wrap the call again in a check for
`CMAKE_ANDROID_NDK` and put the manifest in the `src/main` subdirectory, where
Gradle expects it:
@code{.cmake}
add_library(my-application SHARED MyApplication.cpp)
if(CMAKE_ANDROID_NDK)
android_create_apk(my-application src/main/AndroidManifest.xml)
endif()
@endcode
@subsection platforms-android-apps-gradle Using Gradle
Besides plain CMake and Gradle it's also possible to use the classic
`ndk-build`. but that's the least recommended way and it might not be supported
in newer NDK builds. The following guide assumes you have Gradle installed in a
system-wide location available in @cb{.sh} $PATH @ce. See the
[Gradle installation docs](https://docs.gradle.org/current/userguide/installation.html)
for more information, @ref platforms-android-gradlew "see below" if you want to
use the `gradlew` wrappers instead.
Create a `build.gradle` file that references your root `CMakeLists.txt`.
Assuming it's saved right next to your root `CMakeLists.txt`, the most minimal
version might look like this:
@code{.gradle}
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
defaultConfig {
minSdkVersion 22
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
ndk {
abiFilters 'arm64-v8a'
}
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
}
@endcode
Important things are @cb{.gradle} compileSdkVersion @ce and
@cb{.gradle} minSdkVersion @ce, which set SDK version that will be used to
compile the project and minimal SDK version that the app can run on. You can
add further CMake parameters in the @cb{.gradle} arguments @ce line (here it's
just requesting to use static libc++) and the @cb{.gradle} abiFilters @ce allow
you to restrict which ABIs will the project be built for --- Gradle by default
builds for both 32 and 64-bit ARM, MIPS and x86, which might be quite annoying
to wait for (during development at least). The @cb{.gradle} path @ce then
references your `CMakeLists.txt` file. Gradle by default bundles all shared
library targets defined in the CMake project, so there's no need to specify a
particular library name.
Gradle by default chooses Android SDK Build Tools version that corresponds to
the @cb{.gradle} compileSdkVersion @ce. If you want to have control (for
example to make it use Build Tools that you already have installed), specify
it using @cb{.gradle} buildToolsVersion @ce:
@code{.gradle}
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
...
@endcode
The [official documentation](https://developer.android.com/studio/projects/gradle-external-native-builds.html#configure-gradle)
contains a more complete overview of all possibilities.
For the `AndroidManifest.xml` file, Gradle expects it to be placed inside the
`src/main` subdirectory, *not* straight besides the `build.gradle` file.
With everything set up, you are now ready to build the project by simply
executing the following from the directory with your `build.gradle`. During the
first run, Gradle will download a huge amount of random stuff when building
even the simplest thing. Close your eyes and ignore that it happened.
@code{.sh}
gradle build
@endcode
8 years ago
You can also use @cb{.sh} gradle assembleDebug @ce which is slightly faster as
it doesn't build both Debug and Release and omits some unneeded checks.
Installing on a connected device or emulator is then a matter of
@code{.sh}
gradle installDebug
@endcode
after which you can launch the app from your home screen. See the
@ref platforms-android-troubleshooting section below if you ran into problems.
@section platforms-android-multiple-abis Building for multiple ABIs and system versions
The above guide simplifies things a bit and builds for just a single ARM64 ABI.
In order to support multiple platforms, you need to separately build and
install the dependencies for all ABIs of choice --- create separate build
directories and run CMake with different `CMAKE_ANDROID_ARCH_ABI` and
corresponding `CMAKE_INSTALL_PREFIX`. Similarly with SDK versions, adapt
`CMAKE_SYSTEM_VERSION` and `CMAKE_INSTALL_PREFIX` to a desired version. The
headers are shared and should be always installed into `&lt;ndk&gt;/sysroot/usr`
regardless of ABI or SDK version. The supported ABI values are:
ABI | Corresponding install prefix
----------- | ----------------------------
armeabi-v7a | &lt;nk&gt;/platforms/android-&lt;version&gt;/arch-arm/usr
arm64-v8a | &lt;nk&gt;/platforms/android-&lt;version&gt;/arch-arm64/usr
x86 | &lt;nk&gt;/platforms/android-&lt;version&gt;/arch-x86/usr
x86_64 | &lt;nk&gt;/platforms/android-&lt;version&gt;/arch-x86_64/usr
After that, you can add the additional ABIs to the `abiFilters` list in your
`build.gradle`.
For example, building Magnum for 32-bit and 64-bit ARM with SDK version 24
could look like this:
@code{.sh}
mkdir build-android-arm && cd build-android-arm
cmake .. \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=24 \
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=<ndk>/platforms/android-24/arch-arm/usr \
-DMAGNUM_INCLUDE_INSTALL_PREFIX=<ndk>/sysroot/usr
cmake --build . --target install
cd ..
mkdir build-android-arm64 && cd build-android-arm64
cmake .. \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=24 \
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=<ndk>/platforms/android-24/arch-arm64/usr \
-DMAGNUM_INCLUDE_INSTALL_PREFIX=<ndk>/sysroot/usr
cmake --build . --target install
@endcode
The `build.gradle` for your app then looks like below.
@attention Building multiple ABIs is currently not supported with the CMake
@cmake android_create_apk() @ce macro, using Gradle is the only way.
@code{.gradle}
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
defaultConfig {
minSdkVersion 24
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
}
@endcode
See [the official documentation about ABIs](https://developer.android.com/ndk/guides/abis.html)
for more information.
@section platforms-android-output-redirection Redirecting output to Android log buffer
While printing to standard output and standard error output "just works" with
command-line apps, you might want to redirect your @ref Corrade::Utility::Debug "Debug",
@ref Corrade::Utility::Warning "Warning" and @ref Corrade::Utility::Error "Error"
output to Android log buffer. so it can be accessed through the @cb{.sh} adb logcat @ce
utility. See @ref Corrade::Utility::AndroidLogStreamBuffer for more
information.
The @ref Platform::AndroidApplication sets this up implicitly with a `magnum`
tag, you can then filter it out like this, for example:
@code{.shell-session}
$ adb logcat *:S magnum
...
03-16 17:35:26.703 17726 17745 I magnum : Renderer: Mali-G71 by ARM
03-16 17:35:26.703 17726 17745 I magnum : OpenGL version: OpenGL ES 3.2 v1.r2p0
03-16 17:35:26.703 17726 17745 I magnum : Using optional features:
03-16 17:35:26.703 17726 17745 I magnum : GL_EXT_robustness
@endcode
@section platforms-android-system-cmake Using system-wide CMake installation
According to the [official documentation](https://developer.android.com/studio/projects/add-native-code.html#vanilla_cmake),
it's possible to use system CMake installation without needing to install
Android SDK version of CMake 3.6. Simply update the
@cb{.gradle} externalNativeBuild @ce in your `build.gradle` file to specify
CMake version that you have installed in your system, for example:
@code{.gradle}
android {
...
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
...
version '3.10.2'
}
}
}
@endcode
However, be aware that this is an experimental feature and may be broken. At
the time of writing (March 2018), it didn't work for me with NDK r16b, Android
buid plugin 3.0.1 and CMake 3.10.
@section platforms-android-gradlew Using gradlew wrappers instead of a system installation
It's possible to bundle Gradle in the project itself as opposed to requiring a
pre-existing system installation. It has the downside of having a bit more
boilerplate files in your project, though.
First, add the following to your `build.gradle` file:
@code{.gradle}
task wrapper(type: Wrapper) {
gradleVersion = '4.0'
}
@endcode
Then run this on a system that has Gradle installed:
@code{.sh}
gradle wrapper
@endcode
This will generate the following files that you can then add to version
control:
- `gradlew` shell script for Unix-like systems
- `gradle.bat` batch script for Windows
- `gradle/` directory with wrapper binaries
With this in place, you can just use @cb{.sh} gradlew @ce instead of
@cb{.sh} gradle @ce.
@section platforms-android-travis Setting up Android build on Travis CI
For simple compilation tests, add the following to your `.travis.yml` matrix
builds. According to the [Travis Android documentation](https://docs.travis-ci.com/user/languages/android/),
`build-tools-22.0.1` and `android-22` are always present, so your builds
shouldn't get any extra delay when requesting them. The @cb{.sh} $TARGET @ce
environment variable is used here only to disambiguate later, you might or
might not need it.
@code{.yml}
matrix:
include:
# ...
- language: android
os: linux
dist: trusty
env:
- TARGET=android
android:
components:
- build-tools-22.0.1
- android-22
@endcode
At the time of writing (March 2018), while the generic Ubuntu 14.04 images
already have CMake 3.9.2, for some reason the Android Ubuntu 14.04 images have
just CMake 3.2. Android support is builtin since version 3.7, but
[an important fix](https://gitlab.kitware.com/cmake/cmake/issues/17253) for the
LLVM toolchain was merged as late as in 3.9.2, so you may want to grab that
version. Example `.travis.yml` setup that downloads the binary and extracts it
to @cb{.sh} $HOME/cmake @ce, with @cb{.sh} $PATH @ce setup and caching:
@code{.yml}
cache:
directories:
- $HOME/cmake
install:
- >
if [ "$TARGET" == "android" ] && [ ! -e "$HOME/cmake/bin" ]; then
cd $HOME ;
wget https://cmake.org/files/v3.9/cmake-3.9.2-Linux-x86_64.tar.gz &&
mkdir -p cmake &&
cd cmake &&
tar --strip-components=1 -xzf ../cmake-3.9.2-Linux-x86_64.tar.gz &&
cd $TRAVIS_BUILD_DIR ;
fi
- >
if [ "$TARGET" == "android" ]; then
export PATH=$HOME/cmake/bin:$PATH &&
cmake --version ;
fi
@endcode
The NDK can be fetched as a simple `*.zip` file. However, version r16b has over
800 MB, so you might want to explore creation of a
[Standalone Toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html)
with only the things you need to speed up the build. Again, downlading it into
@cb{.sh} $HOME/android-ndk-r16b @ce is a matter of adding this into your
@cb{.yml} install: @ce section:
@code{.yml}
- >
if [ "$TARGET" == "android" ]; then
cd $HOME ;
wget https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip &&
unzip -q android-*.zip &&
cd $TRAVIS_BUILD_DIR ;
fi
@endcode
Travis CI discourages caching the NDK, as downloading the cache will take
roughly the same amount of time as downloading it from upstream.
Building your actual code is just a matter of setting up a correct NDK path.
You can install the dependencies to any location as long as you specify the
same location in `CMAKE_PREFIX_PATH` and `CMAKE_FIND_ROOT_PATH` in depending
projects. Using `armeabi-v7a` instead of `arm64-v8a` ensures that you can run
the code in a preinstalled emulator later, see below.
@code{.sh}
mkdir build-android-arm && cd build-android-arm
cmake .. \
-DCMAKE_ANDROID_NDK=$HOME/android-ndk-r16b \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=22 \
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \
-DCMAKE_ANDROID_STL_TYPE=c++_static \
-DCMAKE_PREFIX_PATH=$HOME/deps \
-DCMAKE_FIND_ROOT_PATH=$HOME/deps \
...
@endcode
@subsection platforms-android-travis-run Running tests on the emulator
In order to run your tests on the emulator, you need to request some system
image. Again, `sys-img-armeabi-v7a-android-22` is part of the default
installation, so it shouldn't add any extra time to your build:
@code{.yml}
matrix:
include:
- language: android
# ...
android:
components:
# ...
- sys-img-armeabi-v7a-android-22
@endcode
As described [in the Travis documentation](https://docs.travis-ci.com/user/languages/android/#How-to-Create-and-Start-an-Emulator),
create a system image and wait for the emulator to start (be prepared, it can
easily take up *minutes*). Assuming you use
the @ref TestSuite-Tester-running-cmake "Corrade::TestSuite Android integration",
simply run your tests via `ctest` and optionally enable colored output for
extra clarity:
@code{.sh}
echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
emulator -avd test -no-audio -no-window &
android-wait-for-emulator
CORRADE_TEST_COLOR=ON ctest -V
@endcode
@subsection platforms-android-travis-bundle APK bundle creation
At the time of writing (March 2018), Travis Ubuntu 14.04 has Gradle 4.0,
however the Android build plugin 3.0 requires at least Gradle 4.1, so you need
to backport `gradle.build` to plugin version 2.3.3 compared to the
@ref platforms-android-apps "template above". In particular, the `classpath`
needs to be updated, `compileSdkVersion` and `minSdkVersion` adapted to
versions defined in @cb{.yml} components: @ce in your `travis.yml` file and the
`buildToolsVersion` explicitly specified, because that's needed in plugin
versions before 3.0:
@code{.gradle}
buildscript {
// ...
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
// ...
android {
compileSdkVersion 22
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 22
// ...
@endcode
Gradle bundles its own CMake 3.6, downloading it on-demand and then failing
because SDK licenses are not signed. Solution is to install CMake and sign its
license explicitly beforehand. Add the following to your `.travis.yml`:
@code{.yml}
before_install:
- if [ "$TARGET" == "android" ]; then yes | sdkmanager "cmake;3.6.4111459"; fi
@endcode
Unlike above, and especially if you build for multiple ABIs, it's better to
install all dependencies where Gradle expects them. In particular, in case of
Corrade and ARM64 ABI and NDK being in @cb{.sh} $HOME/android-ndk-r16b @ce, the
install prefixes look like this:
@code{.sh}
cmake .. \
-DCMAKE_INSTALL_PREFIX=$HOME/android-ndk-r16b/platforms/android-22/arch-arm64/usr \
-DCORRADE_INCLUDE_INSTALL_PREFIX=$HOME/android-ndk-r16b/sysroot/usr \
...
@endcode
Finally, you need to tell Gradle where the NDK is located and where to look for
native binaries (for example the `corrade-rc` executable) using environment
variables. At last, execute `gradle build` in the directory where
`build.gradle` is:
@code{.sh}
export ANDROID_NDK_HOME=$HOME/android-ndk-r16b
export CMAKE_PREFIX_PATH=$HOME/deps-native/
gradle build
@endcode
@section platforms-android-troubleshooting Troubleshooting
@subsection platforms-android-troubleshooting-multiple-devices ADB fails when more devices are connected
If any `adb` command fails with
@code{.shell-session}
error: more than one device/emulator
@endcode
@m_class{m-noindent}
you can pass the device ID via the `-s` parameter to each command. That is,
however, verbose and doesn't work for example in case of running
@ref Corrade::TestSuite tests via `ctest`. Instead, it's possible to supply
the device ID via an environment variable, for example:
@code{.sh}
export ANDROID_SERIAL="bla"
@endcode
@subsection platforms-android-troubleshooting-signing-failed Signing the APK using CMake fails
At the moment the location and passphrase for the Android signing keystore is
implicitly set to @cb{.sh} $HOME/.android/debug.keystore @ce with `android` as
a password, since that's the location (and password) that Gradle uses. If you
see output similar to this:
@code{.shell-session}
[1/2] Signing my-application.apk
FAILED: my-application.apk
Failed to load signer "signer #1"
java.io.FileNotFoundException: <home>/.android/debug.keystore
@endcode
then you either don't have the keystore generated yet or it's in some other
location. Similar error can happen if the password is incrrect, in which case
it will say the following instead:
@code{.shell-session}
Failed to load signer "signer #1"
java.io.IOException: Keystore was tampered with, or password was incorrect
@endcode
Generating a debug keystore can be done by running through the Gradle build at
least once. It's also possible to create a keystore
[using Android Studio](https://developer.android.com/studio/publish/app-signing#generate-key)
or using the `keytool` utility that's bundled with Java SDK. The following
creates a key equivalent to the Gradle-generated one, adapt the location as
necessary:
@code{.sh}
keytool -genkeypair -keystore $HOME/.android/debug.keystore \
-storepass android -alias androiddebugkey -keypass android \
-keyalg RSA -validity 10000 -dname CN=,OU=,O=,L=,S=,C=
@endcode
The location and password is controlled via the `ANDROID_APKSIGNER_KEY`
variable, edit your CMake cache to point it to a different location or
password. Note that the command parameters have to be delimited by `;` instead
of a space. For example, setting the location to `/etc/android.keystore` with a
password `secret` can be done like this:
@code{.sh}
cmake . -DANDROID_APKSIGNER_KEY="--ks;/etc/android.keystore;--ks-pass;pass:secret"
@endcode
You can also add further arguments to `apksigner` here. See the
[official documentaton for apksigner](https://developer.android.com/studio/command-line/apksigner)
for details.
@subsection platforms-android-troubleshooting-cant-find Gradle CMake can't find dependencies
Gradle by default searches only in the NDK install path. If you have your
dependencies installed somewhere else (this goes especially for the *native*
`corrade-rc` executable), you might want to point the `CMAKE_PREFIX_PATH`
environment variable to your install location:
@code{.sh}
export CMAKE_PREFIX_PATH=<path-where-your-dependencies-are-installed>
gradle build
@endcode
If you have the dependencies installed in the NDK path, but it still fails,
check that you installed for the same SDK version as in `minSdkVersion` and all
ABIs mentioned in `abiFilters` inside your `build.gradle` file --- Gradle runs
CMake once for each entry in the list so it might happen that it finds them for
all but one ABI. See @ref platforms-android-multiple-abis above for more
information.
@subsection platforms-android-troubleshooting-cant-launch App can't launch
If your application can't launch (or it just blinks and then disappears), you
can inspect @cb{.sh} adb logcat @ce output to see what went wrong, but be
quick, the log is spitting out a lot of info all the time. Possible causes:
- Mismatch between actual library name and library referenced from
`AndroidManifest.xml`, causing Java to fail loading it
- The device has an ABI for which the app was not compiled (check the
@cb{.gradle} abiFilters @ce option in `build.gradle`) or the app was
compiled with SDK version that's not supported by the device yet. See the
[official API level documentation](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels)
for more information.
- The device doesn't support OpenGL ES 3.0 yet. Rebuild Magnum and its
dependencies with the `TARGET_GLES2` option enabled. See @ref building-features
for more information.
- Loading fails with `ANativeActivity_onCreate` symbol not being found. If
you are using @ref Platform::AndroidApplication, this issue should be
prevented, otherwise you need to add `-u ANativeActivity_onCreate` to your
linker flags or reference the symbol some other way. See
[android-ndk/ndk#381](https://github.com/android-ndk/ndk/issues/381) for
details.
- Additional `*.so` libraries are referenced by the main `*.so` but not
bundled in the `*.apk`. One option is to switch to static libraries,
another is explicitly specifying them in the `build.gradle` file. See
[the official documentation](https://developer.android.com/studio/projects/gradle-external-native-builds.html#jniLibs)
for details.
@subsection platforms-android-troubleshooting-java8 Gradle aborting due to too new Java
If you see one of the following outputs, it might be that you're using Java 9
or 10, which is not supported by Android build tools yet:
@code{.shell-session}
$ gradle build
FAILURE: Build failed with an exception.
* What went wrong:
> Failed to notify project evaluation listener.
> javax/xml/bind/annotation/XmlSchema
@endcode
@code{.shell-session}
$ gradle build
FAILURE: Build failed with an exception.
* What went wrong:
> Failed to notify project evaluation listener.
> Could not initialize class com.android.sdklib.repository.AndroidSdkHandler
@endcode
Solution is to point @cb{.sh} $JAVA_HOME @ce to a Java 8 installation, for
example. This affects not just `gradle`, but also other tools like `sdkmanager`
(see @ref platforms-android-troubleshooting-licenses "below"), so you may want
to export it for the whole session:
@code{.sh}
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk/
gradle build
@endcode
On ArchLinux, Java 8 is provided by the [jdk8-openjdk](https://www.archlinux.org/packages/extra/x86_64/jdk8-openjdk/)
package, which is pulled in as a dependency of the `android-sdk` and `gradle`
packages.
@subsection platforms-android-troubleshooting-term Gradle aborting due to termcap
If you see the following output, Gradle is crashing because @cb{.sh} $TERM @ce
is set to `xterm-256color` or `xterm-24`:
@code{.shell-session}
$ gradle build
FAILURE: Build failed with an exception.
* What went wrong:
Could not open terminal for stdout: could not get termcap entry
@endcode
Solution is to set @cb{.sh} TERM=xterm @ce. See
[gradle/gradle#4440](https://github.com/gradle/gradle/issues/4440) for more
information.
@code{.sh}
TERM=xterm gradle build
@endcode
@subsection platforms-android-troubleshooting-licenses Accepting SDK licenses for Gradle
Gradle might refuse to build a project if SDK licenses are not accepted.
Depending on where your SDKs are installed, you might need to execute the
following (assuming you have SDK version 26 at least):
@code{.sh}
sdkmanager --licenses # and then manually accept all of them
@endcode
The tool doesn't provide any diagnostic output if the accepting failed, so be
sure to verify that everything went well by executing @cb{.sh} sdkmanager --licenses @ce
again. If it offers the same licenses again, you might want to force it with
@cb{.sh} sudo @ce.
If the tool blows up with the following error, it's again because the Java
version is too new, see @ref platforms-android-troubleshooting-java8 "above"
for a solution:
@code{.shell-session}
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
@endcode
@subsection platforms-android-troubleshooting-permissions Android SDK directory permissions
Gradle is able to work with system-installed Android SDK. If it complains about
directory permissions such as
@code{.shell-session}
> Failed to install the following SDK components:
[Android SDK Build-Tools 26.0.2, Android SDK Platform 25]
The SDK directory (/opt/android-sdk) is not writeable,
please update the directory permissions.
@endcode
it's often enough to just install such packages. In case of ArchLinux, all
relevant packages are available in AUR. For the above error in particular, the
matching packages are [android-sdk-build-tools-26.0.2](https://aur.archlinux.org/packages/android-sdk-build-tools-26.0.2/)
and [android-platform-25](https://aur.archlinux.org/packages/android-platform-25/).
*/
}