Platform-specific guides » Android

Building and deploying Android projects.

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 Gradle and SDK platform + SDK platform build tools for version of your choice. Gradle is able to download all the dependencies on its own, however it's also possible to install system packages for a cleaner setup.

Gradle requires Android SDK version of CMake, which is currently at version 3.6. See below for an experimental way to use the system CMake instead.

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 Crosscompiling for Android, build your project simply as this (adapt version numbers and ABIs as needed):

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 .

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 cd 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:

adb push build-android-arm64/src/my-application /data/local/tmp
adb shell /data/local/tmp/my-application

You can also use adb shell 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 Platform::WindowlessEglApplication. See also GL::OpenGLTester for information about OpenGL testing.

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/ 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 Platform::AndroidApplication. See its documentation for more information about general usage. You can also use the Android Native Activity directly or any other way.

If you plan to use Platform::AndroidApplication, be sure to request it only on Android, for example:

if(CORRADE_TARGET_ANDROID)
    find_package(Magnum REQUIRED AndroidApplication)
else()
    find_package(Magnum REQUIRED Sdl2Application)
endif()

Compared to building an app for other platforms, you need to create a shared library instead of an executable:

if(NOT CORRADE_TARGET_ANDROID)
    add_executable(my-application MyApplication.cpp)
else()
    add_library(my-application SHARED MyApplication.cpp)
endif()

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:

<?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 }}">
      <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>

Replace {{ package }} with Java-like package name for your app (in this case it could be e.g. "cz.mosra.magnum.my_application", for example), {{ app_name }} with human-readable app name that's displayed in the system (so e.g. "My Application") and finally the {{ lib_name }} is name of the library that you compiled with CMake, which in this case would be "my-application".

The <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="11" /> line says the minimal and target Android SDK version. Setting the version lower than this (or accidentally omitting this element) will enable the Screen Compatibility Mode, which doesn't report events in coordinates that match underlying framebuffer pixel size, which is unsupported by Platform::AndroidApplication since there's no reliable way to get the actual size used for events in that case.

The <uses-feature android:glEsVersion="0x00020000" /> line says that the minimal OpenGL ES version is 2.0, change it in case you require a different version.

Consult the Android developer documentation 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.

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.

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.

if(CORRADE_TARGET_ANDROID AND CMAKE_ANDROID_NDK)
    list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/toolchains/modules/")
    include(UseAndroid)
endif()

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:

$ 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

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 android_create_apk(). Continuing from the above:

add_library(my-application SHARED MyApplication.cpp)
android_create_apk(my-application AndroidManifest.xml)

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:

$ ninja my-application-deploy
[5/5] Installing my-application.apk
Success

(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:

add_library(my-application SHARED MyApplication.cpp)
if(CMAKE_ANDROID_NDK)
    android_create_apk(my-application src/main/AndroidManifest.xml)
endif()

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 $PATH. See the Gradle installation docs for more information, 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:

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'
        }
    }
}

Important things are compileSdkVersion and minSdkVersion, 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 arguments line (here it's just requesting to use static libc++) and the abiFilters 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 path 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 compileSdkVersion. If you want to have control (for example to make it use Build Tools that you already have installed), specify it using buildToolsVersion:

android {
    compileSdkVersion 27
    buildToolsVersion '27.0.3'
    ...

The official documentation 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.

gradle build

You can also use gradle assembleDebug 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

gradle installDebug

after which you can launch the app from your home screen. See the Troubleshooting section below if you ran into problems.

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 <ndk>/sysroot/usr regardless of ABI or SDK version. The supported ABI values are:

ABICorresponding install prefix
armeabi-v7a<nk>/platforms/android-<version>/arch-arm/usr
arm64-v8a<nk>/platforms/android-<version>/arch-arm64/usr
x86<nk>/platforms/android-<version>/arch-x86/usr
x86_64<nk>/platforms/android-<version>/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:

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

The build.gradle for your app then looks like below.

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'
        }
    }
}

See the official documentation about ABIs for more information.

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 Debug, Warning and Error output to Android log buffer. so it can be accessed through the adb logcat utility. See Corrade::Utility::AndroidLogStreamBuffer for more information.

The Platform::AndroidApplication sets this up implicitly with a magnum tag, you can then filter it out like this, for example:

$ 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

Using system-wide CMake installation

According to the official documentation, it's possible to use system CMake installation without needing to install Android SDK version of CMake 3.6. Simply update the externalNativeBuild in your build.gradle file to specify CMake version that you have installed in your system, for example:

android {
    ...
    externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
            ...
            version '3.10.2'
        }
    }
}

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.

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:

task wrapper(type: Wrapper) {
    gradleVersion = '4.0'
}

Then run this on a system that has Gradle installed:

