IT_Programming/Android_Java

[펌] 안드로이드 JNI 콜백 호출 기법

JJun ™ 2014. 3. 31. 12:29

 


 출처: http://blog.naver.com/brighteyes79/130149049628


 

 

 

JNI를 통한 안드로이드라이브러리에서 자바 함수 호출 기법

 

JNI(JavaNative Interface)에서는 managed 코드 (Java) 에서 Native 코드(C/C++)와 상호 작용을 하는
방법을 제공해준다. 

이것은 벤더 중립적으로 동적 공유 라이브러리를로딩하여 사용할 수 있도록 하는 것이다. 안드로이드에서는 Java 언어로개발된 애플리케이션과 프레임워크에서 Native 에 있는 라이브러리를 호출하여 사용하기 위하여 JNI를 사용한다. 

그리고 콜백을 수행하는 기법이라고 알려진 Native 코드에서 Java 메서드를 호출하는 기법이 JNI 에서 제공된다.

JNI 콜백기법은 기본적으로 안드로이드의 Bluetooth, 카메라, 미디어플레이어 등 에서 커널 단으로부터
발생되는 이벤트들을 Native 단에서 Java 프레임워크로 올려줄 때 사용되며, 물론 사용자 라이브러리에서 Async 한 처리를 하고 난 후 결과를 어플리케이션에 알리고자 할 때 유용하게 사용된다. 

자바 프레임워크에서 콜백을 받은 경우는 브로드캐스트를 통하여 안드로이드에 해당 이벤트를 알려주고,
사용자 어플리케이션의 경우에는 콜백에 따른 결과를 UI 에 표시하는등 활용하면 되겠다.

Java 프로그래밍언어에는 몇가지 메서드 유형이 있는데 Static 메서드는 어떠한 인스턴스에 독립적으로
호출하는 반면, 인스턴스 메서드는 클래스의 특정 인스턴스를 호출해야한다.

JNI 는 Native 코드에서콜백을 수행할 수 있도록 기능들을 제공한다. 아래의 예제는native 코드가 Java로
구현된 인스턴스 메서드를 호출하도록 한다.

 

class InstanceMethodCall {

     private native void nativeMethod();

     private void callback() {

         System.out.println("In Java");

     }

     public static void main(String args[]) {

         InstanceMethodCall c = new InstanceMethodCall();

         c.nativeMethod();

     }

     static {

         System.loadLibrary("InstanceMethodCall");

     }

 }

 

아래는 native 코드의 구현부이다.

 

JNIEXPORT void JNICALL

 Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)

 {

     jclass cls = (*env)->GetObjectClass(env, obj);

     jmethodID mid =

         (*env)->GetMethodID(env, cls, "callback", "()V");

     if (mid == NULL) {

         return; /* method not found */

     }

     printf("In C\n");

     (*env)->CallVoidMethod(env, obj, mid);

 }

 

이 프로그램을 실행하면 다음과 같이 나온다.

In C

In Java

 

 

 

 인스턴스메서드 호출

Native 에서는 주어진 클래스에서 해당 메서드를 찾기 위해 GetMethod 함수를 호출한다.
이 때 메서드를 찾기 위해 메서드의이름과 타입 기술자를 살펴본다. 만약 해당 메서드가 존재하지 않으면null 을 리턴한다.

그 다음으로는 CallVoidMethod 함수를 호출한다. 이 함수는 리턴 형이 void 인 인스턴스 함수를 호출한다. 만일 int 형이면CallIntMethod, String 이나 배열 같은 객체를 리턴하는 경우에는CallobjectMethod
사용한다
.

만약 인터페이스 메서드를 호출하고자하면 함수의 <Type>Methodfamily 를 호출해야한다.
예를 들어서com.mdsedu.test.JniTestLib myMethod() 라는 void 함수를 호출하고자 하는 경우에는
다음과 같이 한다
.

 

 

