본문 바로가기
Programming/Android Kernel,Native

안드로이드 네이티브 라이브러리 I

by 개Foot/Dog발?! 2014. 10. 20.

URL : http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=34284


.....


안드로이드 런타임 계층과 라이브러리 계층을 연결하는 왼쪽 화살표로 표현된 부분에서 일반적인 함수호출 관계 외에 추가적으로 C/C++ ↔ java 호출 사이의 ‘glue’가 존재한다. 결국 이 덕분에 안드로이드 프레임워크는 자연스럽게 OS의 복잡한 기능을 리눅스 커널로부터 빌려 쓸 수 있는 것이다.


.....



.....


Java ↔ C/C++ 사이의 glue는 어떤것을 사용할까?
일반적으로 Java에서 C/C++ 모듈을 사용하기 위해서는 다음과 같은 방법을 사용한다.

1. Java Native Access (JNA) 

자바 코드에서 네이티브 코드와 같은 형태로 네이티브 함수를 호출하는 방식으로 기존 코드의 최소한의 수정으로 응용이 가능해, 최상의 방법으로 생각되나 현재 안드로이드 Dalvik VM에서 지원하지 않는 방법이다.

2. Java Native Interface (JNI)
Java 초창기부터 존재한 Java/Native interface 방법으로 안드로이드 Dalvik VM에서 지원하는 방법으로 실제 안드로이드 플랫폼 소스 여러 부분에서 사용되고 있다. 그러나 이 방식은 raw form을 다루는 형태로 개발해야 하기 때문에 개발 시간이 많이 소요되고 에러를 발생 시킬만한 요소가 많다.

3. Simplified Wrapper and Interface Generator (SWIG)
JNI를 좀 더 쉽게 사용 할수 있도록 해주는 도구로 C/C++ 인터페이스를 정의한 파일을 입력 받아 C/C++ JNI wrapper 코드를 생성한다. 이 방법 역시 Dalvik VM과는 호환성 문제가 존재한다.


.....


안드로이드 에뮬레이터를 실행하고 <화면 1>에서 초록색으로 표시한 C/C++ 라이브러리 파일의 실제 위치를 확인해보자. 아래와 같이 커맨드 창에서 emulator를 실행하거나 이클립스 IDE를 통해 emulator를 실행한다.


....


콘솔에서는 <리스트 1>과 같이 adb 명령을 이용하여 shell을 실행한다.


adb shell을 이용하여 네이티브 라이브러리 확인하기

>adb shell
adb shell 이 실행되면 아래와 같이 library path를 확인해보자
#echo $LD_LIBRARY_PATH
#/system/lib
system/lib가 library path로 잡혀있는 것을 확인하고 /system/lib directory로 이동한다.
#cd /system/lib 


내용을 <화면 3>를 통해 확인해보자.


.....  /system 디렉토리는 다운로드한 애플리케이션이 설치할 수 있는 영역이 아니기 때문에 /system 디렉토리 대신 /data 디렉토리에서 동작 할 수 있는 방법을 찾아야 한다.

안드로이드 시스템 이미지
안드로이드 시스템 이미지는 리눅스 운영체제에서 파일시스템은 단순히 자료의 저장기능 뿐 아니라 운영체제 동작에 필요한 필수 정보를 포함하고 장치 관리 등의 특별한 용도로도 사용하므로 선택항목이 아니라 필수 항목이다. 시스템 이미지는 안드로이드 프레임워크를 구동하기 위해 필수적인 항목으로 <안드로이드 SDK설치 디렉토리>/platforms/<플랫폼버전>/images/system. img 에서 확인 할 수 있다.

이 이미지파일은 실제 에뮬레이터가 구동될 때 /system directory에 마운트하게 되는데, 이내용은 adb shell을 실행시킨 후 최상위 디렉토리의 init.rc파일에서 확인 할 수 있다.

/system 디렉토리는 임베디드하여 탑재할 기본 응용프로그램을 포함해 안드로이드 프레임워크에 필요한 라이브러리 설정파일 등이 들어있는 영역으로 추가로 다운로드받은 응용프로그램과는 구별된다. 다운로드 받은 응용프로그램은 /data 디렉토리에 위치하게 된다.
 
