오늘은 JNI를 이용한 C모듈을 안드로이드에 적용하는 방법을 설명하겠습니다.
이 부분은 좀 방대한 부분이고, 저 또한 짧은 시간에 글을 적어야 하므로 충분한 캡처를 동반하지 못함을 아쉽게 생각합니다.
우선 기본환경은
이클립스 + NDK r5b + 우분투 11.04 이며, 모듈 컴파일은 Cygwin에서 사용해도 상관 없습니다.
(참고로 저는 윈도우즈 환경에서 우분투를 VM 으로 사용합니다.)
작업순서는 다음과 같이 하려 합니다.
가. 모듈 컴파일을 위한 NDK 다운로드 및 환경설정
나. *.java 에서 *.h, *.c의 JNI 파일 만드는 방법
다. 안드로이드 프로젝트 내에 JNI 쓰는 방법
가. 리눅스에서 NDK 설정
1. 우선 NDK 부터 사이트에서 받도록 합니다. 윈도우즈 환경에서 MS cl.exe 컴파일러를 이용해 *.dll 모듈을 만들어도 되지만
저는 리눅스의 *.so 모듈을 만들기 위해서 리눅스용 버전을 다운로드 받습니다.
http://developer.android.com/sdk/ndk/index.html
2. 다운로드를 받았으면 압축을 해제하고 자신의 홈 계정 디렉토리 밑으로 옮깁니다.
예) home/maluchi/android-ndk-r5b
maluchi@ubuntu:~/android-ndk-r5b$ ls
GNUmakefile build ndk-build projects tests
README.TXT docs ndk-gdb samples toolchains
RELEASE.TXT documentation.html platforms sources
maluchi@ubuntu:~/android-ndk-r5b$ pwd
/home/maluchi/android-ndk-r5b
3. 폴더 위치에 상관없이 ndk-build 명령이 수행되도록 PATH를 다음처럼 추가합니다.
자신의 홈으로 가서 .bash_profile 파일을 만들어서 다음처럼 추가하고 저장합니다.
PATH=$PATH:$HOME/android-ndk-r5b
maluchi@ubuntu:~/android-ndk-r5b$ cd
maluchi@ubuntu:~$ ls
android-ndk-r5b androiddev workspace 문서 사진
android_gingerbread examples.desktop 공개 바탕화면 음악
android_gingerbread.tgz froyo2.2.tgz 다운로드 비디오 템플릿
maluchi@ubuntu:~$ vi .bash_profile
4. 콘솔에서 source ~/.bash_profile 을 해서 바로 업데이트 하고, 잘 입력이 되었는지 env 명령을 넣어 확인합니다.
maluchi@ubuntu:~$ source ~/.bash_profile
maluchi@ubuntu:~$ env
......
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/maluchi/android-ndk-r5b
....
5. 마지막으로 NDK 폴더 내 samples->hello-jni->jni->Android.mk 를 열어 보면 다음과 같은 부분이 있습니다.
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_MODULE 은 모듈명이며, LOCAL_SRC_FILES는 C의 구현파일입니다. 여러개 존재한다면 '\ ' 추가합니다.
차후에 Android.mk를 복사해서 수정할 것입니다.
예)
LOCAL_SRC_FILES := hello-jni.c \
test.c
6. Android.mk 파일 내용을 봐 보겠습니다. 리눅스에서 해당 파일을 받기 힘드시면 아래 내용으로 파일을 만들어도 됩니다.
maluchi@ubuntu:~/android-ndk-r5b/samples/hello-jni/jni$ more Android.mk
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
maluchi@ubuntu:~/android-ndk-r5b/samples/hello-jni/jni$
이상으로 리눅스에서 모듈을 컴파일을 위한 환경은 완료가 되었습니다.
-------------------------------------------------------------------------------------------------
나. *.java 에서 *.h, *.c의 JNI 파일 만드는 방법
1. 이제 C모듈(*.so)를 java로 가져오는 부분이 필요합니다. CalcJni.java 라는 파일을 만듭니다.
그리고 다음처럼 추가합니다. 사칙연산을 수행하는 네이티브 메서드를 정의하고 "CalcJni" 라는 C 모듈을 로드하는 역할을 합니다.
01.
//package com.maluchi.CalcJNITest;
02.
public
class
CalcJni
03.
{
04.
public
CalcJni(){};
05.
public
native
int
Sum(
int
a,
int
b);
06.
public
native
int
Sub(
int
a,
int
b);
07.
public
native
int
Mul(
int
a,
int
b);
08.
public
native
int
Div(
int
a,
int
b);
09.
public
native
String message();
10.
11.
static
{
12.
System.loadLibrary(
"CalcJni"
);
13.
};}
2. 위의 Java 파일로부터 JNI의 C헤더(*.h) 파일을 만드는 것을 수작업으로 하기에는 번거로운 면이 있습니다.
하지만, Java에서 친절하게도 C헤더 파일을 만들어주는 명령이 있습니다. cmd 창을 하나 띄웁니다.
3. 자바 SDK가 설치되었다는 전제하에 해당 경로로 이동하여 *.class 파일을 만듭니다.
>javac CalcJni.java
4. *.h 헤더 파일을 만듭니다.
>javah CalcJni
5. 다음처럼 CalcJni.h 헤더가 만들어진 것을 확인할 수 있습니다.
C:\tmp\jni>dir
C 드라이브의 볼륨: Win7
볼륨 일련 번호: D037-8597
C:\tmp\jni 디렉터리
2011-06-16 오후 04:23 <DIR> .
2011-06-16 오후 04:23 <DIR> ..
2011-06-16 오후 04:22 425 CalcJni.class
2011-06-16 오후 04:23 1,011 CalcJni.h
2011-06-16 오후 04:22 391 CalcJni.java
3개 파일 1,827 바이트
2개 디렉터리 20,543,619,072 바이트 남음
6. CalcJni.h 에 맞춰 CalcJni.c 파일을 만듭니다. 이 때 가장 중요한 메서드 명명 규칙이 있습니다.
javah로 *.h를 만들면 아래처럼 패키지명이 적용되지 않고 만들어집니다.
JNIEXPORT jint JNICALL Java_CalcJni_Sum(JNIEnv *, jobject, jint, jint);
중요한 것은 JNICALL Java_패키지명_클래스명_메서드(...) 규칙으로 적용해야 한다는 것입니다.
실제 안드로이드 패키지를 만드고 그곳에서 C 모듈을 로드한다면 반드시 패키지명_클래스명 을 확인해야 합니다.
그렇지 않으면 에러의 원인이 됩니다. 더불어 JNI에서의 구현부는 반드시 파라미터명을 주도록 되어 있으므로 생략되어 있는 파라미터 변수명들을 넣습니다.
다시 1번에 가서 보시면 //package com.maluchi.CalcJNITest; 주석 처리된 것을 볼수 있습니다.
이는 후에 저 패키지에 자바파일이 들어가게 되고 메서드명들을 아래처럼 모두 바꿀 필요가 있기에 임시 주석처리를 한 것입니다.
예) JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sum(JNIEnv *, jobject, jint, jint);
CalcJni.h
01.
/* DO NOT EDIT THIS FILE - it is machine generated */
02.
#include <jni.h>
03.
/* Header for class CalcJni */
04.
05.
#ifndef _Included_CalcJni
06.
#define _Included_CalcJni
07.
#ifdef __cplusplus
08.
extern
"C"
{
09.
#endif
10.
/*
11.
* Class: CalcJni
12.
* Method: Sum
13.
* Signature: (II)I
14.
*/
15.
JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sum
16.
(JNIEnv *, jobject, jint, jint);
17.
18.
/*
19.
* Class: CalcJni
20.
* Method: Sub
21.
* Signature: (II)I
22.
*/
23.
JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sub
24.
(JNIEnv *, jobject, jint, jint);
25.
26.
/*
27.
* Class: CalcJni
28.
* Method: Mul
29.
* Signature: (II)I
30.
*/
31.
JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Mul
32.
(JNIEnv *, jobject, jint, jint);
33.
34.
/*
35.
* Class: CalcJni
36.
* Method: Div
37.
* Signature: (II)I
38.
*/
39.
JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Div
40.
(JNIEnv *, jobject, jint, jint);
41.
42.
/*
43.
* Class: CalcJni
44.
* Method: message
45.
* Signature: ()Ljava/lang/String;
46.
*/
47.
JNIEXPORT jstring JNICALL Java_com.maluchi.CalcJNITest_CalcJni_message
48.
(JNIEnv *, jobject);
49.
50.
#ifdef __cplusplus
51.
}
52.
#endif
53.
#endif
CalcJni.c
01.
#include <stdio.h>
02.
#include "CalcJni.h"
03.
04.
JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Sum
05.
(JNIEnv *env, jobject obj, jint a, jint b)
06.
{
07.
return
a+b;
08.
}
09.
/*
10.
* Class: CalcJni
11.
* Method: Sub
12.
* Signature: (II)I
13.
*/
14.
JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Sub
15.
(JNIEnv *env, jobject obj, jint a, jint b)
16.
{
17.
return
a-b;
18.
}
19.
/*
20.
* Class: CalcJni
21.
* Method: Mul
22.
* Signature: (II)I
23.
*/
24.
JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Mul
25.
(JNIEnv *env, jobject obj, jint a, jint b)
26.
{
27.
return
a*b;
28.
}
29.
/*
30.
* Class: CalcJni
31.
* Method: Div
32.
* Signature: (II)I
33.
*/
34.
JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Div
35.
(JNIEnv *env, jobject obj, jint a, jint b)
36.
{
37.
if
(a == 0 || b ==0)
38.
return
0;
39.
return
a/b;
40.
}
41.
/*
42.
* Class: CalcJni
43.
* Method: message
44.
* Signature: ()Ljava/lang/String;
45.
*/
46.
JNIEXPORT jstring JNICALL Java_com_maluchi_CalcJNITest_CalcJni_message
47.
(JNIEnv *env, jobject obj)
48.
{
49.
return
(*env)->NewStringUTF(env,
"Hello world!! Welcome to maluchi's world"
);
50.
}
7. 이제 JNI 파일까지 모두 준비가 됐습니다.
------------------------------------------------------------------------------------------------------
다) 안드로이트 프로젝트에서 JNI 사용하는 방법
가)에서 NDK를 설정하여 *.so 파일을 만드는 환경 설정을 했고,
나)에서 *.so 의 구현 C모듈에서 Java로 읽어들오도록 인터페이스를 만들고 c 파일 구현까지 했습니다.
이제 실질적으로 구현한 C 파일을 리눅스에서 컴파일을 하고 *.so 파일을 얻은 후 안드로이트 프로젝트에 넣어서 실제 동작하는지 테스트 해보는 일만 남았습니다.
이 작업을 설명하겠습니다.
우선 안드로이드 프로젝트를 하나 만듭니다.
1. CalcJNITest 라는 프로젝트를 만들고 패키지경로를 com.maluchi.CalcJNITest 로 합니다.
2. 이 프로젝트에 jni 라는 폴더를 만들고 아까 만들어둔 CalcJni.h, CalcJni.c, CalcJni.java 를 넣고, 가) 에서 봤던 Android.mk도 복사해서 넣습니다.
반드시 jni 라는 폴더에 넣어야 합니다. 그리고 Android.mk 의 LOCAL_MODULE 과 LOCAL_SRC_FILES을 다음처럼 수정합니다.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := CalcJni
LOCAL_SRC_FILES := CalcJni.c
include $(BUILD_SHARED_LIBRARY)
3. 이제 CalcJNITest라는 프로젝트를 리눅스 NDK홈에 projects라는 폴더를 만들고 projects 폴더로 옮깁니다.
maluchi@ubuntu:~/android-ndk-r5b/projects$ pwd
/home/maluchi/android-ndk-r5b/projects
maluchi@ubuntu:~/android-ndk-r5b/projects$ ls
CalcJNITest
4. CalcJNITest에 프로젝트에 들어가서 ndk-build 명령을 넣습니다.
해당 프로젝트에 jni 폴더 밑에 c 파일이 있고, ndk-build 는 이를 바탕으로 Libs 폴더를 만들어 *.so 파일을 생성합니다.
진행은 다음처럼 하시면 됩니다..
maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$ ls
AndroidManifest.xml bin gen proguard.cfg src
assets default.properties jni res
maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$ ndk-build
Compile thumb : CalcJni <= CalcJni.c
SharedLibrary : libCalcJni.so
Install : libCalcJni.so => libs/armeabi/libCalcJni.so
maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$
5. 결국 프로젝트에 Libs/armeabi/libCalcJni.so 파일이 만들어짐을 알게 됩니다.
Libs를 통째로 복사해서 윈도우의 해당 안드로이드 프로젝트에 복사합니다.
6. 이제 이 C구현부를 java로 불러들이는 일만 남았습니다.
아까 만들어둔 jni/CalcJni.java를 com.maluchi.CalcJNITest 패키지로 복사를 합니다.
그리고 CalcJni.java 파일 내에 주석 처리한 package package com.maluchi.CalcJNITest; 주석해제 시킵니다.
7. 구현을 다음과 같습니다.
01.
package
com.maluchi.CalcJNITest;
02.
03.
import
android.app.Activity;
04.
import
android.os.Bundle;
05.
import
android.os.Handler;
06.
import
android.util.Log;
07.
import
android.widget.TextView;
08.
public
class
CalcJNITest
extends
Activity {
09.
/** Called when the activity is first created. */
10.
@Override
11.
public
void
onCreate(Bundle savedInstanceState) {
12.
super
.onCreate(savedInstanceState);
13.
setContentView(R.layout.main);
14.
15.
CalcJni jni =
new
CalcJni();
16.
17.
String string =
""
;
18.
string =
"Sum :"
+jni.Sum(
10
,
10
)+
19.
", Sub: "
+jni.Sub(
20
,
10
)+
20.
", Mul: "
+jni.Mul(
100
,
10
)+
21.
", Div: "
+jni.Div(
100
,
10
)+
22.
", Message: "
+jni.message();
23.
24.
Log.d(
"maluchi"
, string);
25.
TextView v = (TextView) findViewById(R.id.txt);
26.
v.setText(string);
27.
28.
// mHandler.sendEmptyMessageDelayed(0, 300);
29.
30.
}
31.
32.
Handler mHandler =
new
Handler()
33.
{
34.
public
void
handleMessage(android.os.Message msg) {
35.
36.
CalcJni jni =
new
CalcJni();
37.
String string =
""
;
38.
string =
"Sum :"
+ jni.Sum(
10
,
10
) +
", Sub: "
+ jni.Sub(
20
,
10
)
39.
+
", Mul: "
+ jni.Mul(
100
,
10
) +
", Div: "
40.
+ jni.Div(
100
,
10
) +
", Message: "
+ jni.message();
41.
TextView v = (TextView) findViewById(R.id.txt);
42.
v.setText(string);
43.
};
44.
};
45.
}
마지막으로 에러 관련 상황입니다.
06-16 06:33:34.304: ERROR/AndroidRuntime(602): Uncaught handler: thread main exiting due to uncaught exception
06-16 06:33:34.354: ERROR/AndroidRuntime(602): java.lang.UnsatisfiedLinkError: Sum
06-16 06:33:34.354: ERROR/AndroidRuntime(602): at com.maluchi.CalcJNITest.CalcJni.Sum(Native Method)
위처럼 나타나는 에러는 JNI의 메서드명 규칙의 JNICALL Java_ 패키지명_클래스명_메서드명() 규칙이 잘못됐을 때 나타는 현상입니다.
만약 다음과 같은 예외가 발생한다면 라이브러리 패스가 올바르게 설정되어 있는지 확인합니다.
java.lang.UnsatisfiedLinkError: no hello in shared library path at java.lang.Runtime.loadLibrary(Runtime.java)
at java.lang.System.loadLibrary(System.java)
at java.lang.Thread.init(Thread.java)
'공부 > Android' 카테고리의 다른 글
[안드로이드] fragment 개념 (0) | 2013.08.21 |
---|---|
안드로이드 preference (환경설정) (0) | 2013.08.20 |
[안드로이드] 터치화면, 제스처 기능을 이용한 터치 인식 (0) | 2013.07.24 |
안드로이드 코드(.java)에서 Layoutparams에 dip 단위 사용하기 (0) | 2013.07.24 |
안드로이드 레이아웃 겹치기 (0) | 2013.07.23 |