cocos2d-x JNI작업시 Activity UI Thread와 GLSurfaceView의 GLThread간 ThreadSafe하게 메세지 통신하기
지난번에 AD fresca Android용을 cocos2d-x에 붙이는 것과 cocos2d-x에 JNI를 사용하는 간단한 샘플을 정리 했었습니다. 이제는 샘플 단계를 넘어 현재 작업중인 C++ Framework에 통합을 해야했는데 아래와 같은 문제가 저를 괴롭혔습니다.
Fatal signal 11 (SIGSEGV) at 0x00000232 (code=1)
FATAL EXCEPTION: GLThread 593
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
threadid=13: thread exiting with uncaught exception (group=0x40a511f8)
FATAL EXCEPTION: GLThread 461
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:709)
at android.view.View.requestLayout(View.java:12675)
at android.view.View.requestLayout(View.java:12675)
at android.view.View.requestLayout(View.java:12675)
at android.view.ViewGroup.addView(ViewGroup.java:3206)
at android.view.ViewGroup.addView(ViewGroup.java:3188)
at com.android.internal.policy.impl.PhoneWindow.addContentView(PhoneWindow.java:282)
at android.app.Activity.addContentView(Activity.java:1883)
at com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:686)
at com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:633)
at org..game..ShowADFresca(.java:145)
at org.cocos2dx.lib.Cocos2dxRenderer.nativeRender(Native Method)
at org.cocos2dx.lib.Cocos2dxRenderer.onDrawFrame(Cocos2dxRenderer.java:59)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1462)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)
모두 이클립스 LogCat내용으로 제가 아직은 부족한 Java, Android관련 에러입니다. 구글링 들어갔죠.
Fatal signal 11 (SIGSEGV) at
먼저 위와 같은 키워드로 검색해봤을 때는 여러가지 원인으로 에러가 발생하는 것 같더군요. 그래서 해결책도 여러가지였는데, 확인을 위해서는 에러 주소값을 ndk-gdb에서 list로 확인하면 문제가 된 소스가 나온다고는 하는데 일단 gdb 사용할지 모르니 패스했습니다. gdb 관련 사용법은 다음에 해보고 정리를 따로 해봐야겠네요.
FATAL EXCEPTION: GLThread
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
다음으로 위와 같은 로그에서 공통된 키워드가 있는데요, 바로 Thread입니다.
android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
이 로그를 통해 Thread쪽 문제라는 것이 확실해졌습니다. 로그를 자세히 살펴봤습니다.
android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
...
android.view.View.requestLayout(View.java:12675)
...
android.view.ViewGroup.addView(ViewGroup.java:3188)
...
android.app.Activity.addContentView(Activity.java:1883)
com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:633)
org..game..ShowADFresca(.java:145)
org.cocos2dx.lib.Cocos2dxRenderer.nativeRender(Native Method)
org.cocos2dx.lib.Cocos2dxRenderer.onDrawFrame(Cocos2dxRenderer.java:59)
...
android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)
제일 아래가 먼저 출력된 로그죠. 대략적인 호출스택은 android의 GLSufaceView의 GLThread -> Cocos2dxRenderer의 java단 -> Cocos2dxRenderer의 C++단인 nativeRender -> 제가 만든 java 함수인 ShowADFresca -> ADfresca의 showAd -> android Activity의 addContentView -> view의 addView -> requestLayout을 거쳐 최종 checkThread입니다.
처음보기도 하고 그래서 눈에 띄기도 한 GLSufaceView, GLThread, Activity 등으로 구글링을 해봤습니다. 간단히 정리하면 android에는 Activity 생명주기라는 것이 있는데 보통 하나의 Process나 Thread가 Active 또는 Deactive에 따라 App이 돌아가고 안 돌아가는 뭐 그런 개념정도로 보면 되는 것 같더군요. 그리고 android에서 OpenGL ES를 사용하기 위해서는 GLSufaceView를 만들어 따로 GLThread를 통해 Randering을 수행한다고 합니다. 그러니까 App의 최상단 Activity가 Main Thread고 추가로 GLThread가 하나 더 돌게 되는 것이죠. 자세한 것은 추후 정리할 수 있을 때 더 정리하기로 해볼게요.
이제 문제 해결에 대한 내용을 정리해보겠습니다. 원인이 잘 못된 Thread 참조에 의한것이었죠. 그럼 android에서 JNI를 통해 Java < - > C++에 ThreadSafe하게 메세지를 주고 받는 것을 찾아야하겠죠. 즉 android Activity( Java )에서 발생한 사용자 입력 이벤트등을 native인 cocos2d-x( C++ )에 보낼 수 있어야하고 반대로 cocos2d-x에서 android로의 메세지 처리 모두 ThreadSafe하게 해야 합니다.
우선 Android -> C++을 먼저 살펴봅니다. 아래는 Cocos2dxGLSurfaceView.java의 소스중 터치 이벤트 부분을 가져왔습니다.
터치 다운이든 이동이든 간에 중요한 것은 queueEvent(new Runnable() { public void run() { ... } } 입니다. 이것이 Activity의 UI쓰레드에서 Renderer의 GLThread로의 ThreadSafe하게 해주는 놈입니다.
이제 반대로 C++ -> Android를 살펴봅니다. 간단히 MessageBox를 호출하는 부분을 보겠습니다.
JNI로 Android < - > C++간 ThreadSafe한 메세지 통신이 다른 해결책이 더 있을지도 모르지만 일단 전 위와같이 cocos2d-x가 해놓은 방법으로 Android용 ADfresca를 제 C++ Framework에 성공적으로 추가했습니다.
이래저래 에러내용으로 서론이 길고(로그 때문이긴 하지만) 결과적으로 해결부분은 짧게 나왔네요. GLSufaceView, Activity등을 좀 더 깊이있게 다루면 좋겠지만, 일단 블로그에 강좌식이 아닌 정리목적으로 포스팅을 하는것을 우선으로 두고 있기에 여기서 마무리해봅니다.
Fatal signal 11 (SIGSEGV) at 0x00000232 (code=1)
FATAL EXCEPTION: GLThread 593
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
threadid=13: thread exiting with uncaught exception (group=0x40a511f8)
FATAL EXCEPTION: GLThread 461
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:709)
at android.view.View.requestLayout(View.java:12675)
at android.view.View.requestLayout(View.java:12675)
at android.view.View.requestLayout(View.java:12675)
at android.view.ViewGroup.addView(ViewGroup.java:3206)
at android.view.ViewGroup.addView(ViewGroup.java:3188)
at com.android.internal.policy.impl.PhoneWindow.addContentView(PhoneWindow.java:282)
at android.app.Activity.addContentView(Activity.java:1883)
at com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:686)
at com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:633)
at org..game..ShowADFresca(.java:145)
at org.cocos2dx.lib.Cocos2dxRenderer.nativeRender(Native Method)
at org.cocos2dx.lib.Cocos2dxRenderer.onDrawFrame(Cocos2dxRenderer.java:59)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1462)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)
모두 이클립스 LogCat내용으로 제가 아직은 부족한 Java, Android관련 에러입니다. 구글링 들어갔죠.
Fatal signal 11 (SIGSEGV) at
먼저 위와 같은 키워드로 검색해봤을 때는 여러가지 원인으로 에러가 발생하는 것 같더군요. 그래서 해결책도 여러가지였는데, 확인을 위해서는 에러 주소값을 ndk-gdb에서 list로 확인하면 문제가 된 소스가 나온다고는 하는데 일단 gdb 사용할지 모르니 패스했습니다. gdb 관련 사용법은 다음에 해보고 정리를 따로 해봐야겠네요.
FATAL EXCEPTION: GLThread
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
다음으로 위와 같은 로그에서 공통된 키워드가 있는데요, 바로 Thread입니다.
android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
이 로그를 통해 Thread쪽 문제라는 것이 확실해졌습니다. 로그를 자세히 살펴봤습니다.
android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
...
android.view.View.requestLayout(View.java:12675)
...
android.view.ViewGroup.addView(ViewGroup.java:3188)
...
android.app.Activity.addContentView(Activity.java:1883)
com.adfresca.ads.AdFrescaView.showAd(AdFrescaView.java:633)
org..game..ShowADFresca(.java:145)
org.cocos2dx.lib.Cocos2dxRenderer.nativeRender(Native Method)
org.cocos2dx.lib.Cocos2dxRenderer.onDrawFrame(Cocos2dxRenderer.java:59)
...
android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1216)
제일 아래가 먼저 출력된 로그죠. 대략적인 호출스택은 android의 GLSufaceView의 GLThread -> Cocos2dxRenderer의 java단 -> Cocos2dxRenderer의 C++단인 nativeRender -> 제가 만든 java 함수인 ShowADFresca -> ADfresca의 showAd -> android Activity의 addContentView -> view의 addView -> requestLayout을 거쳐 최종 checkThread입니다.
처음보기도 하고 그래서 눈에 띄기도 한 GLSufaceView, GLThread, Activity 등으로 구글링을 해봤습니다. 간단히 정리하면 android에는 Activity 생명주기라는 것이 있는데 보통 하나의 Process나 Thread가 Active 또는 Deactive에 따라 App이 돌아가고 안 돌아가는 뭐 그런 개념정도로 보면 되는 것 같더군요. 그리고 android에서 OpenGL ES를 사용하기 위해서는 GLSufaceView를 만들어 따로 GLThread를 통해 Randering을 수행한다고 합니다. 그러니까 App의 최상단 Activity가 Main Thread고 추가로 GLThread가 하나 더 돌게 되는 것이죠. 자세한 것은 추후 정리할 수 있을 때 더 정리하기로 해볼게요.
//Cocos2dxRenderer.java public void onDrawFrame(GL10 gl) { ... nativeRender(); .... }
//MessageJni.cpp void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) { cocos2d::CCDirector::sharedDirector()->mainLoop(); }cocos2d-x를 통해 C++로 작업된 Native Code들은 모두 GLThread를 통해 처리되고 있던것입니다. Cocos2dxRenderer.java와 MessageJni.cpp를 확인해보시면 관련 된 함수를 보실 수 있습니다.
이제 문제 해결에 대한 내용을 정리해보겠습니다. 원인이 잘 못된 Thread 참조에 의한것이었죠. 그럼 android에서 JNI를 통해 Java < - > C++에 ThreadSafe하게 메세지를 주고 받는 것을 찾아야하겠죠. 즉 android Activity( Java )에서 발생한 사용자 입력 이벤트등을 native인 cocos2d-x( C++ )에 보낼 수 있어야하고 반대로 cocos2d-x에서 android로의 메세지 처리 모두 ThreadSafe하게 해야 합니다.
우선 Android -> C++을 먼저 살펴봅니다. 아래는 Cocos2dxGLSurfaceView.java의 소스중 터치 이벤트 부분을 가져왔습니다.
public boolean onTouchEvent(final MotionEvent event) { ... switch (event.getAction() & MotionEvent.ACTION_MASK) { ... case MotionEvent.ACTION_DOWN: // there are only one finger on the screen final int idDown = event.getPointerId(0); final float xDown = xs[0]; final float yDown = ys[0]; queueEvent(new Runnable() { @Override public void run() { mRenderer.handleActionDown(idDown, xDown, yDown); } }); break; case MotionEvent.ACTION_MOVE: queueEvent(new Runnable() { @Override public void run() { mRenderer.handleActionMove(ids, xs, ys); } }); break; ... }
터치 다운이든 이동이든 간에 중요한 것은 queueEvent(new Runnable() { public void run() { ... } } 입니다. 이것이 Activity의 UI쓰레드에서 Renderer의 GLThread로의 ThreadSafe하게 해주는 놈입니다.
public void handleActionDown(int id, float x, float y) { nativeTouchesBegin(id, x, y); }위와같이 Cocos2dxRenderer.java 에 handleActionDown등이 정의되어 있습니다.
private static native void nativeTouchesBegin(int id, float x, float y);또한 이렇게 선언되어 있구요.
// handle touch event void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesBegin(JNIEnv* env, jobject thiz, jint id, jfloat x, jfloat y) { cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesBegin(1, &id, &x, &y); }최종적으로 c++딴인 paltform/android/jni/TouchesJni.cpp에 위와 같이 구현되어 있습니다.
이제 반대로 C++ -> Android를 살펴봅니다. 간단히 MessageBox를 호출하는 부분을 보겠습니다.
void showMessageBoxJNI(const char * pszMsg, const char * pszTitle)cocos2dx\platform\android\jni\MessageJni.cpp에서 위 함수가 Android MessageBox를 호출하는 native 코드부분입니다. Java는 Cocos2dxActivity.java를 보시면 되는데요,
public class Cocos2dxActivity extends Activity{ ... private static Handler handler; private final static int HANDLER_SHOW_DIALOG = 1; ... protected void onCreate(Bundle savedInstanceState) { ... ... handler = new Handler(){ public void handleMessage(Message msg){ switch(msg.what){ case HANDLER_SHOW_DIALOG: showDialog(((DialogMessage)msg.obj).title, ((DialogMessage)msg.obj).message); break; } } }; } //cocos2d-x native에 의해 호출 되어지는 함수 public static void showMessageBox(String title, String message){ Message msg = new Message(); msg.what = HANDLER_SHOW_DIALOG; msg.obj = new DialogMessage(title, message); handler.sendMessage(msg); }C++ -> Android로 ThreadSafe하게 메세지 핸들링을 하려면 Handler라는 객체를 통해서 Message를 보내서 처리하면 됩니다.
JNI로 Android < - > C++간 ThreadSafe한 메세지 통신이 다른 해결책이 더 있을지도 모르지만 일단 전 위와같이 cocos2d-x가 해놓은 방법으로 Android용 ADfresca를 제 C++ Framework에 성공적으로 추가했습니다.
이래저래 에러내용으로 서론이 길고(로그 때문이긴 하지만) 결과적으로 해결부분은 짧게 나왔네요. GLSufaceView, Activity등을 좀 더 깊이있게 다루면 좋겠지만, 일단 블로그에 강좌식이 아닌 정리목적으로 포스팅을 하는것을 우선으로 두고 있기에 여기서 마무리해봅니다.
작성자가 댓글을 삭제했습니다.
답글삭제"Native Code(C/C++) < - > Java 양방향 호출해보자" 글에서의 프로젝트로... ThreadSafe를 테스트 해보고 있는데요..잘 안되어서 이렇게 글을 남기게 되었습니다.
답글삭제아래와 같이 했는데요..실행하자마자 죽어버리더라구요 ㅠ멀 잘못한걸까요ㅠㅠ
public class testPj extends Cocos2dxActivity{
private static Cocos2dxGLSurfaceView mCocos2dxGLSurfaceView;
//private static Handler sHandler = new Handler();
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mCocos2dxGLSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
nativeCppFunc(); // 자바에서 C++의 함수를 호출함.
}
});
}
private native void nativeCppFunc();//네이티브 코드 선언
음... c++ native딴 함수 정의부 오타 정도의 문제로 보이네요... 죽을때 나오는 에러로그가 있다면 더 확실할듯싶어요. 아마 Unknown 어쩌고 이런단어가 있다면 함수명 오타일듯 합니다.
삭제작성자가 댓글을 삭제했습니다.
삭제염치불구하고 에러 메세지를 올려봅니다 ㅠ
답글삭제FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.app.testPj/com.app.testPj.testPj}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2100)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2125)
at android.app.ActivityThread.access$600(ActivityThread.java:140)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1227)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4898)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.app.testPj.testPj.onCreate(testPj.java:46)
at android.app.Activity.performCreate(Activity.java:5206)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1083)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2064)
11 more
Unable to start activity ComponentInfo 에러.. 저도 있던거네요..
삭제제 블로그에서 왼쪽 최상단이나 오른쪽에 검색바에서 검색해보세요.
Unable to start activity ComponentInfo
이걸로요.. 해결이 되시길...
감사합니다 덕분에 해결했습니다^^
삭제해결 하셨군요 ^^
삭제