IT_Programming/Dev Tools

[Android Studio] Android Studio에서 NDK 빌드하기

JJun ™ 2015. 4. 6. 00:16



 출처: http://blog.naver.com/chc07ktm/220198738523

        http://i5on9i.blogspot.kr/2015/02/android-studio-ndk-hello-world.html

        http://mydevspace.blogspot.kr/2015/03/110-ndk.html



eclipse에서 Android studio로 옮긴뒤, ndk를 빌드해야할 일이 생겼습니다.

(역어샘블링을 힘들게 하기위해서...)

 

기본 안드로이 개발자 사이트에는 Comming soon으로 되어 있어 불가능한줄 알았지만, 

구글선생님 덕분에 아주 쉽게 셋팅했습니다. 

 

설명은 ndk에 기본 샘플인 hello-jni를 기준으로 하겠습니다. 

 

먼저 android studio에서 hello-jni프로젝트를 오픈합니다.

 

 



 

소스를 담기위한 기본 폴더를 위와 같이생성해줍니다. 

 - src/main 폴더 밑에 jni폴더와 libs폴더를 만들어 줍니다. 

 




 

local.properties파일에 다음과 같이 ndk경로를 추가해 줍니다.

ndk.dir=/Users/youngchilcho/Documents/Development/android/ndk/android-ndk-r10c

 

 



 

마지막으로 build.gradle파일내에 jni컴파일을 위한 작업을 추가합니다.

------------------------------------------------------------------------------------------------------------------------------------

ndk {

            moduleName "hello-jni"

}

 

sourceSets.main{

            jni.srcDirs = []

            jniLibs.srcDir 'src/main/libs'

}

 

task buildNative(type: Exec, description: 'Compile JNI source via NDK') {

            def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()

            commandLine "$ndkDir/ndk-build",

                    '-C', file('src/main/jni').absolutePath,

                    '-j', Runtime.runtime.availableProcessors(),

                    'all',

                    'NDK_DEBUG=1'

        }

 

task cleanNative(type: Exec, description: 'Clean JNI object files') {

            def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()

            commandLine "$ndkDir/ndk-build",

                    '-C', file('src/main/jni').absolutePath,

                    'clean'

        }

 

        clean.dependsOn 'cleanNative'

 

        tasks.withType(JavaCompile) {

            compileTask -> compileTask.dependsOn buildNative

        }

------------------------------------------------------------------------------------------------------------------------------------

 

혹시나 소스가 필요한 분들을위해 컴파일 가능한 (ndk 경로는 사용자의 설정에 맞게 셋팅하세요 :) 소스를 첨부합니다.



참고로 샘플에 사용된 ndk 경로는 아래와 같습니다.



 

 






Anroid studio 에서 NDK 사용하기

Android studio 에서 NDK 를 사용해 보자. 이 글은 아래 동영상의 내용보면서 작성한 글이다.

 



class 에 jni definition 넣고 rebuild

class 에 jni 정의 를 넣어주고, project 를 rebuild 한다.(이 때 생긴 class 를 이용해서 javah 를 실행하게 된다.)



javah 실행

rebuild project 를 해준다.

D:\mine\programming\androidStudio\AndroidSamples\NdkHelloWorld\app\src\main>"c:\Program Files\Java\jdk1.7.0_25\bin\javah.exe" -d jni -classpath "D:\Program Files\Android\sdk\platforms\android-19\a
ndroid.jar";"d:\Program Files\Android\sdk\extras\android\support\v7\appcompat\libs\android-support-v7-appcompat.jar";"d:\Program Files\Android\sdk\extras\android\support\v7\appcompat\libs\android-
support-v4.jar";..\..\build\intermediates\classes\debug com.namh.ndkhelloworld.MainActivity


그러면 jni folder 가 생성된다.





.c 만들기

이 header 파일중에 우리는 MainActivity.h 에 jni 함수정의를 넣어놨으니, *_MainActivity.h 를 확인하면 함수정의를 찾을 수 있다. 이 함수정의를 이용해서 이제 .c 를 만들자.

