Program/Android Java

안드로이드 Handler 이야기 remove send

너구리V 2013. 2. 6. 16:38

출처 : http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110116293622


시작하기에 앞서

 안드로이드 Handler 이야기. 두 번째 포스트입니다. 이전 포스트에서는 Handler 의 동작 원리에 대하여 소개한 만큼, 이번에는 Handler 를 이용해서 가능한 몇 가지 응용 시나리오, 그 중에서도 Handler 를 통해 서로 다른 어플리케이션 사이에서도 메세지를 주고 받을 수 있게 만들어주는 Messenger 클래스 관하여 이야기 해보겠습니다.


1. Handler 활용하기.

 우선 Handler 의 동작 방식을 간단하게 정리해 보겠습니다. 이전 포스트에 사용된 그림을 재활용 해보았습니다.


 Handler 는 MessageQueue 와 Looper 를 갖고 있는 스레드와 함께 동작하며, 여러 스레드에서 Handler 를 통해 메세지를 해당 Looper 에 메세지를 전달 할 수 있지만, 전달된  메세지는 우선 MessageQueue 에 저장된 후, Looper 에 의해 하나씩 하나씩 순서대로 꺼내진 후, Handler 의 handleMessage 메서드를 통해 처리됩니다.


 또한, Handler 는 Looper 의 메세지큐를 관리할 수 있는 메세드를 제공합니다. 대표적으로 가장 유용하게 사용될 수 있는 3 가지 메서드를 소개해 봅니다.

  • sendMessageAtFrontOfQueue : 특정 메세지를 MessageQueue 의 가장 처음으로 전달합니다. 현재 수행중인 메세지 처리 구문이 끝나면, 바로 해당 메세지가 처리됩니다.
  • sendMessageDelayed : 최소한 정해진 시간이 지난 후에, 해당 메세지가 처리됩니다. (해당 시간 이전에 메세지가 처리되지 않는 것만 보장됩니다.)
  • removeMessage : 특정 조건을 만족하는 메세지를 삭제합니다.

 이 메서들을 이용하면 메세지 루프 자체를 개발자의 의도대로 조작 할 수 있습니다. 대표적으로, 일정 시간마다 특정 작업을 수행하는 폴링 서비스를 생각할 수 있겠네요. 10초 단위로 메세지를 폴링하다가, 폴링을 멈추라는 메세지가 오면 바로 작업을 중지하는 예제 코드를 한벅 작성해 보았습니다.

mPoolingHandler = new Handler(mDownloadThread.getLooper()){


private final int POOLING_FREQUENCY = 1000 * 10;//10 seconds.


@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);


switch(msg.what){

case START_POOLING:

sendEmptyMessage(DO_POOLING);

break;

case STOP_POOLING:

removeMessages(DO_POOLING);

break;

case DO_POOLING:

//do some time consuming job.

try {

Log.d("HandlerSample", "Do Pooling");

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

sendEmptyMessageDelayed(DO_POOLING, POOLING_FREQUENCY );

break;

}

}

};

 위의 Handler 는 다음과 같은 방식으로 폴링 작업을 시작하거나 종료할 수 있습니다. 한 가지 폴링 작업을 멈출 때, 직접 removeMessage 메서드를 호출 한 것이 아니라, 별도의 메세지로 removeMessage 요청을 전달하 점에 주의하시면 좋습니다. removeMessage 을 호출 한다고 해서, 현재 진행중인 메세지 처리 작업이 정지되는 것은 아니기 때문에, 만일 DO_POOLING 작업이 수행 중인 와중에, removeMessage 가 호출 되면, 폴링 작업이 멈추지 않고 계속 반복되게 됩니다. (마지막에 sendEmptyMEssageDelayed 가 호출되서 그럿습니다.) 

public void handleClick(View v){

switch(v.getId()){

case R.id.start:

mDownloadHandler.sendEmptyMessage(START_POOLING);

break;

case R.id.stop:

Message msg = mDownloadHandler.obtainMessage(STOP_POOLING);

mDownloadHandler.sendMessageAtFrontOfQueue(msg);

break;

}

}

 간단한 예이지만, 여러가지로 활용 될 수 있습니다. 특히나 네트워크 작업을 처리할 때 유용한데, 단순 폴링이나. 네트워크 오류 시 일정 시간 후에 특정 요청을 재시도 하는 형태의 구문을 작성할 때도 유용합니다.) 별도의 스레드를 통해 작업을 수행하고자 하는데, AsyncTask 로는 왠지 좀 부족함이 느껴질 때, Handler 와 세 종류의 메서드를 한번 고려해 보시면 좋을거 같네요.


