Android NDK Adventures

This is meant to serve as a reference and quick tutorial for future NDK projects. Going through this hassle once was more than enough.

Dependencies: Compiling C/C++ Libraries from Source

Makefile Projects


# Points to a standalone Android toolchain
# See
# and
export ANDROID_NDK_TOOLCHAIN=$HOME/android/toolchain-armeabi

# Do not touch these
export CC="arm-linux-androideabi-gcc"
export CXX="arm-linux-androideabi-g++"

# Add additional args here as appropriate
../configure --prefix=$(pwd) --host arm-linux-androideabi

make install

The script is meant to be placed inside a directory such as build/ inside the project's source tree, to avoid polluting the project's root directory.

CMake Projects


# Points to a standalone Android toolchain
# See
# and
export ANDROID_NDK_TOOLCHAIN=$HOME/android/toolchain-armeabi

# Points to the Android SDK

# This file must be downloaded from the android-cmake project on github:
export ANDTOOLCHAIN=$HOME/android/android-cmake/android.toolchain.cmake

# Do not touch these
export PATH=$PATH:$ANDROID_SDK/tools
export PATH=$PATH:$ANDROID_SDK/platform-tools
export PATH=$PATH:$ANDROID_SDK/android-toolchain/bin

# Add additional args here as appropriate
      -DCMAKE_BUILD_TYPE=Release \
      -DANDROID_ABI="armeabi-v7a" \
      -DANDROID_NATIVE_API_LEVEL=android-21 \
      -DCMAKE_INSTALL_PREFIX=install \

# This is to remove the versioned shared libs in assimp.
# Might be useful for other CMake-based projects, otherwise remove this line.
sed -i s/-soname,, code/CMakeFiles/assimp.dir/link.txt

make install

Shared Library Patching

Most C/C++ projects / build scripts generate versioned shared libraries. For example, many scripts will generate a, a, and a, the former being the actual library and the latter being soft links. Android does not support versioned libs and expects a simple file, however, so if a simple can't be generated, the versioned shared library must be patched.

To "unversion" a shared library

  1. Run objdump -p | grep so.
  2. Run rpl -R -e to rename to in the exports table. Also rename the soname following the same rule.
  3. Rename the shared library to match its soname and rename its dependencies to match the new names.

This works because and have the same length. If we patch a name with a differently sized string, the resulting shared library becomes corrupted.


Running objdump on a library libfoo:     file format elf32-little

Patching the dependency table and soname:     file format elf32-little

And then renaming all files: ->     ->    ->

Building the Main C++ Project

Android Studio's support for the NDK is essentially unusable at the time of writing, which is remarkable, to say the least.

Create a directory jni and the place source files there, together with and


Here we assume the typical project layout that is created when starting a new project using Android Studio.

To build:

  1. Run ndk-build inside jni. This will generate the main shared library and copy the library and its dependencies to Project/libs.
  2. Copy libs/* to app/src/main/jniLibs.

The libraries in jniLibs are automatically packed with the APK when the Java project is built.

Calling the C++ Library from Java

Java Side

public class Native {

    // Libraries must be loaded from least dependent to most dependent.
    // If X depends on Y and Y is not already loaded upon loading X,
    // a linker error will occur.
    // If library file is, pass "foo" to System.loadLibrary().
    static {
        System.loadLibrary("C"); //
        System.loadLibrary("B"); //
        System.loadLibrary("A"); //

    public static native String func1();
    public static native void func2();

C++ Side

Functions must follow a specific naming convention. If we had a function

void foo(int x)

and we wanted to call it from class Native inside package com.example.hello, then the function signature would have to be:

JNIEXPORT void JNICALL Java_com_example_hello_Native_foo(JNIEnv*, jclass, jint x)

See the JNI documentation for details.