// com_namh_ndkhelloworld_MainActivity.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_namh_ndkhelloworld_MainActivity */
#ifndef _Included_com_namh_ndkhelloworld_MainActivity
#define _Included_com_namh_ndkhelloworld_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_namh_ndkhelloworld_MainActivity_MODE_PRIVATE
#define com_namh_ndkhelloworld_MainActivity_MODE_PRIVATE 0L
#undef com_namh_ndkhelloworld_MainActivity_MODE_WORLD_READABLE
#define com_namh_ndkhelloworld_MainActivity_MODE_WORLD_READABLE 1L
...
...
...
/*
 * Class:     com_namh_ndkhelloworld_MainActivity
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_namh_ndkhelloworld_MainActivity_stringFromJNI
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif


아래처럼 .c 를 만들어 줬다.

// hello.c
#include "com_namh_ndkhelloworld_MainActivity.h"
JNIEXPORT jstring JNICALL Java_com_namh_ndkhelloworld_MainActivity_stringFromJNI
  (JNIEnv * env, jobject obj)
  {
    return (*env)->NewStringUTF(env, "hello jni !");
  }


No rule to make target

.c 가 한개밖에 없을 때 "No rule to make target" 를 내뿝는다. 이게 windows ndk bug 라고 하니, 빈 .c 파일을 하나 만들어서 추가하자.



local.properties 에 ndk.dir 추가

이제 ndk 경로를 추가해 주자. <project_path>/local.properties 에 ndk.dir 을 추가해 주면 된다. 단, 주의할 것은 공백(space) 가 있는 경로는 안된다.[ref. 2]

sdk.dir=D\:\\Program Files\\Android\\sdk
ndk.dir=D\:\\Android\\ndk\\android-ndk-r10d



library 를 application 이 시작할 때 load 하기 위해서 activity class 에
static {
        System.loadLibrary("hello-jni");
    }
를 넣어주자. 이 때 "hello-jni" 는 library 의 이름이 되는데, 이녀석은 app/build.gradle 에 정의해 줘야 한다.

apply plugin: 'com.android.application'
android {
    ...
    defaultConfig {
        applicationId "com.namh.ndkhelloworld"
        minSdkVersion 16
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
        ndk{
            moduleName "hello-jni"
        }
    }
    ...
}


제대로 compile 이 되면 아래 그림처럼 .so 들이 만들어진다.




이제 Make project 를 다시 실행하면 된다.

이제 실제 동작하는지 activity 에서 jni 함수를 넣은 code 를 작성해서 한 번 실행 해 보면 된다.



Custom Android.mk 사용하기

jni.srcDirs=[]

ref.4 를 보면 gradle Android plugin 에서 convention 으로 잡혀있는 path 를 바꿀 수 있는데, jni 도 그렇게 바꿀 수 있다.

Build project 를 실행할 때 jni 가 folder 가 있으면 자동으로 NdkCompile task(code : NdkCompile.groovy) 를 실행되게 되는데,[ref. 2]

  • jni.srcDirs = []


를 하면 jni 가 없다는 뜻이 되고, jni 에 대한 compile(NdkCompile) 을 시도하지 않을 것이다.


그러면 원래 NdkCompile 이 ndk-build 를 수행하면서 만드는 아래의 파일들을 만들지 않게 된다.

  • <project_path>\app\build\intermediates\ndk\debug
  • 자동으로 만들어지는 파일
    • Android.mk
    • lib/<eabi_folder>/libhello-jni.so


android{
  sourceSets.main {
        jniLibs.srcDir ' src/main/libs'
        jni.srcDirs = []    //disable automatic ndk-build call with auto-generated Android.mk
    }




tasks.withType(JavaCompile)

tasks.withType(JavaCompile) 을 추가해 주자. 이 부분에서 compile 시에 ndkLibsToJar 이 호출되도록 dependency 를 추가해 주게 된다.

참고로 이전에는 tasks.withType(Compile) 도 됐는데, 현재는 안된다. ref. 5를 참고하자.

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn ndkLibsToJar
}



ndkLibsToJar

이제 ndkLibsToJar task 가 필요하다. ndkLibsToJar 은 ref. 2에서 가져왔다. 여기에 보면 dependsOn 에 'ndkBuild' 가 있다. 이제 ndkBuild task 를 만들자.

task ndkLibsToJar(type: Zip, dependsOn: 'ndkBuild', description: 'Create a JAR of the native libs') {
    destinationDir new File(buildDir, 'libs')
    baseName 'ndk-libs'
    extension 'jar'
    from(new File(buildDir, 'libs')) { include '**/*.so' }
    into 'lib/'
}



ndkBuild

task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    println('executing ndkBuild')
    commandLine "echo", "hello world"
}

ndkBuild 가 잘 동작하는지 확인하는 차원에서 ref. 2 에서 처럼 hello world 를 한 번 찍어보자. 이부분을 나중에는 아래처럼 수정할 것이다.

task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
    println(ndkDir)
    commandLine "$ndkDir/ndk-build.cmd",
            'NDK_PROJECT_PATH=build',
            'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
            'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}


이제, Build > Make Project 를 하자.




위처럼 executing ndkBuild 는 task 를 define 하는 시점에 실행되고, commandLine 부분은 실제 task 가 실행되는 시점에 실행된다.[ref. 2] 그렇기 때문에 위와 같은 결과화면이 나온다.

이제 진짜 ndkBuild 부분을 넣고 Make project 를 실행하면 된다.

task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
    println(ndkDir)
    commandLine "$ndkDir/ndk-build.cmd",
            'NDK_PROJECT_PATH=build',
            'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
            'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}





source git




------------------------------------------------

gradle 을 이용해서 architecture 별로 build

이제 architecture 별로 build 할 수 있는 법을 살펴보자. 동영상은 아래 link 를 확인하면 된다.








