관리 메뉴

드럼치는 프로그래머

[안드로이드] Remote Service 구현하기 본문

★─Programing/☆─Android

[안드로이드] Remote Service 구현하기

드럼치는한동이 2013. 6. 14. 10:47

안드로이드 Activity life 사이클이 끝나면 해당 Activity는 안드로드 Activity Stack으로 부터 삭제되고,

다시 해당 Activity를 시작하면 onCreate부터 다시 시작된다.

그렇다면, 내가만든 엑티비티가 종료되어도 백그라운에서 동작하게 하고 싶다면 어떻게 할까??

방법은 여러가지 있지만~ 일반적으로 생각해 낼수 있는 방법은 역시 Service를 실행 시키는 방법이다~

Service는 Activity와 다른 프로세스이다.

다시 말하면 전혀 다른 프로그램이라고 봐도된다. 단지 interface가 동일해 RPC로 서로 통신할 수 있는 것 뿐이다.

많은 분들이 패키지 내에 Activity와 Service를 같이 만든다해서 같은 프로세스(프로그램)으로 보는 분들이 많은데..

절대 아니다 ㅠㅠ

이둘은 메모리 영역도 틀려서 서로간의 메모리를 공유 할 수 없다(서로 다른 프로세스이기 때문에)

그렇기 때문에 메모리를 공유하기 위해서는 정형화된 interface를 통해서 RPC 통신으로 객체를 주고 받아야한다.

 

그렇다면 간단하게 일반적인(??) Service를 구현해보자~~

 

 

 

위에 그림 에서 보듯이 Activity에서 Service를 시작하고 bind 한다. 
aidl은 정확하게 찾아보지는 않았지만, 느낌이 android interface definition language 일 것 같은 느낌이 팍팍 든다. 
이런 interface 명세구조는 COM 에서 익숙한 구조이다.  (본인은 처음 회사 생활을 시작할때 COM팀에 있었다ㅋ)
간단하게 말하자면~~

레고 블럭에 보면 튀어 나온 부분과 들어가는 부분의 모양이 서로 맞아야 두개의 블럭이 잘 조립되듯, 
Activity 와 Service도 두개의 프로세스가 interface가 정확하게 맞아야 서로간에 call 이 가능한 것이다. 
Service는 데몬처럼 엑티비티와 상관 없이 백그라운드로 실행되다가, IRemoteService를 통해 해당 interface call이 오면 구현부 동작을 하게 된다. 
IRemoteServiceCallback은 반대로 Service 에서 Activity로의 interface로 서비스에서 엑티비티 메소드를 호출한다. 
구조를 간편화 시키기위해 모든일을 서비스에서 처리하고 리턴하지 않고 Service <-> Activity 서로간에 통신이 가능하게끔 구현한 구조이다. 

우선 구현을 위해서 AndroidManifest.xml에 서비스를 등록한다. 

        <service android:name=".ServiceComponent" android:process=":remote">
            <intent-filter>
                <action android:name="com.infraware.dualnumber.IRemoteService" />
                
                <action android:name="com.infraware.dualnumber.REMOTE_SERVICE" />
            </intent-filter>
        </service>


Service를 등록했다면 aidl에 interface를 만들자 

  • IRemoteService
    package com.miso.test;
    import com.miso.test.IRemoteServiceCallback;
    
    interface IRemoteService {
    
        void registerCallback(IRemoteServiceCallback cb);    
        void unregisterCallback(IRemoteServiceCallback cb);    
        String onService(int msg);
        
    }
    


간단하게 설명 하면 
IRemoteServiceCallback을 인자로 받는 registerCallback(), unregisterCallback() 메소드는 서비스에서 엑티비티로 call back 하기 위해 만든 메소드로, 
서비스는 콜백 Function을 등록한 모든 엑티비티에 메소드를 call하게 된다. 
onService() 메소드는 간단하게 int 형의 메세지를 받아 해당하는 메세지의 일을 수행하고 String 값을 리턴하면 된다. 
onService를 호출하기 위해서 interface 자체가 마샬링 되어야 한다. 
따라서 인자가 커질수록(큰 객체..) 일수록 속도가 저하될 수 있다. 
상황에 따라 인자로 객체가 필요한 경우는 onService에 인자를 Message나 Bundle로 수정해 객체를 전달하는 것보다, 
aidl에 interface를 추가하는 식으로 구현을 하는 것이 퍼포먼스에 조금이나마 도움이 되지 않을까 생각한다. 

예를 하나들어보면 현재 진행하고 있는 프로젝트에 내부 로컬 DB가 있고 로컬 DB를 핸들링 하는 핸들링 클래스를 만들었는데..

Service에서 핸들링 클래스를 생성 했다. 그런데 이게 쓰다보니 Activity에서도 굉장히 많이 필요하게 되었다-_-;;

Service에서 생성하고 default DB Handler를 Activity로 보내줄까 했는데..

그냥 Activity 프로세스에서도 같은 DB Handler를 생성해 버렷다.

다른 프로세스끼리 통신하기 위해서 내부적으로 하는일이 생각보다 어마어마 하다;; +  ㅁ +

DB Handling이 자주 일어날 경우 지속적인 RPC통신은 퍼포먼스에 영향을 줄 수 있기때문에~~

메모리를 조금더 사용함을 감안 하더라도 같은 객체를 양쪽으로 생성하게 되었다 ㅠ0ㅠ
안드로이드 dev 사이트에서는 퍼포먼스를 위해 RPC 관련 마샬링을 직접 구현해도 된다고 나와 있다.
아래는 IRemoteServiceCallback.aidl interface 정의부 이다.

  • IRemoteServiceCallback
    package com.miso.test;
    
    oneway interface IRemoteServiceCallback {
        void MessageCallback(int msg);
    }
    