2. Messenger 를 통핸 프로세스간 통신.

 에.. 어쩌다 보니 서론이 길었네요. Handler 를 통해 서로 다른 스레드 간에 메세지를 주고 받을 수 있습니다. 그런데 거기서 한발 더 나아가, 프로세스의 경계를 넘어 서로 다른 어플리케이션, 서로 다른 프로세스에 존재하는 Handler 로 메세지를 주고 받을 수 있는 방법도 지원됩니다. 바로 이번에 소개해 드릴 Messenger 클래스를 활용하면 가능합니다. 안드로이드 개발자 사이트에 관련된 예제도 있습니다만, 모르시는 분들이 많더군요.


 안드로이드 개발자 사이트에 Messenger 클래스에 관해 설명한 내용을 참고해 보겠습니다.

 Messenger 는 특정 Handler 인스턴스의 리퍼런스를 갖고 있으며, 이를 이용하여 해당 Handler 로 메세지를 보낼 수 있습니다. 이를 이용하여, 프로세스간 메세지 기반 커뮤니케이션을 수행할 때 활용될 수 있습니다. 

 네. Messenger 는 특정 Handler 를 감싸는 클래스입니다. 가장 큰 특징은 바로, 이 Messenger 가 Parcelable 인터페이스를 구현하고 있다는 점 입니다. Handler 자체는 다른 프로세스로 넘겨 줄 수 없지만, 이를 Messenger 로 감싸면, 해당 Handler 로 원격에서 메세지를 전할 수 있는 Messenger 인스턴스를 생성할 수 있고, 이 Messenger 인스턴스는 한 프로세스에서 다른 프로세스로 이동 할 수 있습니다. 그래서, 복잡한 AIDL 을 정의하지 않고도 간편하게 Message 에 기반한 IPC 작업을 수행할 수 있습니다. 예제 코드를 살펴 보도록 하지요.


 우선 Message 를 수신할 서비스 쪽 의 onBind 함수 입니다.

public class MessengerService extends Service {


Messenger mMessenger = new Messenger(new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

}

});

    

@Override

public IBinder onBind(Intent intent) {

return mMessenger.getBinder();

}

}//end of class

 간단합니다. Message 를 처리하기 위한 Handler 를 생성한 후, 해당 Handler 를 참조하는 Messenger 인스턴스를 생성합니다. 그리고 서비스의 onBind 요청 시, 생성한 Messenger 인스턴스의 getBinder() 메서드를 이용하여, IBInder 객체를 클라이언트 쪽으로 전달해 주면 됩니다. 임의로 정의한 AIDL stub 객채의 Binder 를 전달해주는 것과 거의 유사합니다.


 해당 서비스와 연결하는 클라이언트 코드는 다음과 같습니다.

public static class MessengerClient extends Activity {


Messenger mService = null;

private ServiceConnection mConnection = new ServiceConnection() {

public void onServiceConnected(ComponentName className,

IBinder service) {


mService = new Messenger(service);

}

public void onServiceDisconnected(ComponentName className) {

mService = null;

}

};


@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);


bindService(new Intent(MessengerClient.this, 

MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);

}

}

 일반적인 서비스 바인딩 작업과 크게 다르지 않습니다. 그저 사용자가 임의로 작성한 AIDL 을 사용하는 것보다 코드도 훨씬 간단한 편이지요. 서비스 바인드가 성공적으로 수행되면, 서비스가 전달해준 IBinder 객체를 기반으로 Messenger 인스턴스를 생성하면 됩니다.  그 다음에...

Message msg = Message.obtain();

mService.send(msg);

 이런 식으로 메세지를 전달하면, 놀랍게도 다른 프로세스에서 동작 중인 서비스의 Handler 로 메세지가 전달 됩니다. 귀찮게 AIDL 을 정의하거나, AIDL 클래스가 잘 생성되었는지, gen 폴더를 들락 날락 거리지 않고도 깔끔하게 프로세스간 통신을 수행할 수 있습니다.


 한 가지 더, 만일 서비스 측에서 클라이언트 쪽으로 응답 메세지를 보내야 할 경우 Message 의  replyTo 필드를 활용할 수 있습니다. 절차는 거의 동일합니다. 클라이언트에 응답 메세지를 수신할 Handler 인스턴스를 구현한 후, 해당 Handler 를  참조하는 Messenger 를 구성합니다. 그리고 서비스 쪽으로 메세지를 전달할 때, Message replyto 필드에 해당 Messenger 인스턴스를 첨부합니다. Message 를 수신한 서비스 핸들러는 replyTo 필드의 Messenger 인스턴스를 통해 응답 메세지를 클라이언트로 전달 할 수 있습니다. 아주 손쉽게 콜백 기능을 구현 할 수 있는 셈 입니다. 