source 는 ndk 의 sample source 를 참고했다.


<ndk_path>\android-ndk-r10d\samples\hello-jni\src\com\example\hellojni\HelloJni.java




  1. Android Project 만들기
  2. terminal 에서 javah 실행
  3. copy <hello-jni>\jni ---> <NdkHelloWorld>\app\src\main\jni
  4. <NdkHelloWorld>\app\src\main\libs 만들기 :ndk-build에서 출력 디렉토리를 설정할 수가 없기 때문에 결과 파일이 src->main->libs 에 생긴다[ref. 1]
  5. <NdkHelloWorld>\app\src\main\libs 아래 eabi platform 별로 directory 만들기
  6. <NdkHelloWorld>\local.properties 에 ndk.dir 추가
  7. <NdkHelloWorld>\app\build.gradle 에 ndk 관련 설정 추가(설명은 ref. 2에서 확인할 수 있다.)
    android {
        ...
        ndk {
            moduleName "hello-jni"
        }
        sourceSets.main{
            jni.srcDirs = []
            jniLibs.srcDir 'src/main/libs'
        }
        task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
            def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
            commandLine "$ndkDir/ndk-build",
                    '-C', file('src/main/jni').absolutePath,
                    '-j', Runtime.runtime.availableProcessors(),
                    'all',
                    'NDK_DEBUG=1'
        }
        task cleanNative(type: Exec, description: 'Clean JNI object files') {
            def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
            commandLine "$ndkDir/ndk-build",
                    '-C', file('src/main/jni').absolutePath,
                    'clean'
        }
        clean.dependsOn 'cleanNative'
        tasks.withType(JavaCompile) {
            compileTask -> compileTask.dependsOn buildNative
        }
    }
    



See Also

  1. JNI 동작원리 개념 - 1



References

  1. ndk-build를 Android Studio에서 사용하기 - Burt K.
  2. Using custom Android.mk with Gradle/Android Studio
  3. Android Studio, gradle and NDK integration | ph0b's
  4. Android Tools Project Site > 3.2.1 Configuring the Structure
  5. After upgrading to Gradle 2.0: Could not find property 'Compile' on root project








안드로이드 스튜디오 1.1.0 업데이트 + NDK




안드로이드 스튜디오를 좀 늦게 1.1.0 으로 업데이트 하고

설정에서 버전을 1.1.0 으로 변경
classpath 'com.android.tools.build:gradle:1.1.0'

그러면 NDK 를 사용하는 소스에서는 일부 에러가 발생함
전에 블로그에서 NDK 빌드하는 방법을 아래 링크와 같이


설명을 했는데 1.1.0 으로 업그레이드 하면 두가지 문제가 생겼음
첫번째는 “빌드시 build.gradle 파일 안에서 task() 함수를 못찾는 다는 에러가 발생”
두번째는 “getNdkFolder()” 에러 

첫번째는 task() 파일 함수가 defaultConfig 내부에 있으면 에러가 발생함
그래서 밖으로 빼내면 됨, 아래와 같이 

defaultConfig {
       applicationId "com.***.*******"
       minSdkVersion 19
       targetSdkVersion 21
       versionCode 1
       versionName "1.0.0"

       ndk {
           moduleName "mbcore"
       }

       sourceSets.main{
           jni.srcDirs = []
           jniLibs.srcDir 'src/main/libs'
       }
}

task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
       def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder()
       commandLine "$ndkDir/ndk-build",
               '-C', file('src/main/jni').absolutePath,
               '-j', Runtime.runtime.availableProcessors(),
               'all',
               'NDK_DEBUG=1'
}

task cleanNative(type: Exec, description: 'Clean JNI object files') {
       def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder()
       commandLine "$ndkDir/ndk-build",
               '-C', file('src/main/jni').absolutePath,
               'clean'
}


clean.dependsOn 'cleanNative'

tasks.withType(JavaCompile) {
       compileTask -> compileTask.dependsOn buildNative
}


전 소스와 비교해 보시면 어느 부분을 밖으로 뺏는지 확인 가능합니다.

그리고 getNdkFolder() 함수 못찾는 에러, 열심히 구글링을 한 결과
위 소스에도 나와 있지만

getNdkFolder()  이렇게 그냥 선언한 함수를
sdkHandler.getNdkFolder() 이렇게 sdkHandler 를 추가해주면 됨


[1.0.0 버전]
task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
       def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
       commandLine "$ndkDir/ndk-build",
               '-C', file('src/main/jni').absolutePath,
               '-j', Runtime.runtime.availableProcessors(),
               'all',
               'NDK_DEBUG=1'
}

[1.1.0 버전 ]
task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
       def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder()
       commandLine "$ndkDir/ndk-build",
               '-C', file('src/main/jni').absolutePath,
               '-j', Runtime.runtime.availableProcessors(),
               'all',
               'NDK_DEBUG=1'

}

모두 굿 개발 하세용 ^^