Hyun Chul's Utopia

AIDL을 이용한 Service <-> Activity간 Callback통신. 본문

프로그래밍/Android

AIDL을 이용한 Service <-> Activity간 Callback통신.

디프시다루핀 2012. 10. 15. 17:33

 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 RemoteCallbackList callbacks = 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_Service_Example.zip




 아직 궁금한점은... AIDL Interface 작성시.. oneway라고 명시해 주어야 하는데, 저도 지식이 얕은지라..;;; oneway 가 무슨 이유로 붙는지는 잘 모르겠습니다. 대충 알아본 바로는 Service와 통신을 하기 위한 과정에서 데이터의 동기화를 보장하기 위한 용도로 사용된다는 것 같은데.. 정확히 이해를 못했네요.. (영어가 짧아서.. -_-;;) 혹시 자세히 아시는 분은 댓글 부탁 드립니다.


좋은 자료가 되어서 블로그에 방문하신 분들께 유익한 자료가 되었으면 좋겠습니다.





Comments