본문

안드로이드 리모트 서비스 만들기 #1. with AIDL

안드로이드 서비스는 백그라운드에서 실행하는 컴포넌트로 UI가 없다(윈도우의 서비스와 유닉스 데몬과 비슷). 그리고 서비스 객체는 스레드를 자동으로 만들지 않기 때문에, 별도의 스레드로 동작시키지 않으면 서비스가 메인 스레드에서 동작한다. 서비스가 하는 일이 짧으면 상관없지만 오래 걸리는 경우에는 별도의 스레드(혹은 AsyncTask)를 사용해야 한다. 


서비스는 로컬 서비스와 리모트 서비스로 구분되는데, 그중에서 리모트 서비스는 해당 애플리케이션 이외의 범위에서도 서비스를 사용할 수 있으며, 이를 위해서 AIDL(Android Interface Definition Language)를 사용한다. AIDL은 서비스를 사용하는 클라이언트에게 제공되는 인터페이스를 정의하고, 바인더로 사용할 Binder 클래스를 자동으로 생성(\gen에 저장됨)하는데 사용된다. 


AIDL은 클라이언트와 서비스상에서 공유되며, 클라이언트 부분에서는 AIDL을 참고하여 서비스에서 어떤 메소드를 제공하는지에 대해 알 수 있으며, 서비스 부분에서는 메소드 껍데기인 AIDL을 실제로 구현하여 클라이언트의 요청에 준비한다. 서비스를 다른 클라이언트에 노출하기 위해서, 서비스쪽 AndroidManifest.xml파일에 서비스를 추가하고 해당 인텐트 필터를 넣어준다. 위 내용은 아래와 같이 세가지 부분로 구분하여 진행할 수 있다.


1. AIDL 설정(어떤 메소드를 서로가 사용할 것인가에 대해 정의)

2. 서비스쪽에서 AIDL을 구현하고, AndroidManifest.xml을 설정하여 다른 클라이언트가 사용할 수 있도록 함.

3. 클라이언트쪽에서 AIDL을 사용하여 호출준비.


[아래 예제는 클라이언트에서부터 넘어온 숫자를 10초동안 매초마다 1씩 증가시켜 반환하는 간단한 서비스이다.]




1. AIDL 설정. (AIDL은 확장자가 .aidl인 자바 인터페이스이다.)

>ICalculatorService.aidl

package com.frontjang1.service_interface;

 

interface ICalculatorService{

     int increase(int num);

}


=> interface를 의미하는 I를 원하는 서비스명 앞에 붙여주고, 그 안에 서비스에서 클라이언트에게 제공하고자 하는 메소드를 기입한다. 저장을 완료하면 이클립스상에서 자동으로 소스를 처리하여 gen 폴더 아래에 새로이 java 파일을 생성하여 이를 사용한다. (자료형과 방향 지시자에 대한 사항은 다음 글에서 다룬다)


2-1. AIDL 구현

>CalculatorService.java

package com.frontjang1.service;

import com.frontjang1.service_interface.ICalculatorService;


public class CalculatorService extends Service {

     @Override

     public IBinder onBind(Intent arg0) {

           return new CalculatorServiceImpl();

     }

    

     public class CalculatorServiceImpl extends ICalculatorService.Stub{

           public int increase(int num) throws RemoteException {

                return ++num;

           }

     }

} 


=> 서비스 측에서는 구현되어있지 않은 AIDL(인터페이스)를 구현해주어야 한다. 이때 서비스는 onBind()를 오버로드 해야하는, android.app.Service를 상속한 클래스를 사용한다. onBind()의 역할은 바인딩시 AIDL의 구현체 객체를 리턴하는것이며, 이를 위해 코드 아래에 구현체 클래스를 정의하였다. 인터페이스.Stub을 상속하는 ~Impl(구현, implementation) 구현체 클래스는 AIDL에서 정의된 메소드를 정의한다. 


2-2. AndroidMenifest.xml 설정

>AndroidMenifest.xml

<service android:name="com.frontjang1.service.CalculatorService">

    <intent-filter >

        <action android:name="com.frontjang1.service_interface.ICalculatorService" />

    </intent-filter>