gradle wrapper

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 gradlew instead of gradle.

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, build-tools-22.0.1 and android-22 are always present, so your builds shouldn't get any extra delay when requesting them. The $TARGET environment variable is used here only to disambiguate later, you might or might not need it.

matrix:
  include:
  # ...
  - language: android
    os: linux
    dist: trusty
    env:
    - TARGET=android
    android:
      components:
      - build-tools-22.0.1
      - android-22

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 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 $HOME/cmake, with $PATH setup and caching:

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

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 with only the things you need to speed up the build. Again, downlading it into $HOME/android-ndk-r16b is a matter of adding this into your install: section:

- >
  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

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.

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 \
    ...

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:

matrix:
  include:
  - language: android
    # ...
    android:
      components:
      # ...
      - sys-img-armeabi-v7a-android-22

As described in the Travis documentation, create a system image and wait for the emulator to start (be prepared, it can easily take up minutes). Assuming you use the Corrade::TestSuite Android integration, simply run your tests via ctest and optionally enable colored output for extra clarity:

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

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 template above. In particular, the classpath needs to be updated, compileSdkVersion and minSdkVersion adapted to versions defined in components: in your travis.yml file and the buildToolsVersion explicitly specified, because that's needed in plugin versions before 3.0:

buildscript {
    // ...
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

// ...

android {
    compileSdkVersion 22
    buildToolsVersion '26.0.2'

    defaultConfig {
        minSdkVersion 22
        // ...

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:

before_install:
- if [ "$TARGET" == "android" ]; then yes | sdkmanager "cmake;3.6.4111459"; fi

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 $HOME/android-ndk-r16b, the install prefixes look like this:

cmake .. \
    -DCMAKE_INSTALL_PREFIX=$HOME/android-ndk-r16b/platforms/android-22/arch-arm64/usr \
    -DCORRADE_INCLUDE_INSTALL_PREFIX=$HOME/android-ndk-r16b/sysroot/usr \
    ...

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:

export ANDROID_NDK_HOME=$HOME/android-ndk-r16b
export CMAKE_PREFIX_PATH=$HOME/deps-native/

gradle build

Troubleshooting

Signing the APK using CMake fails

At the moment the location and passphrase for the Android signing keystore is implicitly set to $HOME/.android/debug.keystore with android as a password, since that's the location (and password) that Gradle uses. If you see output similar to this:

[1/2] Signing my-application.apk
FAILED: my-application.apk

Failed to load signer "signer #1"
java.io.FileNotFoundException: <home>/.android/debug.keystore

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:

Failed to load signer "signer #1"
java.io.IOException: Keystore was tampered with, or password was incorrect

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 or using the keytool utility that's bundled with Java SDK.

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:

cmake . -DANDROID_APKSIGNER_KEY="--ks;/etc/android.keystore;--ks-pass;pass:secret"

You can also add further arguments to apksigner here. See the official documentaton for apksigner for details.

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:

export CMAKE_PREFIX_PATH=<path-where-your-dependencies-are-installed>
gradle build

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 Building for multiple ABIs and system versions above for more information.

App can't launch

If your application can't launch (or it just blinks and then disappears), you can inspect adb logcat 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 abiFilters 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 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 Enabling or disabling features for more information.
  • Loading fails with ANativeActivity_onCreate symbol not being found. If you are using 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 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 for details.

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:

$ gradle build

FAILURE: Build failed with an exception.

  What went wrong:

> Failed to notify project evaluation listener.
   > javax/xml/bind/annotation/XmlSchema
$ 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

Solution is to point $JAVA_HOME to a Java 8 installation, for example. This affects not just gradle, but also other tools like sdkmanager (see below), so you may want to export it for the whole session:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk/
gradle build

On ArchLinux, Java 8 is provided by the jdk8-openjdk package, which is pulled in as a dependency of the android-sdk and gradle packages.

Gradle aborting due to termcap

If you see the following output, Gradle is crashing because $TERM is set to xterm-256color or xterm-24:

$ gradle build

FAILURE: Build failed with an exception.

  What went wrong:
Could not open terminal for stdout: could not get termcap entry

Solution is to set TERM=xterm. See gradle/gradle#4440 for more information.

TERM=xterm gradle build

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):

sdkmanager --licenses # and then manually accept all of them

The tool doesn't provide any diagnostic output if the accepting failed, so be sure to verify that everything went well by executing sdkmanager --licenses again. If it offers the same licenses again, you might want to force it with sudo.

If the tool blows up with the following error, it's again because the Java version is too new, see above for a solution:

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
        at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)

Android SDK directory permissions

Gradle is able to work with system-installed Android SDK. If it complains about directory permissions such as

> 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.

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 and android-platform-25.