해당 포스트는 "안드로이드 ndk의 모든 것", "안드로이드 NDK 네이티브 프로그래밍" 책의 내용을 요약한 것이다.



※ 네이티브 스레드

네이티브 라이브러리를 사용해 네이티브 단에서 스레드를 생성할 수 있다. 네이티브 라이브러리를 사용하는 애플리케이션을 만들다 보면 자바 애플리케이션에서 네이티브 단의 상태를 받아 사용자에게 알려줘야 하는 상황이 발생한다. 이 때 네이티브에 상태를 체크하는 스레드를 생성하여 어떤 이벤트가 발생하면 자바 함수를 호출해서 마치 콜백처럼 동작시키면 된다. 


ex) 네이티브 스레드가 1초 간격으로 자바 함수를 호출하는 예제

<메인 액티비티>


public class MainActivity extends AppCompatActivity {

static {
System.loadLibrary("nativethread");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView tv = (TextView) findViewById(R.id.textView1);

int ret = -1;
ret = startThread();
}

public static void callback(int a){
Log.e("Java", "callback : " + a);
}

@Override
protected void onStop() {
super.onStop();
int ret = -1;
ret = endThread();
Log.e("Java","endThread : "+ ret);
}

public native int startThread();
public native int endThread();
}

메인 액티비티에서 네이티브 함수인 startThread를 호출해서 네이티브 스레드가 생성된다. 네이티브 스레드에서는 1초 간격으로 callback 함수를 호출할 예정이다. onStop 에서는 네이티브 함수 endThread를 호출해서 네이티브 스레드를 멈추게 할 것이다. 


1. JNI_OnLoad()를 만들어서 초기화한다. JNI_OnLoad 함수를 사용하는 이유는 네이티브 단에서 자바 함수에 능동적으로 접근하는 데 JVM이 필요하기 때문이다. JNI_OnLoad 에서 함수를 연결하고 전역 변수인 glpVM에 JVM을 저장한다.

static JNINativeMethod methods[] = {
{"startThread", "()I", (void*)startThread_Native },
{"endThread", "()I", (void*)endThread_Native },
};


JavaVm * glpVM = NULL;

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
jint result = -1 ;
JNIEnv* env = NULL ;
jclass cls ;

if ( vm->GetEnv( (void**)&env, JNI_VERSION_1_4) != JNI_OK ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "GetEnv failed.\n" ) ;
goto error ;
}

cls = env->FindClass( "com/example/user/myapplication/MainActivity" ) ;
if ( cls == NULL ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Native registration unable to find class(AVMJni)" ) ;
goto error ;
}

if ( env->RegisterNatives( cls, methods, sizeof( methods ) /
sizeof ( methods[0] ) ) < 0 ) {
__android_log_print(ANDROID_LOG_INFO, "NATIVE", "Registernatives failed !!!\n" ) ;
goto error ;
}
result = JNI_VERSION_1_4 ;
glpVM = vm ;
error:
return result ;
}


2. 자바에서 호출하는 네이티브 함수 startThread_Native, endThread_Native 함수를 구현한다.


JNIEXPORT jint JNICALL startThread_Native
(JNIEnv * env, jobject thiz) {

end_flag = 1 ;
int b = 2 ;

__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Call start thread" ) ;
jclass cls ;
cls = env->FindClass( "com/example/user/myapplication/MainActivity") ;
if ( cls == NULL ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Can't find the class.") ;
}

jObject = (jclass)env->NewGlobalRef( cls ) ;
funccb = env->GetStaticMethodID( cls, "callback", "(I)V" ) ;
if ( funccb == 0 ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Can't find the function." ) ;
env->DeleteGlobalRef( jObject ) ;
}
else {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Method connect success....\n") ;
env->CallStaticVoidMethod( cls, funccb, 10 ) ;
}

thr_id = pthread_create( &p_thread[1], NULL, t_function, (void*)&b ) ;
if ( thr_id < 0 ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Create thread fail.\n" ) ;
return -1 ;
}
return 0 ;
}

JNIEXPORT jint JNICALL endThread_Native
(JNIEnv * env, jobject thiz) {

__android_log_print( ANDROID_LOG_INFO, "NATIVE", "Call end thread" ) ;
end_flag = 0 ;
return 0 ;
}

네이티브 스레드에서 호출할 자바 함수에 접근할 수 있도록 jObject 와 funccb를 세팅한다. JObject는 NewGlobalRef를 사용해서 전역 레퍼런스로 세팅한다. pthread_create를 통해서 네이티브 스레드가 시작된다. 


3. 네이티브 스레드에 의해서 실행되는 t_function 구현한다.


void Notify(int n) {
int value = 0 ;
value = n ;
if ( !glpVM ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "error (!glpVM)" ) ;
return ;
}

if ( !funccb ) {
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "error (!funccb)" ) ;
return ;
}

JNIEnv* env = NULL ;
glpVM->AttachCurrentThread( &env, NULL ) ;
if ( env == NULL || jObject == NULL ) {
glpVM->DetachCurrentThread() ;
__android_log_print( ANDROID_LOG_INFO, "NATIVE", "error (env == NULL || AVM_JM.JObject == NULL)" ) ;
return ;
}

env->CallStaticVoidMethod( jObject, funccb, value ) ;
glpVM->DetachCurrentThread( ) ;
}


void *t_function(void *data)
{
int id;
int i=0;
id = *((int *)data);

while(end_flag)
{
Notify(i);
i++;
sleep(1);
}
}

JNI 인터페이스 포인터인 JNIEnv는 현재 스레드 안에서만 유용하다. 만일 다른 스레드에서 JNIEnv에 접근하려면 반드시 AttachCurrentThread()를 호출해서 JNI 인터페이스 포인터를 얻어야 한다. 그 후 JNIEnv를 일반적인 자바 스레드에서 동작되는 것처럼 사용 가능하고 작업이 완료되면 DetachCurrentThread()를 사용하여 분리해야 한다. 

+ Recent posts