본문 바로가기

카테고리 없음

Hello Android NDK example

테스트 환경
JDK 1.6, Eclipse 3.5(ADT설치), Android SDK 2.1, Android NDK r4(Revision 4)-Windows, Cygwin 1.7.5, Android API Level 7
Android NDK 설치루트경로 : C:\Android_NDK
Cygwin 설치루트경로 : C:\Cygwin


Android SDK 다운로드 / 설치 / Eclipse에 ADT 플러그인 설치, 여기를 참조하세요

Android NDK
Android NDK는 C, C++ 언어로 작성된 코드를 Java 언어에서 JNI (Java Native Interface)방법으로 호출가능하도록 컴파일하여 *.so 포맷의 라이브러리를 생성해준다. 생성된 라이브러리는 Android 플랫폼의 Java언어에서 로드할 수 있고 로드된 라이브러리 안에 존재하는 C, C++ 함수를 호출할 수 있다. Android SDK에 의해 사용되는 것이 아니라, Cygwin을 설치/실행하여 Command line 상에서 Android NDK가 사용되어 C, C++코드가 컴파일되고 공유 라이브러리 파일(*.so)이 생성된다.
다운로드
압축해제만으로 설치는 완료, 여기서는 루트 디렉토리를 C:\Android_NDK 으로 설정(임의의 디렉토리 선택가능)


Cygwin 설치 (Windows에서 Linux 개발환경을 사용하기 위함)
Android 는 Linux 기반에서 Java 프로그램이 실행되고, Linux 상에서 C, C++언어로 작성된 공유 라이브러리는 *.so 파일로 생성되어야 한다. Windows 상에서 공유 라이브러리는 *.dll 형태로 생성되어야 한다. 그러므로 Windows환경에서 Android NDK를 사용하여 공유 라이브러리를 생성하는 경우 *.so 형태로 라이브러리를 생성하기 위해서는 Cygwin이라는 프로그램으로  Linux 개발환경을 갖출 필요가 있다.
Cygwin 을 실행하여 Android NDK가 설치된 루트 디렉토리로 이동하여 'make APP=프로젝트_이름<enter>' 명령으로 C, C++소스를 컴파일하고 공유 라이브러리 파일(*.so)을 생성할 때 사용된다.
다운로드
cygwin.exe 실행 >루트 디렉토리를 C:\Cygwin (임의의 디렉토리 선택가능) 으로 설정 >
설치할 패키지를 묻는 창에서 검색창 우측의 [Clear] 버튼을 클릭하여 선택을 모두 해제한 다음에 [devel] 항목을 클릭하여 노드를 연 다음에 아래의 3가지 패키지를 클릭하면 Install 상태로 설정되므로 이어서 [Next] 버튼을 클릭하면 설치가 진행된다
 
  • devel/gcc-core
  • devel/gcc-g++
  • devel/make


 

개요

Android SDK, Android NDK, Cygwin 프로그램이 시스템에 설치되었고 ADT가 Eclipse에 설치되었다면 Android 프로젝트에서 JNI(Java Native Interface)를 이용하여 C, C++언어에서 작성된 공유 라이브러리(*.so)를 로드하고 포함된 함수를 호출할 수 있는 프로그램을 작성할 수 있다.


Android Project 작성
일반적인 Android Project를 정상적으로 생성한다.
프로젝트가 저장되는 실제 디렉토리(Workspace)는 Android NDK 루트의 아래의 <프로젝트 이름> 디렉토리가 되도록 Workspace를 설정한다. 이렇게 하면 Eclipse에서 작성하는 모든 소스파일(*.java, *.c, *.h 등)이나 설정파일은 Android NDK 루트의 <프로젝트 이름>디렉토리 안에 저장되므로 NDK에서 C소스파일을 컴파일할 때 편리하다.

자바클래스 작성
임의의 클래스를 작성하여, JNI 규칙에 따라서 공유라이브러리를 로드하고 라이브러리에 포함된 함수를 호출하는 native 메소드를 선언한다.

