Skip to content

Android:NDK

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

NDK 컴파일방법은 아래와 같다. (참고로 작업 디렉터리는 Eclipse프로젝트 기준으로 .project가 존재하는 디렉터리이다.)

$ ndk-build NDK_APP_APPLICATION_MK=[MAKEFILE_PATH]

(기본설정에서) 중요한점은 Makefile이 존재하는 디렉터리는 기본(Default)설정으로, ${workspace_loc:/projectname}/jni/ 디렉터리이며, 기본(Default) 파일명은 Android.mk이다.

기본(Default)설정에서 결과파일(Library files, Object files)이 생성되는 디렉터리는 각각 ${workspace_loc:/projectname}/libs/, ${workspace_loc:/projectname}/obj/ 디렉터리이다. (${workspace_loc:/projectname}Eclipse의 projectname에 해당하는 프로젝트의 Root 디렉터리를 나타낸다)

Android NDK에서 STL을 사용하는 방법

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

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

Android NDK 디버깅 방법

Android NDK의 C/C++측에 디버깅정보를 남기고 싶을 경우 APPLICATION.mk 파일에 아래와 같이 추가하고 AndroidManifest.xml파일의 debuggable="true"로 하면 된다.

APP_OPTIM:=debug
NDK_DEBUG:=1

여러 폴더에 있는 라이브러리 링크하기

프로젝트를 진행하다보면 여러 폴더에 나뉘어 있는 라이브러리를 링크해야 할 일이 생기는데, NDK 샘플이나 여타의 코드를 보면 전부 한 폴더에 있는 라이브러리만 링크하고 있더군요. 방법은 아래처럼 LOCAL_LDLIBS에 원하는 폴더를 모두 적어주면 됩니다.

# 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

위와 같이할 경우 한 가지 주의점은 static library 링크는 문제가 없는데 dynamic library 링크는 런타임시에 링크 에러가 발생한다는 것 입니다. Dynamic library링크는 빌드시에 단순한 정보들만을 확인하므로 실제 모듈은 별도로 로딩을 해 주어야 하기 때문이죠. (System.loadLibrary()메서드를 사용해서)

실제로 라이브러리를 사용하는 프로젝트의 $(~ProjectDir)/libs/armeabi폴더는 ndk-build 를 실행하면 모두 삭제되므로, 빌드가 끝난 후 모든 Dynamic library를 $(~ProjectDir)/libs/armeabi 폴더로 복사시켜 주면 됩니다. 쉘 스크립트로 자동으로 복사되게끔 하면 편리하겠죠.

NDK 에서 static library 빌드 하기

NDK를 사용하는 예제들을 보면 대부분 dynamic library 를 빌드하는 예제인데요. 간혹, static library 가 필요할 때가 있죠. 그래서 이번에는 static library 빌드하는 방법과 몇 가지 주의할 점을 알아보겠습니다. Static library 빌드하는 기본적인 방법은 $(NDK)/samples/two-libs 예제를 참고하시면 됩니다. two-libs의 메이크 파일은 아래와 같은 내용인데요.

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 빌드

위의 내용을 보시면 먼저 static library 를 빌드하고 두 번째로 dynamic library 를 빌드하는 부분으로 나눠져 있는데, 주의점은 아래와 같습니다.

  • static library 만 빌드하더라도 반드시 dynamic library 를 빌드하는 부분이 나와야 합니다.
  • 이 때는 dynamic library 부분의 LOCAL_SRC_FILES 항목이 없어도 됩니다.
  • static library 부분의 코드에는 JNI 관련 코드가 들어가서는 안 됩니다.
  • JNI 관련 코드를 사용하려면 Java 층에서 dynamic library 를 불러들여 사용해야 하기 때문에, static library 에 들어가게 되면 런타임시 링크 에러가 발생하게 됩니다.
  • 때문에, 일반적인 공통 모듈은 static library 로 빌드하고 JNI 관련 부분은 dynamic library 로 빌드해야 합니다.
  • 빌드 후 dynamic library 파일(.so)은 $(ProjectDir)/libs/armeabi 폴더에 복사되고, static library 파일(.a)은 $(ProjectDir)/obj/local/armeabi 폴더에 그대로 남아 있습니다.

만약, 기존에 빌드된 Dynamic(Shared) library가 존재할 경우 LOCAL_SRC_FILES값으로 *.so파일을 지정하면 된다.

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

--sysroot 사용방법에 대한 힌트를 제공한다.

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

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 번역
Application-mk_ko.txt

See also

Favorite site

References


  1. Jni_tips.pdf 

  2. Android_mk_syntax.pdf