안드로이드 SDK 설치
앞서 설명된 부분들을 실습하기 위해서는 안드로이드 SDK를 설치하고 버추얼 디바이스를 생성해야 하는데, 이 부분은 이번 컬럼의 주제와는 벗어나므로 아래 URL을 참고해 설치하기 바란다.

http://developer.android.com/sdk/index.html


NDK와 만나다

..... http://developer.android.com/sdk/ndk/1.6_r1/index.html

NDK는 C/C++로 작성한 코드로 리눅스 OS 기반의 ARM binary를 생성하기 위한 크로스 툴체인을 제공한다. 여기서 ‘크로스 툴체인’이란 타깃머신과는 다른 환경에서 타깃 머신용으로 바이너리를 생성할 수 있도록 제공되는 툴인데 컴파일러 링커 그리고 기타 컴파일에 필요한 유틸리티를 포함하고 있다.


Application.mk 파일

APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES      := hello-jni


일반적인 x86호환 CPU의 윈도우나 리눅스 PC기반 환경에서 ARM이나 MIPS등 임베디드 머신의 CPU에서 동작하는 바이너리를 컴파일 할 수 있도록 해준다.

그리고 아래와 같은 라이브러리를 사용할 수 있도록 header file을 제공하는데, 이 내용은 앞서 제시된 adb 쉘에서 확인한 /system/lib 디렉토리에 있는 라이브러리의 일부임을 확인할 수 있다.

(1)  libc(C library)
(2)  libm(math library)
(3)  JNI interface
(4)  libz (zip 압축 library)
(5)  liblog(Android logging library)
(6)  OpenGL ES library (3D graphic library, NDK 1.6버전부터)
 
추가적으로 안드로이드 응용프로그램 패키지 파일(.apk)에 native library를 포함할 수 있는 방법을 제공한다.

.... NDK를 설치해 보도록 하자.
 
(1) http://developer.android.com/sdk/ndk/1.6_r1/index.html 에서 윈도우용 패키지를 다운로드 받는다.
(2) 적당한 위치에 (1)에서 다운로드 받은 압축파일을 푼다.
(3) www.cygwin.com에서 최신 cygwin 을 설치한다.
(4) 설치한 Cygwin bash shell을 실행하고 bash shell에서 /cygdrive/<NDK 설치 경로> 로 이동해 ‘Build/host-setup.sh’ 라고 명령을 수행한다.

이처럼 순차적으로 따라하고 나면, <화면 4>와 같이 ‘Host setup complete…’라는 문구가 나타나는데, 이럴 경우 셋업이 성공한 것이다.


이제 설치가 끝났으니 먼저 NDK에 샘플로 제공되는 hello-jni 예제를 빌드해 보자. 빌드시에는 $make APP=hello-jni라고 명령을 수행한다. 재빌드시에는 -B 옵션을 사용하고 실제 build command를 모두 확인하고 싶은 경우 V=1 옵션을 사용한다.


빌드결과로 apps/hello-jni/project/libs/areabi/libhello-jni.so가 생성되었는지 확인하자.

어떻게 NDK에서 제공하는 툴을 이용하여 예제가 빌드 되는지 알고 싶다면, apps/hello-jni/Application.mk 와 sources/ hello-jni/Android.mk 파일을 열어보자.
일반적인 make 파일과 같은 형태이며 빌드를 위한 변수를 설정하는 부분을 확인 할 수 있다. 또한 Application.mk 파일에는 응용프로그램 에서 어떤 네이티브 라이브러리 모듈을 사용할 것인지에 대해 기술해야 한다. 이 부분은 네이티브 라이브러리를 빌드하는 것과는 직접적인 관계가 없으나 이 파일을 참조해 빌드할 타깃 모듈을 결정하기 때문에 필수적으로 작성해야 한다.


Android.mk 파일

… 중략…
 
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES   := hello-jni.c
 
include $(BUILD_SHARED_LIBRARY) 


Android.mk 파일에는 실제 shared library로 빌드할 c/c++ 파일 목록 과 사용하는 라이브러리 목록, compile/link option 등을 기술해야 하는데, 이때 변수 LOCAL_MODULE 에 모듈이름을 기술하게 된다. 이어 변수 LOCAL_SRC_FILES 에 빌드하려고 하는 소스 파일명을 기술하고, 파일명 사이는 스페이스로 구별한다.