헤더파일 작성
위에서 작성된 클래스와 javah.exe 를 이용하여 C 소스에서 정의할 함수의 헤더파일을 작성한다. Command line을 이용하므로 윈도우의 cmd.exe나 위에서 설치한 Cygwin을 실행하고 프로젝트가 컴파일된 디렉토리(bin)로 이동하여 'javah <패키지명.클래스이름><enter>' 명령을 하면 헤더파일이 생성된다. 생성된 헤더 파일의 이름이 너무 복잡하면 간단하게 수정하여 사용해도 된다. 여기서 작성한 헤더파일을 복사하여 다음에 생성할 jni 디렉토리로 붙여넣기한다.

라이브러리를 생성할 소스 디렉토리 생성
Eclipse에서 프로젝트 안에 jni 라는 이름의 디렉토리를 생성한다.
<프로젝트이름>/project/jni -- > *.c, *.h, Android.mk 파일들을 저장한다. 위에서 생성한 헤더파일을 여기로 이동한다.
<프로젝트이름>/project/libs --> 소스를 컴파일하면 이 디렉토리가 생성되고 그 안에 라이브러리(libxxxxx.so)가 생성된다.

Android.mk 설정파일 생성
Eclipse에서 <프로젝트이름>/jni 디렉토리 안에 Android.mk 설정파일을 생성하고, 생성할 모듈의 이름과 컴파일 대상 소스파일 이름 등을 등록한다.

C 소스파일 작성
Eclipse에서 <프로젝트이름>/jni/ 아래에 앞에서 생성한 헤더파일(*.h)을 복사해 넣고, C소스파일을 작성한다. jni 디렉토리에 저장되어 있는 헤더파일을 include 하고 JNI 규칙에 따라서 자바측에서 호출할 함수를 정의해야 한다. 헤더파일에 있는 함수의 형식을 소스파일에서 정확히 정의할 수 있다면 굳이 헤더파일은 필요없고 include하지 않아도 된다. native 메소드를 포함한 자바 클래스의 패키지 경로에 따라서 native 메소드를 정의한 C 함수의 이름이 달라지므로 주의해야 한다. 헤더파일의 함수 이름을 복사하여 소스파일에 함수를 정의한다면 문제가 없을 것이다. 이 페이지 하단부 참조

C 소스 컴파일/공유라이브러리 생성
Cygwin을 실행하고 명령행에서 /cygdrive/c/android_ndk/<프로젝트이름> 디렉토리 안으로 이동하여 다음과 같이 명령한다.
../ndk-build<enter>
Eclipse의 프로젝트를 Refresh하면 <프로젝트이름>/libs/안에 *.so 라이브러리가 생성된 것을 확인할 수 있다.

native 메소드 호출
Eclipse에서 Activity 클래스의 onCreate()안에서 native메소드를 가진 클래스의 인스턴스를 생성하고 native메소드를 호출한다.

프로젝트 실행 / 에뮬레이터 화면 출력결과 확인
프로젝트를 실행하여 에뮬레이터의 화면에서 native호출 결과를 확인한다

Eclipse Project

NdkHello.zip





다음 절차를 수행하기 전에 여기를 참조하여 Android SDK 및 ADT, Eclipse등을 설정하세요.

Android Project 생성

Eclipse에서 일반 Android Project를 생성한다.

사용자 삽입 이미지



사용자 삽입 이미지


Android_NDK의 루트에 프로젝트가 저장되도록 변경한다.
프로젝트가 저장되는 Workspace를 C:\Android_NDK\NdkHello으로 변경한다 (프로젝트 생성시에 설정해도 됨)

사용자 삽입 이미지



사용자 삽입 이미지



native 메소드를 포함한 자바 클래스를 작성한다.

사용자 삽입 이미지



사용자 삽입 이미지


 NdkHello.java
native키워드를 가진 메소드가 포함된 클래스는 헤더파일을 생성할 때도 사용됨

package android.ndk.test.hello;

/** 공유 라이브러리를 로드하고 라이브러리에 포함된 함수를 호출하는 메소드 선언*/
public class NdkHello {
 
    static {
        System.loadLibrary("HelloLibrary"); // libHelloLibrary.so 파일을 로드함
    }

    public native String getMsgFromJni();
}


 main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:id="@+id/textView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
</LinearLayout>



NdkHelloActivity.java

