안녕하세요 초보개발자 도깨비입니다.
soundpool을 이용해서 효과음을 재생시키는데 간단한 예제(버튼클릭시 효과음을 순차적으로 재생)를 만들어서
재생시킬 때는 문제가 없지만
이를 현 프로젝트에 적용시켜서 C에서 호출해서 재생하려고 할 때 어플이 뻗어버리는 문제가 생기네요
효과음이 잘나오다가도
연속적으로 효과음을 재생시키거나 아니면 갑자기 효과음이 출력되려고 할때 어플이 종료가 되버립니다.
혹시 이같은 문제를 겪어본 적이 없는지 어떻게 해결하셨는지 궁금합니다.
답변 감사합니다. 아즈라엘님.
에러 메세지는 아래와 같습니다.
02-14 17:04:48.636: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528b58) failed to find entry (valid=1)
02-14 17:04:48.636: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528b80) failed to find entry (valid=1)
02-14 17:04:48.636: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40529138) failed to find entry (valid=1)
02-14 17:04:48.636: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528af0) failed to find entry (valid=1)
02-14 17:04:48.636: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x4051c590) failed to find entry (valid=1)
02-14 17:04:48.656: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528b58) failed to find entry (valid=1)
02-14 17:04:48.656: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528b80) failed to find entry (valid=1)
02-14 17:04:48.656: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40529138) failed to find entry (valid=1)
02-14 17:04:48.656: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x40528af0) failed to find entry (valid=1)
02-14 17:04:48.656: W/dalvikvm(31807): JNI WARNING: DeleteLocalRef(0x4051c590) failed to find entry (valid=1)
처음 액티비티를 생성하면서 initJNI()를 호출하게 되고
화면을 갱신하기 ㅇSurfaceView의 run에서 C의 함수를 호출합니다. 이때 화면갱신중 PlaySound를 호출하게 됩니다.
C - intiJNI
JNIEnv* jni_env; jclass jni_cls; jobject* jni_obj; jmethodID jni_methodID;
JNIEXPORT void JNICALL Java_InitJNI(JNIEnv *env, jobject obj, jint sw, jint sh)
{
jni_env = env;jni_obj = (jobject)(*env)->NewGlobalRef(env,obj); jni_cls = (*jni_env)->GetObjectClass(jni_env,jni_obj);
playSoundMID = (*jni_env)->GetMethodID(jni_env, jni_cls, "PlaySound","(I)V"); }
C - PlaySound
void PlaySound(int nRequestNum, boolean bLoop, int nNowTime)
{(*jni_env)->CallVoidMethod(jni_env, jni_obj, playSoundMID, nRequestNum);
}
이렇게 구성된 파일을 수정했습니다. 아즈라엘님의 말처럼 수정해봤습니다.
수정후 C - initJNI
static JavaVM *g_VM; ////////////////////////////////// JNIEnv* jni_env; jclass jni_cls; jobject* jni_obj; jmethodID jni_methodID; //////////////////////////////////
JNIEXPORT void JNICALL Java_InitJNI(JNIEnv *env, jobject obj, jint sw, jint sh)
{
JavaVM* l_VM;
(*env)->GetJavaVM(env, &l_VM);
g_VM = (*env)->NewGlobalRef(env, l_VM);
//LOGW("----------- init jni --------------");
jni_obj = (jobject)(*env)->NewGlobalRef(env,obj);
}수정후 C - PlaySound
void PlaySound(int soundNum, boolean bLoop, int nNowTime)
{
JNIEnv* env;if(!g_VM) return;
(*g_VM)->AttachCurrentThread (g_VM, (JNIEnv **) &env, NULL); jni_cls = (*env)->FindClass(env, "com/example/exam/NativeCall"); jni_methodID = (*env)->GetMethodID(env, jni_cls, "PlaySound","(I)V");
(*env)->CallVoidMethod(env, jni_obj, jni_methodID, soundNum); }
위 처럼 수정하고 나니 아직까지 문제가 발생하지는 않았습니다.
위 처럼 수정하고 난뒤에 다시 지우고 g_VM을 가져오지 않은 상태에서 처음 initJNI에서
JNIEnv* jni_env를 NewGlobalRef로 할당하고 플레이 하면 같은 오류가 발생하더군요.
결국 (*g_VM)->AttachCurrentThread를 사용해야지만 제 문제는 해결되는듯 합니다.
제가 조언을 해주신대로 옳바르게 한건지 모르겠네요...
그런데 질문이 있습니다. jni_cls 와 jni_methodID값은 항상 같은 클래스 같은 메소드를 참조하게 됩니다.
그렇다고 해도 PlaySound에 넣어서 (*env)를 통해 계속 값을 할당받아야 하는지 궁금합니다.
PlaySound가 어쩌다 보니 거의 매프레임마다 불리어지게끔 되어있어서 그렇습니다.
* 소스코드에서 엔터만 치면 저렇게 나눠져 버리네요... 죄송합니다. 이쁘게 보여드려야하는데...
jni_cls 와 jni_methodID 차이는 obj 로 가져 오느냐 clz 로 가져 오느냐 차이 입니다.
보통 static 을 사용해서 가져 오는게 제일 간단하고 빠릅니다. 안전하기도 하고요
obj로 가져 올경우 현재 메모리에 있는 인스턴스 상황에 따라 가비지컬렉션까지 생각해야 합니다.
간단한 메서드 사용시 멤버변수와 메서드를 스태틱으로 호출하는게 제일 안전하다고 생각하고 있습니다.
호출 관계에서 AttachCurrentThread 를 사용하라고 한 이유는 메인 스레드가 아닐경우 간헐적으로
jni 연결이 안되는 경우를 찾았기 때문입니다.
그리고 에러 메세지를 보다 빠르고 정확하게 찾으실려면 ndk-stack 을 사용해 보시길 바라겠습니다.
C - initJNI
static JavaVM *g_VM; ////////////////////////////////// // jni enviroment static jclass jni_cls; static jmethodID jni_methodID; //////////////////////////////////
JNIEXPORT void JNICALL Java_InitJNI(JNIEnv *env, jobject obj, jint sw, jint sh)
{
(*env)->GetJavaVM(env, &g_VM);
jni_cls = (*env)->FindClass(env, "com/example/exam/NativeCall");
jni_methodID = (*env)->GetStaticMethodID(env, jni_cls, "PlaySound","(I)V");}
C - PlaySound
void PlaySound(int soundNum)
{
JNIEnv* env;
if(!g_VM)
return;
(*g_VM)->AttachCurrentThread (g_VM, &env, NULL);
(*env)->CallStaticVoidMethod(env, jni_cls, jni_methodID, (jint)soundNum);
}제가 최종적으로 수정한 소스입니다. C에서 호출되는 메소드인 자바메소드 PlaySound(위의 C-PlaySound가 아닙니다.)는
static void 함수로 선언했습니다. 아직까지 문제는 나타나지 않았습니다.
그리고 AttachCurrentThread의 두번째 인자인 &env를 (void** )&env로 호출하라고 되어있는 예제들이 많이있습니다.
제 환경에서는 (void**)를 포함했을 때 warnig이 발생하여 찾아보니 (void**)를 제거하고 하라더군요.
저와 같은 문제를 겪는 분들에게 도움이 되었음 합니다.
그럼 마지막으로 도움을 주신 아즈라엘님 감사합니다. 정말 많은 도움이 되었습니다.




일단 어떤 상황인지 에러 메세지도 없고 소스도 없어서 잘 모르겠지만
간헐적으로 나타난다면
GLSurfaceView 혹은 Thread 상황에서 Jni 연결한거라면
jvm->AttachCurrentThread(&env, NULL) 를 체크해주시길 바라겠습니다.