jmethodID mid;

 jclass Intf =

     (*env)->FindClass(env, "com/mdsedu/test/JniTestLib");

 if (Intf == NULL) {

     ... /* error handling */

 }

 mid = (*env)->GetMethodID(env, Intf, "myMethod", "()V");

 if (mid == NULL) {

     ... /* error handling */

 }

 (*env)->CallVoidMethod(env, Intf, mid);

 ... /* check for possible exceptions */

 

 

 

 

  JNITypes Data Structures

 

JNI에서 Java 타입과 Native 타입을 매핑할 때 Primitive Type 의 경우다음의 테이블과 같이 사용한다.

Java Type

Native Type

Description

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

N/A

 

Type Signature

JNI Type Signature 라고하는 java VM 의 표기법을 사용한다.

Type Signature

Java Type

Z

boolean

B

byte

C

char

S

short

I

int

J

long

F

float

D

double

L fully-qualified-class ;

fully-qualified-class

[ type

type[]

( arg-types ) ret-type

method type

 

예를 들어, Java 함수가 아래와 같다면

 

Long f (int n, String s, int[] arr);

 

다음과 같은 type signature 를 갖는다.

 

(ILjava/lang/String;[I)J

 

 

Static메서드 호출

Native 코드로부터 static 메서드를다음과 같이 호출할 수 있다.

GetStaticMethodId 로 메서드아이디를 취득한 후 CallStaticVoidMethod 에 클래스,메서드 아이디,
static
메서드의 family 중하나를 넘기며 해당 함수를 호출한다.

다음은 native 코드로부터 static 메서드를콜백으로 호출하는 예제이다.

 

class StaticMethodCall {

     private native void nativeMethod();

     private static void callback() {

         System.out.println("In Java");

     }

     public static void main(String args[]) {

         StaticMethodCall c = new StaticMethodCall();

         c.nativeMethod();

     }

     static {

         System.loadLibrary("StaticMethodCall");

     }

 }

 

아래는 native 호출 구현이다.

 

JNIEXPORT void JNICALL

 Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj)

 {

     jclass cls = (*env)->GetObjectClass(env, obj);

     jmethodID mid =

         (*env)->GetStaticMethodID(env, cls, "callback", "()V");

     if (mid == NULL) {

         return;  /* method not found */

     }

     printf("In C\n");

     (*env)->CallStaticVoidMethod(env, cls, mid);

 }

 

 

 

Q/A

 

# 메서드 아이디를 얻을때 String 타입 파라미터 하나에String 리턴 타입인 Java 함수를
  호출하기 위해서 네 번째 인자인 signature를 어떻게 지정 해야하는가 ?

   위에서 설명한 TypeSignature 표기법에 따라 (Ljava/lnag/String)java/lang/String

 

# Native 단에서스레드를 사용하면서 JNI 콜백 함수를 호출할 수 있는가 ?

   Native의 쓰레드는 커널에 의해 스케줄링 되는 리눅스 스레드인데, 일반적으로 managed 코드에서
   시작하지만 어디든 생성된 후 Java VM 에 attach 된다.예를 들어서 스레드는 pthread_create 에서
   생성되고 JNI AttachCurrentThread 로 attach된다. 스레드는 attach 될 때까지 JNIEnv가 없고
   JNI 콜을 할 수 없다.

   안드로이드는 Native 코드를 실행하는 스레드를 서스팬드시키지 않는다.
   가비지 콜렉터가 진행중이거나 디버거가 서스팬드 요청을 하는 경우에는 안드로이드가 일시 중지를
   시킨다.

   Attach된 스레드가 종료하기 전에JNI는 DetachCurrentThread 를 호출해야한다.

 

 

if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
return JNI_FALSE;
}

static void *work_thread(void *arg) {

    JNIEnv* env;
thread_data_t *data = (thread_data_t *)arg;
int sk;

if (jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
return NULL;
}

if (jvm->DetachCurrentThread() != JNI_OK) {
LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
}

return NULL;

}