package android.ndk.test.hello;

import android.ndk.test.hello.R;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class NdkHelloActivity extends Activity {
    /** Called when the activity is first created. */
 
 /** 공유 라이브러리를 로드하고 그 안의 함수를 호출하는 클래스*/
    NdkHello ndkHello;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        /** 공유 라이브러리를 로드하고 그 안의 함수를 호출하는 클래스의 인스턴스 생성*/
        ndkHello = new NdkHello();
        TextView tv = (TextView)findViewById(R.id.textView);
        tv.setText(ndkHello.getMsgFromJni());    
    }
}


NdkHello 클래스가 컴파일된 bin 디렉토리 안에서 javah 명령으로 헤더파일을 생성한다.
여기서는 Cygwin을 실행하여 이 작업을 했지만 Windows에 있는 cmd.exe를 이용해도 된다.
생성된 헤더파일은 jni 디렉토리로 복사해 넣을 예정이다.

사용자 삽입 이미지



위의 과정에서 생성된 헤더파일 이름이 너무 복잡하므로 편의상 간략하게 이름을 변경한다.

사용자 삽입 이미지



프로젝트에 C, C++ 관련 파일들(*.c, *.cpp, Android.mk 등)을 저장할 jni 디렉토리를 생성한다.
 

사용자 삽입 이미지



사용자 삽입 이미지


jni 디렉토리 안에는 위에서 생성한 헤더파일을 비롯하여 라이브러리와 관련한 파일들을 저장한다.

사용자 삽입 이미지



Android.mk
내용 중에서 LOCAL_MODULE 항목에 설정한 값(HelloLibrary)은 컴파일 결과 생성될 공유 라이브러리의 이름을 구성한다. 즉, 생성될 라이브러리 파일의 이름은 'libHelloLibrary.so' 가 된다. 컴파일할 소스파일이 다수개일 경우에는  LOCAL_SRC_FILES 항목에 나열해 주면 된다.

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloLibrary
LOCAL_SRC_FILES := NdkHello.c
include $(BUILD_SHARED_LIBRARY)


NdkHello.h
javah 명령으로 생성된 헤더파일의 내용이다.
선언된 함수는 C 소스파일에서 정의되어야 한다.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class android_ndk_test_hello_NdkHello */

#ifndef _Included_android_ndk_test_hello_NdkHello
#define _Included_android_ndk_test_hello_NdkHello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     android_ndk_test_hello_NdkHello
 * Method:    getMsgFromJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_android_ndk_test_hello_NdkHello_getMsgFromJni
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


NdkHello.c
헤더파일에 선언된 C 함수를 정의한다

#include "NdkHello.h"
#include <string.h>

jstring Java_android_ndk_test_hello_NdkHello_getMsgFromJni(JNIEnv *env, jobject thiz)
{
    return (*env)->NewStringUTF(env, "This message is from Native module");
}


C 소스파일을 컴파일하여 so 파일을 생성한다.
Cygwin을 실행하고 해당 프로젝트 디렉토리로 이동하여 ../ndk-build 명령으로 컴파일한다. 이 결과
프로젝트 아래에 libs디렉토리가 생성되고 그 안에 *.so 파일이 생성된다.
ndk-build 명령을 어디서나 편리하게 실행하려면, 윈도우 환경변수, PATHndk-build가 있는 경로를 등록해주면 된다.
 

사용자 삽입 이미지



프로젝트를 Refresh 하면 다음과 같이 생성된 라이브러리를 확인할 수 있다.

사용자 삽입 이미지



프로젝트를 실행하여 라이브러리에 포함된 함수가 호출되어 에뮬레이터 화면에 출력되는지 확인한다.

사용자 삽입 이미지



사용자 삽입 이미지



참고:
----------------------------------------------------------------------------------------------------
만약, native 메소드를 포함한 자바 클래스가 android.ndk.jni.test.HelloJNIActivity 이고 native메소드가 다음과 같이 선언되어 있다면,

public native String  stringFromJNI();

이 메소드를 정의한 C 함수의 이름은 다음과 같아야 한다.
 Java_android_ndk_jni_test_HelloJNIActivity_stringFromJNI() 처럼 되어야 한다.