Embedding native code
Let's stick to the topic of this book and write some native C++ code for our template application. We will start with the jni/Wrappers.cpp
file, which will contain a single function definition:
#include <stdlib.h> #include <jni.h> #include <android/log.h> #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "NDKApp", __VA_ARGS__)) extern "C" { JNIEXPORT void JNICALL Java_com_packtpub_ndkmastering_AppActivity_onCreateNative( JNIEnv* env, jobject obj ) { LOGI( "Hello Android NDK!" ); } }
This function will be called from Java using the JNI mechanism. Update AppActivity.java
as follows:
package com.packtpub.ndkmastering; import android.app.Activity; import android.os.Bundle; public class AppActivity extends Activity { static { System.loadLibrary( "NativeLib" ); } @Override protected void onCreate( Bundle icicle ) { super.onCreate( icicle ); onCreateNative(); } public static native void onCreateNative(); };
Now, we have to build this code into an installable .apk
package. We need a couple of configuration files for this. The first one, jni/Application.mk
, contains the platform and toolchain information:
APP_OPTIM := release APP_PLATFORM := android-19 APP_STL := gnustl_static APP_CPPFLAGS += -frtti APP_CPPFLAGS += -fexceptions APP_CPPFLAGS += -DANDROID APP_ABI := armeabi-v7a-hard APP_MODULES := NativeLib NDK_TOOLCHAIN_VERSION := clang
We use the latest version of the Clang compiler—that is 3.6, as we write these lines, and the armeabi-v7a-hard
target, which enables support of hardware floating point computations and function arguments passing via hardware floating point registers resulting in a faster code.
The second configuration file is jni/Android.mk
, and it specifies which .cpp
files we want to compile and what compiler options should be there:
TARGET_PLATFORM := android-19 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NativeLib LOCAL_SRC_FILES += Wrappers.cpp LOCAL_ARM_MODE := arm COMMON_CFLAGS := -Werror -DANDROID -DDISABLE_IMPORTGL ifeq ($(TARGET_ARCH),x86) LOCAL_CFLAGS := $(COMMON_CFLAGS) else LOCAL_CFLAGS := -mfpu=vfp -mfloat-abi=hard -mhard-float -fno-short-enums -D_NDK_MATH_NO_SOFTFP=1 $(COMMON_CFLAGS) endif LOCAL_LDLIBS := -llog -lGLESv2 -Wl,-s LOCAL_CPPFLAGS += -std=gnu++11 include $(BUILD_SHARED_LIBRARY)
Here, we link against OpenGL ES 2, specify compiler switches to enable the hardware floating point for non-x86 targets and list the required .cpp
source files.
Use the following command from the root folder of the project to build the native code:
>ndk-build
The output should be as follows:
>ndk-build [armeabi-v7a-hard] Compile++ arm : NativeLib <= Wrappers.cpp [armeabi-v7a-hard] SharedLibrary : libNativeLib.so [armeabi-v7a-hard] Install : libNativeLib.so => libs/armeabi-v7a/libNativeLib.so
The last thing is to tell Gradle that we want to pack the resulting native library into the .apk
. Edit the build.gradle
file and add the following line to the main
section of sourceSets
:
jniLibs.srcDirs = ['libs']
Now, if we run the command gradle build
, the resulting package build\outputs\apk\3_NDK-debug.apk
will contain the required libNativeLib.so
file. You can install and run it as usual. Check the line Hello Android NDK! printed into the Android system log with adb logcat
.