마지막 줄이 shared library, 즉 .so 파일로 빌드 하겠다는 뜻이다. 이렇게 기술하면 자동으로 lib+모듈명+.so 형태의 파일을 빌드하게 된다.

추가로 특정 라이브러리를 사용하기 위해서는 아래와 같이 LOCAL_LDLIBS 변수에 내용을 기술한다. 아래는 안드로이드 로그 라이브러리를 사용하기 위한 예이다. 또한 이에 대한 추가적인 내용은 NDK document 폴더의 ‘ANDROID-MK.TXT’,’APPLICATION-MK.TXT’,’HOWTO.TXT’ 문서를 참고하기 바란다.

LOCAL_LDLIBS     :=-L$(SYSROOT)/usr/lib/ -llog

이제 네이티브 라이브러리 빌드가 끝났으니 네이티브 라이브러리를 사용하는 Java 코드를 살펴보자.


.....


에뮬레이터를 동작하기 위해서는 플랫폼 버전에 맞는 AVD 파일을 미리 생성해야 하는데 이 내용은 http://developer.android.com/guide/developing/eclipse-adt.html을 참고하길 바란다.


.....

이전에 adb shell 명령을 이용한 안드로이드 파일시스템을 살펴보았는데, 이번에는 이클립스 IDE에서 DDMS툴을 이용하여 손쉽게 파일시스템을 확인해보자. 이때 이클립스 툴바 오른편에서 DDMS perspective 버튼을 선택한다. 오른편에 File Explorer 창을 이용하여 data/data/com.example.hellojni 폴더의 내용을 확인할 수 있으며, lib폴더아래에 libhello-jni.so 파일이 있는 것 또한 확인할 수 있다.

..... 결국 안드로이드 응용프로그램은 system/lib 폴더의 라이브러리 이외에 /data/data/<자신의 package이름>/lib 아래 네이티브 라이브러리를 추가로 참고하는 것을 확인할 수 있다.
 
네이티브 라이브러리를 사용하는 Java 코드 맛보기


Java코드 에서 어떻게 C로 작성한 네이티브 코드를 참고하는지 궁금해졌다. 이번에는 허우대 대리와 함께 src/com/example/HelloJni.java 코드를 살펴보자


HelloJni.java 파일

… 중략…
public class HelloJni extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        TextView  tv = new TextView(this);
       tv.setText( stringFromJNI() ); //-- (3)
        setContentView(tv);
    }
 
public native String  stringFromJNI(); //-- (1)
 
… 중략…
   static {
        System.loadLibrary("hello-jni"); //-(2)
    }

stringFromJNI 함수를 호출하여 얻은 문자열을 textView에 내용으로 넣어주는 간단한 소스로, 3가지 의미로 이용된다.

1) native 키워드를 붙혀서 선언부만 작성한다. 네이티브 라이브러리에 구현부가 있음을 알려준다.

2) static {..} 부분은 이 class가 로딩될 때 호출되는 되는데 이때 ‘hello-jni’ 라는 이름의 네이티브 라이브러리를 로딩하라는 의미이다.

3) 네이티브 라이브러리 함수라도 보통의 Java 메소드와 같은 방식으로 사용한다.
 
이어 Source/samples/hello-jni.c 파일을 열어서 네이티브 라이브러리 구현부도 함께 살펴보도록 하자.


네이티브 라이브러리 구현부 코드

Jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !")

함수이름 앞에 ‘Java_com_example_ hellojni_HelloJni_’ 가 붙어 있는데, 이 부분은 JNI 함수이름 규칙에 의해 package명_클래스명이 Java에서 선언한 함수 이름 앞에 붙게 된다. 함수의 내용은 단순히 “Hello from JNI!” 라는 문자열을 Java에서 사용하는 String 타입으로 변환하여 반환하는 내용이다.


참고자료
1. http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
2. http://developer.android.com/sdk/ndk/1.6_r1/index.html
3. http://www.aton.com/android-native-libraries-for-java-applications/
4. http://www.swig.org/