Android Caused by: android.os.NetworkOnMainThreadException

Shutting down VM
threadid=1: thread exiting with uncaught exception (group=0x40a461f8)
FATAL EXCEPTION: main
java.lang.Error: FATAL EXCEPTION [main]
Unity version     : 4.1.2f1
Device model      : Acer A500
Device fingerprint: acer/a500_ww_cus1/picasso:4.0.3/IML74K/1336617649:user/release-keys
Caused by: android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1099)
at java.net.InetAddress.lookupHostByName(InetAddress.java:391)
at java.net.InetAddress.getAllByNameImpl(InetAddress.java:242)
at java.net.InetAddress.getAllByName(InetAddress.java:220)
at libcore.net.http.HttpConnection.<init>(HttpConnection.java:71)
at libcore.net.http.HttpConnection.<init>(HttpConnection.java:50)
at libcore.net.http.HttpConnection$Address.connect(HttpConnection.java:351)
at libcore.net.http.HttpConnectionPool.get(HttpConnectionPool.java:86)
at libcore.net.http.HttpConnection.connect(HttpConnection.java:128)
at libcore.net.http.HttpEngine.openSocketConnection(HttpEngine.java:308)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:460)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:432)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:282)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:232)
at libcore.net.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:80)
at libcore.net.http.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:188)
at libcore.net.http.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:280)
at com.google.android.gcm.server.Sender.post(Sender.java:479)
at com.google.android.gcm.server.Sender.post(Sender.java:458)
at com.google.android.gcm.server.Sender.sendNoRetry(Sender.java:170)
at com.google.android.gcm.server.Sender.send(Sender.java:121)
at com.test.gcm.MainActivity$1.run(MainActivity.java:49)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4424)
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:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Force finishing activity com.test.gcm/.MainActivity

 유니티3D에 GCM 연동 작업중 위와같은 오류가 발생했습니다. thread라는 단어가 보이지만 기존에 처리했던 UI Thread문제는 아니더군요. android.os.NetworkOnMainThreadException 로 검색을해보니 네트워크 처리때문에 생긴건데 허니컴 이전에는 그냥 메인 쓰레드에서 처리해도 되었지만 허니컴 이상부터는 꼭 따로 Thread를 둬서 처리하게 바꼈다고 합니다. 하긴 지금은 GCM같이 작은 작업이지만 만약 큰 작업을 메인쓰레드에서 처리해서 잡아먹고 있다면 블록이 걸려 화면 응답속도가 현저히 느려지겠죠.

 안드로이드에서 쓰레드 처리는 기존에 처리했던 runOnUiThreadHandler, Message말고도 Thread로 하거나 AsyncTask로 한다고합니다. GCM 처리하는데 있어 간단하게 Send정도만 하는 이벤트성 처리이므로 Thread보다는 AsyncTask로 처리하는 것을 정리해봅니다.

 간단하게 AsyncTask를 정리해보면 데이터 로딩하면서 프로그래스바등 UI의 갱신등 주로 비동기 작업에 쓰이는 클래스입니다. Handler와 Message를 사용하지 않아도 AsyncTask하나만으로도 각 상태별 핸들링 처리도 가능하네요.

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }
 안드로이드 개발자 문서에서 AsyncTask 샘플소스를 가져왔습니다. AsyncTask 객체를 만들때 제너릭 타입 3개를 넘겨주는데 <Params, Progress, Result> 에 매칭됩니다.

  • Params - 쓰레드 작업에 필요한 데이터 타입
  • Progress - 쓰레드 작업중 진행 상황을 표현하는데 사용되는 타입
  • Result - 작업의 결과로 리턴될 타입
 각 타입별 사용하는 함수는 색상으로 처리해봤으니 참고하세요. 만약 제너릭 타입들이 필요없다면 <Void, Void, Void> 이런식으로 해주면 됩니다.

 위에 샘플에서 오버라이드한 함수말고도 void onPreExecute() 하나 더 있습니다. 호출 순서는 아래와 같습니다.

  1. void onPreExecute() - AsyncTask가 execute 되자마자 호출됨. 쓰레드 처리를 하기전에 처리해야할 것들을 해주면 된다.
  2. Result doInBackground(Params... params) - 쓰레드로 처리가 되는 부분. publishProgress를 호출해서 UIThread의 갱신을 처리할 수 있다.
  3. void onProgressUpdate(Progress... values) - publishProgress가 호출되면 호출된다. UI 진행 상황을 갱신하면 된다.
  4. void onPostExecute(Result result) - 쓰레드 작업이 완료되면 호출된다.

new DownloadFilesTask().execute(url1, url2, url3);
 실행은 이렇게 해주죠. 각 인자들은 Params로 정의해준 타입의 배열로 넘어값니다. 그런데 이건 일반 안드로이드 어플 개발시 이렇게 해주고 만약 유니티3D나 cocos2d-x와 같이 GLThread를 사용하용해 외부 SDK 모듈 연동을 하신다면 runOnUiThread를 사용해서 처리해줘야합니다.

 간단한 AsyncTask 설명은 여기서 마무리합니다. 위 스샷은 곧 포스팅할 유니티3D에 GCM을 적용해본 로그 스샷입니다.

 추가적으로 StrictMode.enableDefaults(); 라는 Strict Mode를 사용해서도 간단히 해결할 수 있다고는 하네요. 어플리케이션의 IO 동작중 성능 저하 요소를 찾아서 개선할 수 있게 모니터링 해주는 기능이라고 합니다. 개발자 버전에서만 사용하는게 좋을 듯합니다. 자세한 것은 링크로 대체합니다.

댓글

이 블로그의 인기 게시물

'xxx.exe' 프로그램을 시작할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

goorm IDE에서 node.js 프로젝트로 Hello World Simple Server 만들어 띄워보기

애드센스 수익을 웨스턴 유니온으로 수표대신 현금으로 지급 받아보자.