Android NDK Adventures
2015-08-17
Tips and tricks for Android NDK projects.
Dependencies: Compiling C/C++ Libraries from Source
Makefile Projects
http://kvurd.com/blog/compiling-a-cpp-library-for-android-with-android-studio/
The configure script should be given the argument
--host arm-linux-androideabi
instead of--host-arm-linux-androideabi
(notice the extra-
).If compilation fails saying
arm-linux-androideabi
is not recognised, update theconfig.guess
andconfig.sub
files from http://git.savannah.gnu.org/gitweb/?p=config.git;a=tree as described here.
build.sh
#!/bin/sh
# Points to a standalone Android toolchain
# See https://developer.android.com/ndk/guides/standalone_toolchain.html
# and http://www.myandroidonline.com/2015/06/09/
compile-assimp-open-source-library-for-android/
export ANDROID_NDK_TOOLCHAIN=$HOME/android/toolchain-armeabi
# Do not touch these
export PATH=$ANDROID_NDK_TOOLCHAIN/bin:$PATH
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
make install
The build.sh
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
http://www.myandroidonline.com/2015/06/09/compile-assimp-open-source-library-for-android/
- Download android CMake files from https://github.com/taka-no-me/android-cmake
build.sh
#!/bin/sh
# Points to a standalone Android toolchain
# See https://developer.android.com/ndk/guides/standalone_toolchain.html
# and http://www.myandroidonline.com/2015/06/09/
compile-assimp-open-source-library-for-android/
export ANDROID_NDK_TOOLCHAIN=$HOME/android/toolchain-armeabi
# Points to the Android SDK
ANDROID_SDK=$HOME/android/sdk
# This file must be downloaded from the android-cmake project on github:
# https://github.com/taka-no-me/android-cmake
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
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDTOOLCHAIN \
-DANDROID_NDK=$ANDROID_NDK \
-DCMAKE_BUILD_TYPE=Release \
-DANDROID_ABI="armeabi-v7a" \
-DANDROID_NATIVE_API_LEVEL=android-21 \
-DANDROID_FORCE_ARM_BUILD=TRUE \
-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,libassimp.so.3/-soname,libassimp.so/g \
code/CMakeFiles/assimp.dir/link.txt
make
make install
Shared Library Patching
http://computervisionandjava.blogspot.com.es/2015/05/trouble-with-versioned-shared-libraries.html http://stackoverflow.com/questions/11491065/linking-with-versioned-shared-library-in-android-ndk
Most C/C++ projects / build scripts generate versioned shared
libraries. For example, many scripts will generate a
libfoo.so.x.y
, a libfoo.so.x
, and a
libfoo.so
, the former being the actual library and the
latter being soft links. Android does not support versioned libs and
expects a simple libfoo.so
file, however, so if a simple
libfoo.so
can’t be generated, the versioned shared library
libfoo.so.x.y.z
must be patched.
To “unversion” a shared library
libfoo.so.x.y.z
:
- Run
objdump -p libfoo.so.x.y.z | grep so
. - Run
rpl -R -e libxxx.so.y libxxx_y.so libfoo.so.x.y.z
to renamelibxxx.so.y
tolibxxx_y.so
in the exports table. Also rename the soname following the same rule. - Rename the shared library to match its soname and rename its dependencies to match the new names.
This works because libfoo.so.x
and
libfoo_x.so
have the same length. If we patch a name with a
differently sized string, the resulting shared library becomes
corrupted.
Example:
Running objdump
on a library libfoo
:
libfoo.so.x.y.z: file format elf32-little
NEEDED libbar.so.3
NEEDED libbaz.so.15
SONAME libfoo.so.x
Patching the dependency table and soname:
libfoo.so.x.y.z: file format elf32-little
NEEDED libbar_3.so
NEEDED libbaz_15.so
SONAME libfoo_x.so
And then renaming all files:
libfoo.so.x.y.z -> libfoo_x.so
libbar.so.3 -> libbar_3.so
libbaz.so.15 -> libbaz_15.so
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 Android.mk
and
Application.mk
:
Project/
app/
build/
gradle/
...
jni/
Android.mk
Application.mk
main.cc
Here we assume the typical project layout that is created when starting a new project using Android Studio.
To build:
- Run
ndk-build
insidejni
. This will generate the main shared library and copy the library and its dependencies toProject/libs
. - Copy
libs/*
toapp/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 libfoo.so, pass "foo" to System.loadLibrary().
static {
System.loadLibrary("C"); // libC.so
System.loadLibrary("B"); // libB.so
System.loadLibrary("A"); // libA.so
}
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.