Hyun Chul's Utopia
AIDL을 이용한 Service <-> Activity간 Callback통신. 본문
Service를 이용하여 백그라운드에서 작업을 하다보면, Activity로 데이터를 전달 해야 한다던가, 혹은 어떠한 순간에 데이터를 전달해 주어야 할 때가 있습니다. 처음에 이 작업을 위하여 삽질을 하다보니 좋은 방법이 있다는걸 뒤늣게 알아 버렸습니다.
Android에서는 Service에 Bind된 클라이언트들은 Service로부터 특정 시점에 특정 데이터를 Callback 받기 위하여 RemoteCallbackList 라는 방법을 제공합니다. 해당 클래스는 템플릿 형태로 되어 있으며 원하는 타입을 지정하여 사용할 수 있습니다.
사용법은 간단합니다. Bind를 위한 ADIL과, Callback 시점에서 수행할 interface class를 정의한 ADIL이 준비물의 전부 입니다. Bind를 위한 ADIL에서는 Callback를 등록/제거 할 수 있는 정도가 있으면 되겠네요. 예제 코드가 길지 않으니 바로 소스를 보시면 바로 이해하실 수 있을것 같습니다.
IRemoteService (ADIL)
package com.example.servicetest.service; import com.example.servicetest.service.IRemoteServiceCallback; interface IRemoteService { boolean registerCallback(IRemoteServiceCallback callback); boolean unregisterCallback(IRemoteServiceCallback callback); }
IRemoteServiceCallback (ADIL)
package com.example.servicetest.service; oneway interface IRemoteServiceCallback { void valueChanged(long value); }
TestService (Class - Service)
package com.example.servicetest.service; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; public class TestService extends Service{ public static final String INTENT_ACTION = "intent.action.bhc.test.service"; private static final int MSG_WORK = 1; final RemoteCallbackListcallbacks = new RemoteCallbackList (); private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public boolean unregisterCallback(IRemoteServiceCallback callback) throws RemoteException { boolean flag = false; if(callback != null){ flag = callbacks.register(callback); } return flag; } @Override public boolean registerCallback(IRemoteServiceCallback callback) throws RemoteException { boolean flag = false; if(callback != null){ flag = unregisterCallback(callback); } return flag; } }; @Override public IBinder onBind(Intent intent) { Log.d("BHC_TEST", "onBind.."); if(intent.getAction().equals(INTENT_ACTION)){ Log.d("BHC_TEST", "action is equals.. :: " + intent.getAction()); return mBinder; } Log.d("BHC_TEST", "action is not equals.."); return null; } @Override public void onCreate() { Log.d("BHC_TEST", "Service is onCrreate.."); handler.sendEmptyMessage(MSG_WORK); super.onCreate(); } @Override public void onDestroy() { Log.d("BHC_TEST", "Service is onDestory.."); handler.removeMessages(MSG_WORK); super.onDestroy(); } private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // TODO Auto-generated method stub switch (msg.what) { case MSG_WORK: int N = callbacks.beginBroadcast(); for(int i = 0; i < N; i++){ try { callbacks.getBroadcastItem(i).valueChanged(System.currentTimeMillis()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } Log.d("BHC_TEST", "Handler work.. :: callbacks clients count is " + N); callbacks.finishBroadcast(); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } handler.sendEmptyMessage(MSG_WORK); break; default: break; } return false; } }); }
MainActivity (Class - Activity)
package com.example.servicetest; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.Menu; import com.example.servicetest.service.IRemoteService; import com.example.servicetest.service.IRemoteServiceCallback; import com.example.servicetest.service.TestService; public class MainActivity extends Activity { IRemoteServiceCallback mCallbcak = new IRemoteServiceCallback.Stub() { @Override public void valueChanged(long value) throws RemoteException { Log.i("BHC_TEST", "Activity Callback value : " + value); } }; IRemoteService mService; ServiceConnection mConntection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub if(mService != null){ try { mService.unregisterCallback(mCallbcak); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub if(service != null){ mService = IRemoteService.Stub.asInterface(service); try { mService.registerCallback(mCallbcak); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }; private void startServiceBind(){ startService(new Intent(this, TestService.class)); bindService(new Intent(TestService.INTENT_ACTION), mConntection, Context.BIND_AUTO_CREATE); } private void stopServiceBind(){ unbindService(mConntection); stopService(new Intent(this, TestService.class)); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onResume() { // TODO Auto-generated method stub startServiceBind(); super.onResume(); } @Override protected void onStop() { // TODO Auto-generated method stub stopServiceBind(); super.onStop(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
위의 코드를 실행하면 결과를 Logcat를 통해서 확인할 수 있습니다.
서비스에서 생성한 숫자를 Activity에서 Service에 Bind한 뒤, 등록된 Callback를 통하여 원하는 데이터를 전달 받습니다. 마치 Observer패턴의 구조와 비슷하다고 느껴지기도 하네요..
테스트코드의 첨부파일 입니다.
아직 궁금한점은... AIDL Interface 작성시.. oneway라고 명시해 주어야 하는데, 저도 지식이 얕은지라..;;; oneway 가 무슨 이유로 붙는지는 잘 모르겠습니다. 대충 알아본 바로는 Service와 통신을 하기 위한 과정에서 데이터의 동기화를 보장하기 위한 용도로 사용된다는 것 같은데.. 정확히 이해를 못했네요.. (영어가 짧아서.. -_-;;) 혹시 자세히 아시는 분은 댓글 부탁 드립니다.
좋은 자료가 되어서 블로그에 방문하신 분들께 유익한 자료가 되었으면 좋겠습니다.
'프로그래밍 > Android' 카테고리의 다른 글
기기에 설치된 어플리케이션 확인 및 마켓 연결 방법 (0) | 2013.11.13 |
---|---|
Media Scanning 수행시키기 (0) | 2013.01.07 |
허니컴 이하에서 ActionBar 사용하기 (ActionbarSherlock) (9) | 2012.10.05 |
엑티비티 상에서 키보드의 스타일 지정 (0) | 2011.10.04 |
하단 커스텀 탭 메뉴 만들기 (13) | 2011.08.11 |