출처: http://westwoodforever.blogspot.kr/2012/10/cocos2d-x-jni-activity-ui-thread.html
지난번에 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가 하나 더 돌게 되는 것이죠. 자세한 것은 추후 정리할 수 있을 때 더 정리하기로 해볼게요.
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등이 정의되어 있습니다.
또한 이렇게 선언되어 있구요.
최종적으로 c++딴인 paltform/android/jni/TouchesJni.cpp에 위와 같이 구현되어 있습니다.
이제 반대로 C++ -> Android를 살펴봅니다. 간단히 MessageBox를 호출하는 부분을 보겠습니다.
cocos2dx\platform\android\jni\MessageJni.cpp에서 위 함수가 Android MessageBox를 호출하는 native 코드 부분입니다.
Java는 Cocos2dxActivity.java를 보시면 되는데요,
|
C++ -> Android로 ThreadSafe하게 메세지 핸들링을 하려면 Handler라는 객체를 통해서 Message를 보내서 처리하면 됩니다.
JNI로 Android < - > C++간 ThreadSafe한 메세지 통신이 다른 해결책이 더 있을지도 모르지만
일단 전 위와같이 cocos2d-x가 해놓은 방법으로 Android용 ADfresca를 제 C++ Framework에 성공적으로 추가했습니다.
이래저래 에러내용으로 서론이 길고(로그 때문이긴 하지만) 결과적으로 해결부분은 짧게 나왔네요.
GLSufaceView, Activity등을 좀 더 깊이있게 다루면 좋겠지만, 일단 블로그에 강좌식이 아닌 정리목적으로 포스팅을 하는것을 우선으로 두고 있기에
여기서 마무리해봅니다.
'IT_Games > Cocos2D' 카테고리의 다른 글
[펌] cocos2d-x JNI를 가지고 android에서 Native Code(C/C++) ↔ Java 양방향 호출해보자 (0) | 2012.09.28 |
---|