Mastering Android NDK
上QQ阅读APP看书,第一时间看更新

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.

Note

Those who do not want to tackle Gradle in such a small project without dependencies will be able to use good old Apache Ant. Just run the command ant debug to make it happen. No additional configuration files are required to put shared C++ libraries into .apk this way.