Skip to content
Search

Android:NDK

lastmod 2023-04-03created 2014-03-1216 edits

The NDK(Native development kit) is a toolset that allows you to implement parts of your app using native-code languages such as C and C++. For certain types of apps, this can be helpful so that you may reuse existing code libraries written in these languages and possibly increased performance.

Category

NDK Example

JNI

ETC

How to compile

Compile with the NDK as follows. (Note that the working directory is the directory containing .project, based on the Eclipse project.)

$ ndk-build NDK_APP_APPLICATION_MK=[MAKEFILE_PATH]

The important point (with the default settings) is that the directory containing the Makefile is, by default, the ${workspace_loc:/projectname}/jni/ directory, and the default file name is Android.mk.

With the default settings, the output files (Library files, Object files) are generated in the ${workspace_loc:/projectname}/libs/ and ${workspace_loc:/projectname}/obj/ directories, respectively. (${workspace_loc:/projectname} denotes the Root directory of the project corresponding to projectname in Eclipse.)

Using STL in the Android NDK

After creating the Application.mk, add this line of code:

#둘 중 하나를 선택하면 된다.
#STLPORT 구현체.
APP_STL := stlport_static
#GNUSTL 구현체.
APP_STL := gnustl_static

Debugging the Android NDK

To keep debugging information on the C/C++ side of the Android NDK, add the following to the APPLICATION.mk file and set debuggable="true" in the AndroidManifest.xml file.

APP_OPTIM:=debug
NDK_DEBUG:=1

Linking libraries in multiple folders

During a project you sometimes need to link libraries split across multiple folders, but the NDK samples and other code all link only libraries in a single folder. The way to do it is to list all the folders you want in LOCAL_LDLIBS, as below.

# libadd.so 는 LibTest1/libs/armeabi 에
# libsubtract.so  는 LibTest2/libs/armeabi 에 있다고 가정합니다.
LOCAL_LDLIBS := -L$(call host-path, $(LOCAL_PATH)/../../LibTest1/libs/armeabi) \
                          -L$(call host-path, $(LOCAL_PATH)/../../LibTest2/libs/armeabi) \
                          -ladd -lsubtract

One caveat when doing this is that static library linking works fine, but dynamic library linking produces link errors at runtime. This is because dynamic library linking only checks simple information at build time, so the actual module has to be loaded separately (using the System.loadLibrary() method).

The $(~ProjectDir)/libs/armeabi folder of the project that actually uses the libraries is entirely deleted when ndk-build runs, so after the build finishes, copy all the Dynamic libraries into the $(~ProjectDir)/libs/armeabi folder. It would be convenient to have a shell script copy them automatically.

Building a static library with the NDK

Most examples that use the NDK build dynamic libraries. Occasionally, though, you need a static library. So this time, let's look at how to build a static library and a few points to watch out for. For the basic way to build a static library, refer to the $(NDK)/samples/two-libs example. The two-libs makefile looks like this.

LOCAL_PATH:= $(call my-dir)

# first lib, which will be built statically
include $(CLEAR_VARS)
LOCAL_MODULE    := libtwolib-first                           # static library 이름
LOCAL_SRC_FILES := first.c                                     # static library 에 빌드될 소스 파일
include $(BUILD_STATIC_LIBRARY)                          # static library 빌드

# second lib, which will depend on and include the first one
include $(CLEAR_VARS)
LOCAL_MODULE    := libtwolib-second                     # dynamic library 이름
LOCAL_SRC_FILES := second.c                               # dynamic library 에 빌드될 소스 파일. 빌드할 소스가 없다면 생략될 수 있다.
LOCAL_STATIC_LIBRARIES := libtwolib-first              #  같이 빌드할 static library 이름
include $(BUILD_SHARED_LIBRARY)                         # dynamic library 빌드

As you can see above, it is split into a part that first builds the static library and a second part that builds the dynamic library. The caveats are as follows.

  • Even if you only build a static library, a part that builds a dynamic library must be present.
  • In this case, the dynamic library part does not need a LOCAL_SRC_FILES entry.
  • The code in the static library part must not contain JNI-related code.
  • To use JNI-related code, the dynamic library has to be loaded and used from the Java layer, so if it goes into the static library, link errors occur at runtime.
  • Therefore, common general-purpose modules should be built as static libraries and JNI-related parts as dynamic libraries.
  • After the build, the dynamic library file (.so) is copied to the $(ProjectDir)/libs/armeabi folder, while the static library file (.a) remains in the $(ProjectDir)/obj/local/armeabi folder.

If a previously built Dynamic (Shared) library exists, specify the *.so file as the LOCAL_SRC_FILES value.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := libffmpeg.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/ffmpeg
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := ffmpeg-bypasser
LOCAL_SRC_FILES := ffmpeg-bypasser.c ffmpeg-command.c cmdutils.c util.c
LOCAL_SHARED_LIBRARIES += ffmpeg
LOCAL_LDLIBS := -llog
LOCAL_CFLAGS := -Wno-deprecated-declarations
include $(BUILD_SHARED_LIBRARY)

How to use --sysroot

Gives a hint on how to use --sysroot.

$ <toolchain>/arm-linux-androideabi-gcc --sysroot=<toolchain>/sysroot test.c

Getting the Android Context

...
// Passes cache directory path from native code to GDScript
godot_variant android_gdnative_test(godot_object *p_instance, void *p_method_data
    , void *p_user_data, int p_num_args, godot_variant **p_args)
{
    // Get JNIEnv* from my function that extends godot_gdnative_core_api_struct
    JNIEnv* env = api->godot_android_get_env();

    // Get context - see https://stackoverflow.com/questions/46869901/how-to-get-the-android-context-instance-when-calling-jni-method    
    jclass activityThread = env->FindClass("android/app/ActivityThread");
    jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
    jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread);
    jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;");
    jobject context = env->CallObjectMethod(at, getApplication);

    // Get path to cache directory
    jclass contextClass = env->FindClass("android/content/Context");
    jclass fileClass = env->FindClass("java/io/File");
    jmethodID getCacheDir = env->GetMethodID(contextClass, "getCacheDir", "()Ljava/io/File;"); 
    jmethodID getAbsolutePath = env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;");    
    jobject file = env->CallObjectMethod(context, getCacheDir);  
    jstring str = (jstring)env->CallObjectMethod(file, getAbsolutePath);     
    const char *cacheDir = env->GetStringUTFChars(str, 0);              

    // Pass cacheDir to GDScript
    ...
}
...

Documentation

Application.mk translation
Application-mk_ko.txt

See also

Favorite site

References


  1. Jni_tips.pdf ↩

  2. Android_mk_syntax.pdf ↩

author Zer0 sitemap