interface 정의가 모두 끝났다면 해당 interface를 구현하면 된다. 
우선 Activity에 아래와 같이 구현해 준다.

private IRemoteService mService = null;

    // Service Component
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        
        public void MessageCallback(int msg) {
        	mHandler.sendEmptyMessage(msg);
        }
    };
    
    
    // Service Connection
    private ServiceConnection mConnection = new ServiceConnection() {
    	
        public void onServiceConnected(ComponentName className,
                IBinder service) {
        	// IRemoteService Interface
            mService = IRemoteService.Stub.asInterface(service);
            
            try {
            	// Set Callback Function            	
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {	
            	Log.e(TAG, "onServiceConnected error", e);
            }
            
        }

        public void onServiceDisconnected(ComponentName className) {
            mService = null;     
            
        }
    };

MessageCallback의 구현은 간단하게 Activity 내의 핸들러 객체로 바로 메세지를 보내준다. 

그리고 아래처럼 MessageCallback 이 구현된 mCallback 객체를 바인딩과 동시에~ 서비스에 등록해준다~

mService.registerCallback(mCallback);


인터페이스 생성 시 Stub()라는 것이 있는데, 이는 같은 interface를 두고 Stub를 통해 인터페이스를 생성함을 뜻한다. 
이와 같은 구조로 인터페이스를 생성하면 엑티비티 A와, B가 같은 interface로 서로 다른 동작을 하게 구현을 할 수 있다. 
다음으로 ServiceConnection이 있는데, 이는 Activity 에서 서비스로 bind 시 이전에 AndroidManifest에 정의했던 Service의 interface를 받아온다. 
이때 받아오는 인터페이스는 Activity에서 만들 방식과 같이, Service에서 stub를 통해 만든 인터페이스를 전달하게 된다. 
Service의 구현은 아래와 같다.

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

 @Override
    public IBinder onBind(Intent intent) {

        if (IRemoteService.class.getName().equals(intent.getAction())) {
            return mBinder;
        }
        return null;
    }

   
    // IRemoteService
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public void registerCallback(IRemoteServiceCallback cb) {
        	
        	if (cb != null) {
            		mCallbacks.register(cb);
            }
        }        
        
        public void unregisterCallback(IRemoteServiceCallback cb) {
            if (cb != null) mCallbacks.unregister(cb);            
        }        

    	// Message Handler
        public String onService(int msg) {
        	
        	switch(msg) {
        	        // Service 내부 구현
        	}
        	return null;
        }
    };

    // Message Call-back
    private void sendMessageCallback(int msg)
    {
    	
    	final int N = mCallbacks.beginBroadcast();
    	for (int i=0; i<N; i++) {
    		
    		try {
				mCallbacks.getBroadcastItem(i).MessageCallback(msg);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
    	}
    	mCallbacks.finishBroadcast();
    }

onBind는 activity에서 bind시 보낼 interface를 리턴하면 된다. 
이는 요청하는 intent에 따라 다른 interface를 전달할 수 있는 구조이다. 
하나의 서비스는 여러개의 interface를 가지고 해당하는 상황에 알맞은 interface를 전달 할 수 있다. 
aidl에 정의했던 IRemoteService 의 구현을 Service에서 해준다. 
registerCallback 에서 콜백 함수 등록하고 unregisterCallback에서 콜백 함수를 해제한다. 
등록된 콜백 함수에 메세지를 보내기 위해 sendMessageCallback() 이라는 멤버 메소드도 만들었다. 
onService()에서 Activity에서 호출하는 int 형을 보고 직접적으로 Service에서 실행할 일들을 구현해 주면된다. 
Service의 onCreate()와 onDestroy()도 정성껏 필요한 리소스들을 로드하거나 생성하고 해제하는 루틴을 잘 만들어 주자. 
여기까지 구현이 다 되었다면, 
activity 에서 아래와 같이 호출하여 service를 바인드하고 서비스 메소드를 호출 할 수 있다. 

        startService(new Intent("com.infraware.dualnumber.REMOTE_SERVICE"));        
        bindService(new Intent(IRemoteService.class.getName()), mConnection, Context.BIND_AUTO_CREATE);

service 메소드를 호출하기 위해서는 아래와 같이 사용하면 된다.

        mService.onService(1/*message*/);

여기서 주의할점은 bindService를 했다고 mService가 바로 바인딩되지 않는다는 것이다. 
따라서 바인드 하자마자 서비스 메소드를 호출하면 해당 메소드가 호출이 될 수도 있고, 안될수도 있다.

아마 대부분의 경우 바로 호출되지 않을 것 이다.
Activity와 Service는 엄연히 다른 프로세스이다. 호출하고 그자리에서 리턴받는 것이아니다. 
따라서 바인드 하고 Service를 호출하기 이전에 Service가 바인딩 되었는지 확인 할 필요가 있다. 
thread 생성 후 while 문을 돌려서 mService가 null 이 아닐때까지 기다리다가 바인드 성공 후 mService 객체가 생성되었다면, 
Thread에서 Activity에 message나 시그널을 보내주도록 하는 방법을 사용할 수 있다.(뮤텍스나 세마포어 응용정도로 보면 된다.) 
아니면 더 시크한 방법으로 sleep 으로 잠깐 쉬어주자-_-;;;;

 

Service 관련된 사항은 ApiDemo에 RemoteService.java, RemoteServiceBinding.java를 참조하면 된다. 

 

[출처] http://blog.naver.com/oh4zzang?Redirect=Log&logNo=40112365166

Comments