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
- NDK Samples
- Iconv:Android:NDK: Android NDK with iconv.
- FreeType:Android:NDK: Android NDK에서 FreeType을 사용하는 방법.
JNI
ETC
- Android:Debugging:Wireless - Android 무선 디버깅 방법
How to compile
NDK 컴파일방법은 아래와 같다. (참고로 작업 디렉터리는 Eclipse프로젝트 기준으로 .project
가 존재하는 디렉터리이다.)
(기본설정에서) 중요한점은 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을 사용하는 방법
- http://www.41post.com/3527/programming/import-stl-libraries-to-android-ndk-code
- http://blog.naver.com/PostView.nhn?blogId=websearch&logNo=70144232656
After creating the Application.mk
, add this line of code:
Android NDK 디버깅 방법
Android NDK의 C/C++측에 디버깅정보를 남기고 싶을 경우 APPLICATION.mk
파일에 아래와 같이 추가하고 AndroidManifest.xml파일의 debuggable="true"
로 하면 된다.
여러 폴더에 있는 라이브러리 링크하기
프로젝트를 진행하다보면 여러 폴더에 나뉘어 있는 라이브러리를 링크해야 할 일이 생기는데, 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
사용방법에 대한 힌트를 제공한다.
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
- NDK Overview
- Android NDK download page
- 샘플: hello-jni | Android NDK | Android Developers
- Eclipse CDT JNI Project
- Android NDK와 C++ 언어로 개발방법
- Hello Android NDK example
- JNI환경에서 Thread 사용시 Error 발생 해결방법