이번 과제에서 안드로이드 클라이언트 파트를 만드는 도중 진저브레드인 폰에서는 제대로 동작하는 코드가 갤럭시 탭 10.1에서는 동작하지 않아서 삽질을 좀 하게 되었습니다. 구글링한 결과 해당 예외의 원인은 "안드로이드 OS 3.0 버전부터 Main Thread에서 네트워크 관련 작업을 할 수 없게 되었다" 였습니다.

 Main Thread에서는 UI 관련 작업을 해야 하는데 네트워크 작업은 시간이 오래 걸릴지도 모르는 작업인데 그동안 UI 관련 작업을 처리해 줄 수 없어 3.0 버전부터는 아예 Main Thread에서 네트워크 작업을 할 수 없게 강제화시켰다는 내용이었습니다.

 제 코드는 Thread에서 통신을 하고 Handler에서 이 Thread들을 제어하는 구조에, Handler는 Main Thread에서 생성해서 사용하는 방식이었습니다. 여기서 문제점은 Handler는 자신을 생성한 스레드에 의존적이라는 점이었는데, 저는 이걸 모르고 코드를 작성해서 문제가 발생한 것이었습니다.

 알아본 해결법들로는 3가지가 있습니다.

  1. 먼저, Handler와 Thread 대신 AsyncTask를 사용하면 해결됩니다. AsyncTask가 나오게 된 배경도 Handler와 Thread로 나누어진 코드를 보다 간단히 캡슐화할 수 있고 되어 있었지만, 코드를 다시 작성해야 해서 이 방법말고 다른 방법이 없을까 하고 더 찾아보았습니다.
  2. 다음으로, StrictMode가 있는데 이 방법을 사용하면 기존 코드의 변경없이 간단한 코드 한줄 추가로 해결이 되지만, 개발 버전에서만 사용하고 배포할 때는 빼기를 추천하고 있었습니다. 이걸 그대로 쓰기는 찜찜하고, AsyncTask를 사용하자니 작업량이 많고.. Handler와 Thread를 그대로 쓸 수 있는 방식을 더 찾다가 간단한 방법을 찾았습니다.
  3. Handler를 생성, 관리하는 Thread를 만드는 것이 제가 선택한 방법입니다. Main Thread에서 바로 Handler를 생성하지 않고, Handler를 생성할 Thread를 생성해주면 됩니다. 다만, Handler를 생성하려면 해당 Thread가 Handler 생성을 지원할 수 있어야 하는데 아래와 같은 방식으로 해결할 수 있었습니다.
    // 핸들러를 생성할 스레드의 run() 메소드
    public void run() {
    	Looper.prepare();
    	/*
    	 * 여기에서 핸들러 생성
    	 */
    	Looper.loop();
    }
    

     이렇게 하고, 핸들러를 생성한 스레드에서 메소드를 따로 만들어 해당 핸들러로 message를 날려주는 방식으로 작성했습니다.


+ 2012.11.05 추가사항

 Looper.loop() 호출 부분에서 블록됩니다.

 위 코드에서 루프를 걸고 socket을 read하고 있을 때 Looper.loop()를 호출하면 더 이상 다음 번 루프로 넘어가지 않았습니다. 그래서 더미 스레드를 하나 만들고 그 안에 위 코드를 집어 넣는 걸로 급하게 해결은 했지만, 다음 번에는 처음부터 AsyncTask로 만들어야겠습니다.

 한 달 전 쯤에 발견했는데 까먹고 있다가 이제서야 올리네요.


* 출처

http://kimeunseok.com/archives/853

http://blog.naver.com/PostView.nhn?blogId=rgjoon&logNo=90094338577

AND