</service>


=> menifest -> application 태그 아래에 위의 코드를 삽입한다. 클라이언트에서는 new Intent(~)으로 서비스를 불러오는데, 이때 첫번째 인자로 위의 action android:name에 명시된 값이 들어간다. 즉, com.frontjang1.service_interface.ICalculatorService== ICalculatorService.class.getName() 이다.


3. AIDL 사용준비

>MainActivity.java

package com.frontjang2.client;

import com.frontjang1.service_interface.ICalculatorService;


public class MainActivity extends Activity {

     private ICalculatorService calculatorService=null;

     int i=0;

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    bindService(new Intent(ICalculatorService.class.getName()), servConn, Context.BIND_AUTO_CREATE);


    super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        new CountDownTimer(10*1000, 1000){

                @Override

                public void onFinish() {

                     increase();

                }

 

                @Override

                public void onTick(long arg0) {

                     increase();

                }

        }.start();

    }


    private ServiceConnection servConn=new ServiceConnection(){

           @Override

           public void onServiceConnected(ComponentName arg0, IBinder arg1) {

                calculatorService=ICalculatorService.Stub.asInterface(arg1);

           }

 

           @Override

           public void onServiceDisconnected(ComponentName arg0) {

                calculatorService=null;

           }

    }; 


    private void increase() {

           try {

                i=calculatorService.increase(i);

                TextView textView=(TextView)findViewById(R.id.number);

                textView.setText(Integer.toString(i));

           catch (RemoteException e) {

                e.printStackTrace();

           }

     }

   

}


=> onCreate()가 진입점인, 클라이언트의 Activity이다. ICalculatorService calculatorService와 ServiceConnection servConn을 발견할 수 있는데, 이때 servConn은 서비스와 클라이언트간의 연결을 관리해주는 객체로서, 서비스가 연결되었을 경우 원격지의 서비스를 불러와 현재 클라이언트에서 수행할 수 있도록 calculatorService에 대입해주고, 이 작업이 완료되면 calculatorService.메소드명()으로 서비스의 메소드를 호출할 수 있다. 


참고로 intent 생성자는 new Intent(인터페이스명, 서비스연결관리자변수, 메서드가_정상적으로_실행되면_이어서_ServiceConnection가_자동실행됨) 의 구조를 가지고 있다. 그리고 onServiceDisconnected()는 서비스 연결을 끊을때 발생하는것이 아니고, 서비스가 죽었을 경우 호출된다. 서비스를 연결하고 나서, CountDownTimer(){}에 있는 메소드를 통해 1초마다 증가되는 숫자를 화면에 출력한다. 그리고 bindService()를 실행하는데에는 0.1초정도 걸릴 수 있기 때문에 bindService()를 실행하고 바로 서비스를 사용하려고 하면 오류가 발생할 수 있다.



====================

일부러 서비스와 클라이언트, 그리고 aidl과 서비스를 분리해서 작성하였다. 이렇게 하면 관리가 편해지기 때문이다. '그러면 aidl파일은 클라이언트에서 접근하지 못하지 않는가?'와 같은 의문에는, 인터넷에 있는대로 서비스를 jar로 export 하고 그걸 클라이언트에서 라이브러로 불러오는 방법과, server\bin 파일들을 client\bin 으로(\gen?) 복사하는 방법이 있다. 하지만 그것보다는 이클립스-안드로이드에서 제공하는 기능을 사용하면 더 편하다. (본문 양을 줄이기 위해 위에는 설명을 넣지 않았다)


프로젝트->Property->Android->Library에서 서버는 Is Library에 체크하고, 클라이언트는 add버튼을 눌러 서비스를 라이브러리처럼 사용하여 aidl 설정을 불러올 수 있다. 하지만 is library를 체크하면 말그대로 라이브러리화가 되어 프로젝트 수행이 안되므로 신경써주어야 한다. 이에 관하여는 또다른 짧막한 글으로 설명하는게 좋을 것 같다. 이어서 리모트 서비스에 대한 더 자세한 이야기와, 콜백, Parcelable의 사용과 디버깅에 대해 작성할 예정이다.

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.