3. Messenger 의 동작 원리.

 와. Messenger 는 참 편리한 녀석입니다. 그런데 어떻게 이런 일이 가능할까요? 사실 특별한 비밀은 없습니다. Messenger 가 특별하게 복잡한 일을 수행해주고 있는 것은 아니며, 일반 사용자들도 사용할 수 있는 AIDL 형식을 동일하게 이용하고 있습니다. 한 마디로 안드로이드 플랫폼에서 미리 정의해 둔 AIDL 이라고 할까요?


 Messenger 프레임워크 소스를 살펴보면 Messenger 인터페이스를 정의한 IMessenger.aidl 이 있습니다. 

package android.os;

import android.os.Message;

/** @hide */

oneway interface IMessenger {

    void send(in Message msg);

}

 보시는 것 처럼, 단 하나의 IPC 메서드 send(Message msg) 메서드를 구현하고 있지요. 그리고 이 AIDL 인터페이스의 실제 stub 클래스는 바로 Handler 클래스 아래 private 클래스로 정의되어 있습니다. 자기 자신의 메세지큐에 메세지를 넣도록 구현되어 있더군요. 그리고 이 stub 클래스는 Handler 를 이용하여, Messenger 가 생성하는 시점에만 생성되게 구현되어 있습니다. 따라서, 일반적으로 Handler 를 사용할 때는 오버헤드가 발생하지 않으며, 동시에 반드시 Handler 를 이용해야만, Messenger 클래스를 생성할 수 있도록 강제하고 있습니다. 


 또 한가지, Messenger 를 통해 Message 를 주고 받을 수 있는 또 하나의 가장 중요한 이유는 바로, 위의 클래스 정의에서 볼 수 있듯이, Message 클래스 자체가 가 Parcelable 인터페이스를 구현한 객체이기 때문입니다. 그런데 어랴? Message 는 필드 멤버로 임의의 Object 도 집어 넣을 수 있는 녀석인데 (obj), 어떻게 Parcelable 이 될 수 있는걸까요?  Parcelable 인터페이스는 객체 전체를 포장하는 개념이 아니라, 해당 객체 내에서 선별된 정보만을 포장 하는 방식으로 구현될 수 있기 때문입니다. 예를 들어, Message 의 obj 필드의 경우, 만일 안드로이드 플랫폼 내에서 기본적으로 제공하는 Parcelable 객체를 넣는 경우, 해당 인스턴스가 전달되지만, 그렇지 않을 경우에는 null 값이 담기게 됩니다.


 사용자가 임의로 작성한 Parcelable 클래스를 전달하기위해서는 Message 의 Bundle 을 활용할 수 있습니다. 단, 이때 주의가 필요한데, 안드로이드 플랫폼 관점에서 보자면, Messenger 를 통해 전달된 메세지는 한 JVM 에서 다른 JVM 으로 건너 띄게 됩니다. 그리고, 안드로이드 플랫폼 상에서 모든 JVM 이 공통적으로 갖고있는 클래스로더는 오직 한가지. 바로 Zygote 에 올라가 있는 안드로이드 기본 라이브러리 뿐입니다. 따라서, 사용자가 임의로 생성한 Parcelable 객체들은 비록 다른 프로세스로 전달되더라도 올바르게 해석될 수 없습니다. 이런 경우, 메세지를 수신한 쪽에서 해당 Parcelable 객체를 해석할 수 있는 적절한 ClassLoader 를 선언해 주어야 합니다. 아래와 같은 형식이 될 것입니다. 

public void handleMessage(Message msg) {

Bundle bundle = msg.getData();

bundle.setClassLoader(MyParcelableClass.class.getClassLoader());

MyParcelableClass mydata = bundle.getParcelable("My Data");

}

 이와 관련해서는 AIDL 을 통해 리모트 서비스 연결에 관한 이전 포스트를 참조하셔도 좋을 듯 합니다. 또, 클래스 로더 및 다이나믹 클래스 로드에 관한 또 하나의 포스트를 계획하고 있으니 앞으로 보다 상세히 설을 풀어볼 기회가 있을거 같네요.,


결론

 Handler 는 편리한 클래스입니다. 안드로이드에서 스레드간 통신 뿐만 아니라, 프로세스간 통신을 하는데도 활용될 수 있지요. 비교적 간단한 메세지를 주고 받고, AIDL 을 사용하자니 귀찮은, 그러니까 대부분의 경우... Messenger 와 Handler 를 활용하는 방안을 고려해 보시면 좋을것 같습니다. 이와 관련해서 안드로이드 개발자 사이트에 명시된 내용을 소개해 드리며 글을 마치겠습니다~~~


주의: AIDL 은 오직 Client 의 요청의 결과가 즉시 return 되어야 하기 때문에, Service 단에서 동시에 여러 스레드를 통해 개별 요청을 처리해야 하는 경우에만 필요합니다. 만일 그렇지 않은 경우에는 Messenger 클래스를 활용하세요.

반응형