(원문: http://developer.android.com/guide/components/aidl.html)

(위치: Develop > API Guides > App Components > Services 
> AIDL)

(전체 번역 글 목록으로 이동하기)


안드로이드 인터페이스 정의 언어
(Android Interface Definition Language (AIDL))


AIDL은 IDL(인터페이스 정의 언어)들과 비슷합니다. 이것은 클라이언트와 서버가 서로 다른 프로세스에서도 통신(IPC)할 수 있도록 프로그래밍 인터페이스를 정의하는데에 사용됩니다. 안드로이드 시스템에서, 프로세스는 다른 프로세스의 메모리에 직접 접근할 수 없습니다. 따라서 다른 프로세스와 통신하기 위해서는, 우선 객체들을 OS가 이해할 수 있도록 원시 타입의 데이터(primitives)로 분리하고, 마샬링(직렬화와 유사)을 할 필요가 있습니다. 그런데 마샬링을 위한 개발작업은 상당히 지루하기 때문에, 안드로이드는 AIDL을 이용하여 그것의 상당부분을 대신 해줍니다. 

메모: AIDL은, 다른 앱의 클라이언트가 내 앱의 서비스에 IPC를 해야하고, 내 앱의 서비스가 IPC를 통해 들어온 요청들을 멀티쓰레드 방식으로 처리해야하는 경우에만 직접 사용하고, 그렇지 않은 경우에는 다른 방법들을 사용하기를 권장합니다. 만약 다른 앱에서 IPC를 하는 것이 아니라, 내 앱 내에서 앱컴포넌트가 서비스를 바인드하는 경우라면, Binder를 확장하는 방식을 사용합니다. 그리고 만약 IPC를 사용해야하기는 하지만 멀티쓰레드 방식으로 동시에 요청을 처리할 필요가 없다면, Messenger를 사용하면 됩니다. AIDL을 구현하기 전에 일단은 바인드 되는 서비스에 대해 학습하시기를 권장합니다. 

AIDL 인터페이스를 사용하도록 설계를 시작하기 전에, AIDL 인터페이스는 함수를 직접 호출하는 방식임을 기억하시기 바랍니다(Handler를 이용하는 방식이 아니라는 의미입니다). 그래서 호출이 내 로컬 프로세스의 쓰레드에서 발생한 것인지, 아니면 원격 프로세스의 쓰레드에서 발생한 것인지에 따라 차이가 있습니다. 자세한 내용은 아래와 같습니다:

  • 로컬 프로세스에서 호출이 발생하는 경우에는, 호출을 하는 쪽과 호출을 받는 쪽이 같은 쓰레드에서 실행됩니다. 메인 UI 쓰레드에서 AIDL 인터페이스를 호출했다면 메인 UI 쓰레드에서 서비스 로직이 실행되고, 다른 쓰레드에서 호출했다면 그 쓰레드에서 실행된다는 것입니다. 따라서, 로컬 쓰레드들에서만 서비스가 실행된다면, 어떤 쓰레드에서 서비스가 실행되는지를 관리할 수 있습니다 (그러나 이러한 경우라면, AIDL을 사용하지 말고, Binder를 확장하는 방법을 사용하시기 바랍니다). 
  • 원격 프로세스에서 호출이 발생하는 경우에는, 내 앱의 프로세스 안에서 관리되는 쓰레드풀로부터 호출이 옵니다. 따라서 임의의 쓰레드들(unknown threads)로부터 호출이 오는 것과, 동시에 여려개의 호출이 오는 것에 대한 대비를 해야 합니다. 다시 말해서, AIDL 인터페이스를 구현할 때는 멀티쓰레드 문제를 고려해야 한다는 것입니다(thread-safe).
  • oneway는 원격 호출의 실행 방식을 지정하는 키워드 중 하나로, 인터페이스 선언부나 인터페이스의 메소드 선언부에 붙일 수 있습니다. oneway 키워드를 사용하면, 원격 호출시 블록킹 되지 않고(not block), 데이터를 전송한 직후에 바로 리턴됩니다. 인터페이스의 구현체는 그 전송된 데이터를, Binder 쓰레드풀로부터 받습니다. 하지만 oneway로 지정된 인터페이스라도, 로컬 프로세스 내에서의 호출에 대해서는 효과가 없으며, 여전히 동기적 방식으로 동작하게 됩니다. 


AIDL 인터페이스 정의하기

AIDL 인터페이스는 .aidl 파일 안에 자바 언어의 문법으로 정의해야 하며, .aidl 파일은 서비스를 가지고 있는 앱과 그 서비스를 바인드 하려는 앱 모두의 소스코드 경로(src/ 디렉토리) 아래에 저장해야 합니다. 

.aidl 파일을 포함하고 있는 프로젝트를 빌드하게 되면, 안드로이드 SDK툴이 .aidl 파일에 기반한 IBinder 인터페이스를 생성하여 gen/ 디렉토리 아래에 저장합니다. 서비스는 IBinder 인터페이스를 적절히 구현해야 하며, 클라이언트는 그 IBinder 객체를 통해 서비스와 프로세스간 통신(IPC)을 할 수 있습니다. 

AIDL을 이용하여 바인드 되는 서비스를 만들기 위해서는, 아래 단계를 따르시기 바랍니다:

1. .aidl 파일 생성

.aidl 파일은 서비스에서 제공할 메소드에 대한 프로그래밍 인터페이스를 정의합니다.

2. 인터페이스 구현

안드로이드 SDK툴은 .aidl 파일에 기반하여 자바 인터페이스를 생성합니다. 이 인터페이스는 Binder클래스를 확장하는 Stub이라는 추상 클래스를 포함하고 있는데, 여기에는 .aidl 파일에 정의된 인터페이스들이 자바 인터페이스 메소드들로 선언되어 있습니다. 이 Stub 추상 클래스를 확장하여 인터페이스 메소드들을 구현해야 합니다.

3. 클라이언트에 인터페이스 전달

Service 클래스를 상속받아서 내 앱의 서비스를 만들고, onBind() 콜백 메소드를 오버라이드하여, Stub을 확장하여 구현한 클래스의 객체를 리턴해줍니다.

주의사항: 앱이 출시되어 서비스가 공개된 이후에 AIDL 인터페이스를 수정해야 하는 상황이 발생한다면, 이미 이전 버전의 AIDL 인터페이스를 이용하여 내 앱의 서비스를 사용하고 있는 다른 앱과의 충돌을 방지하기 위하여, 하위 호환(backward compatible)이 되어야 합니다. 다른 앱에서 AIDL 인터페이스를 이용하기 위해 내 앱의 .aidl 파일을 복사해서 사용했을 것이기 때문에, 여기에 정의되어 있는 인터페이스 메소드들은 그 형태가 유지되어야 합니다. 


1. .aidl 파일 생성

AIDL은 하나 또는 그 이상의 메소드를 포함하는 인터페이스를 선언하기 위해 간단한 문법을 사용하는데, 각 메소드는 매개변수와 리턴값을 가질 수 있습니다. 매개변수와 리턴값은 자바의 모든 타입이 가능하며, 심지어 다른 AIDL에 의해 생성된 인터페이스도 가능합니다. 

.aidl 파일은 자바 언어로 작성합니다. 하나의 .aidl 파일에는 하나의 인터페이스만 정의되어야 하며, 하나의 인터페이스 선언부와 메소드들의 선언부로 구성됩니다. 

기본적으로, AIDL은 아래의 데이터 타입들을 지원합니다:

  • 자바 언어의 원시 타입 데이터들 (int, long, char, boolean 등) 
  • String
  • CharSequence
  • List
    List의 모든 구성요소는 이 섹션에서 소개하는 데이터 타입이거나, 다른 AIDL에서 생성된 인터페이스이거나, Parcelable을 구현한 데이터 타입이어야 합니다. List는 (List<String>처럼) 제너릭("generic") 클래스가 될 수도 있습니다. 그리고 메소드를 정의할 때 List 인터페이스를 사용했다하더라도, 실제로 생성된 클래스에서는 항상 ArrayList로 생성됩니다. 
  • Map
    List와 마찬가지로 Map의 모든 구성요소도 이 섹션에서 소개하는 데이터 타입이거나, 다른 AIDL에서 생성된 인터페이스이거나, Parcelable을 구현한 데이터 타입이어야 합니다. 하지만 (Map<String, Integer>와 같은) 제너릭("generic") Map은 지원되지 않습니다. 그리고 메소드를 정의할 때 Map 인터페이스를 사용했다하더라도, 실제로 생성된 클래스에서는 항상 HashMap으로 생성됩니다.

위의 목록에 나열된 것들(원시 타입, String, CharSequence, List, Map) 이외의 데이터 타입들(다른 AIDL 인터페이스, Parcelable)을 사용하기 위해서는 import 구문을 작성해야 합니다. 이는 인터페이스와 같은 패키지에 속해 있는 것들이라도 마찬가지입니다. 

인터페이스를 정의할 때, 아래 내용들을 알아두시기 바랍니다:

  • 메소드들은 0개 이상의 매개변수를 가질 수 있고, 결과를 리턴하거나, 리턴 타입이 void일 수 있습니다.
  • 원시 타입이 아닌 모든 매개변수들 앞에는, 어느 방향으로의 매개변수인지를 가리키는 in 또는 out 또는 inout 이 있어야 합니다. 
    원시 타입은 기본적으로 in 이며, 다른 것은 불가능합니다.

    주의사항: 데이터를 마샬링하는 것은 시스템 입장에서 꽤 부담스러운 작업이기 때문에, 필요에 맞춰서 매개변수의 방향을 정확히 정해주는 것이 좋습니다.

  • .aidl 파일에 포함된 모든 주석들은 생성된 IBinder 인터페이스에도 포함됩니다(package 및 import 구문 위에 추가한 주석은 제외됩니다). 
  • 메소드만 지원됩니다. AIDL에서 정적 변수(static fields)는 전달할 수 없습니다. 

아래 코드는 .aidl 파일의 예제입니다:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
   
/** Request the process ID of this service, to do evil things with it. */
   
int getPid();

   
/** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

   
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
           
double aDouble, String aString);
}

.aidl 파일을 프로젝트의 src/ 디렉토리 아래의 적당한 경로에 저장하고 빌드를 하면, SDK툴이 IBinder 인터페이스 파일을 gen/ 디렉토리 아래의 대응되는 경로에 생성해 줍니다. 생성되는 IBinder 파일의 이름은 .aidl 파일의 이름과 같지만 확장자만 .java로 바뀝니다(예를 들어, IRemoteService.aidl에 의해서 생성된 파일은 IRemoteService.java입니다).

개발할 때 이클립스나 안드로이드 스튜디오를 사용한다면, 빌드할 때 .aidl이 있을 경우 알아서 IBinder 인터페이스를 생성해 줍니다. 하지만 이클립스나 안드로이드 스튜디오를 사용하지 않는다면, Ant툴을 사용하여 IBinder 인터페이스를 생성할 수 있습니다. 이 경우, .aidl 파일을 작성한 후에 ant debug(또는 ant release)를 하여 프로젝트를 빌드해야 IBinder 인터페이스가 생성되어 다른 코드들과 제대로 연결됩니다. 


2. 인터페이스 구현

.aidl 파일을 저장하고 나서 빌드를 하면, 안드로이드 SDK툴이 .aidl 파일과 (확장자 앞의) 이름이 같은 .java 인터페이스 파일을 생성합니다. 생성된 인터페이스 안에는 Stub 이라는 서브클래스가 포함되어 있는데, 이것은 부모인 인터페이스를 구현하는 추상 클래스입니다. 그리고 인터페이스 안에는 .aidl 파일 안에 있던 메소드들이 모두 선언되어 있습니다. 

메모: Stub에는 몇개의 헬퍼 메소드들도 정의되어 있는데, 그 중에서도 가장 눈에 띄는 것이 asInterface()입니다. 이것은 IBinder를 인자로 받고, Stub 인터페이스를 구현한 객체를 리턴해줍니다. asInterface()는 보통 클라이언트의 onServiceConnected()에서 매개변수로 받은 IBinder 객체를 인자로 넘깁니다. 자세한 내용은 아래의 IPC 메소드 호출하기에서 학습하실 수 있습니다. 

.aidl로부터 생성된 인터페이스를 구현하기 위해서는, YourInterface.Stub을 확장하고, .aidl에서 선언했던 메소드들을 구현해야 합니다. (YourInterface.StubBinder 클래스를 확장합니다.)

아래 예제 코드는, 위의 IRemoteService.aidl 파일로부터 생성된 IRemoteService 인터페이스를 구현하는 것을 보여줍니다:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
   
public int getPid(){
       
return Process.myPid();
   
}
   
public void basicTypes(int anInt, long aLong, boolean aBoolean,
       
float aFloat, double aDouble, String aString) {
       
// Does nothing
   
}
};

위의 코드에서 mBinder는 (Binder 클래스를 확장한) Stub 클래스의 객체이며, RPC(원격 프로시저 호출) 인터페이스를 정의하고 있습니다. 이 객체가 클라이언트에게 전달되며, 서비스와 상호작용할 수 있게 해주는 것입니다. 

AIDL 인터페이스를 구현할 때 알아둬야할 몇가지 규칙이 있습니다:

  • 바인더 객체의 메소드가 메인쓰레드에서 호출된다는 보장이 없기 때문에, 서비스를 멀티쓰레드 환경에서 안전하도록 만들어야 합니다(thread-safe).
  • 기본적으로, RPC 호출은 동기적으로(synchronous) 동작합니다. 만약 서비스가 어떤 요청을 처리하는데 시간이 꽤 걸린다는 것을 안다면, 액티비티의 메인쓰레드에서 그 요청에 대한 호출을 하면 안됩니다. 왜냐하면 그 시간 동안 앱이 멈추기 때문입니다(멈추고 나서 일정시간 이상 지나면 ANR(Application is Not Responding) 다이얼로그를 보게 됩니다). 따라서 이러한 경우에 클라이언트는 다른 쓰레드에서 호출하도록 해야 합니다.
  • 서비스에서 발생시킨 예외(exception)를 클라이언트로 보낼 수 없습니다.


3. 클라이언트에 인터페이스 전달

서비스에 바인더 인터페이스를 구현했다면, 그것을 클라이언트가 바인드하여 가져갈 수 있도록 해줘야 합니다. 그러기 위해서는, 서비스의 onBind() 콜백 메소드를 구현하여, Stub을 구현한 객체를 리턴해줘야 합니다. 아래 예제 코드는 IRemoteService에 대한 구현 객체를 클라이언트에게 리턴해주는 것을 보여줍니다.

public class RemoteService extends Service {
   
@Override
   
public void onCreate() {
       
super.onCreate();
   
}

   
@Override
   
public IBinder onBind(Intent intent) {
       
// Return the interface
       
return mBinder;
   
}

   
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
       
public int getPid(){
           
return Process.myPid();
       
}
       
public void basicTypes(int anInt, long aLong, boolean aBoolean,
           
float aFloat, double aDouble, String aString) {
           
// Does nothing
       
}
   
};
}

이제 (액티비티와 같은) 클라이언트가 서비스를 바인드하기 위해 bindService()를 호출하면, 서비스의 onBind()에서 리턴해주는 mBinder를, 클라이언트의 onServiceConnected() 콜백 메소드에서 받을 수 있습니다. 

클라이언트는 넘겨받은 mBinder를 사용해야 하는데, 만약 클라이언트와 서비스가 서로 다른 앱에 있다면, 클라이언트 앱의 src/ 디렉토리 아래에는 서비스 앱의 .aidl 파일을 복사하여 저장해야 합니다(그래야 AIDL 메소드들을 호출할 수 있는 android.os.Binder 인터페이스에 대한 구현체가 클라이언트에 생성됩니다).

클라이언트가 onServiceConnected()에서 IBinder를 전달 받았다면, 이제 YourServiceInterface.Stub.asInterface(service)를 호출하여 YourServiceInterface 타입의 인터페이스를 받아서 그것을 사용하면 됩니다. 아래는 예제 코드입니다:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
   
// Called when the connection with the service is established
   
public void onServiceConnected(ComponentName className, IBinder service) {
       
// Following the example above for an AIDL interface,
       
// this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService
= IRemoteService.Stub.asInterface(service);
   
}

   
// Called when the connection with the service disconnects unexpectedly
   
public void onServiceDisconnected(ComponentName className) {
       
Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService
= null;
   
}
};

샘플 코드는 ApiDemos 샘플앱의 RemoteService.java를 참고하시기 바랍니다.


IPC로 객체 전달하기

IPC(프로세스간 통신) 인터페이스를 통해 어떤 프로세스에서 다른 프로세스로 전송할 수 있는 클래스를 만들기 위해서는, 그 클래스가 Parcelable 인터페이스를 구현하고 있어야 합니다. 그것은 객체를 원시타입의 데이터로 분리하는 마샬링 과정을 포함하는데, 이는 안드로이드 시스템이 서로 다른 프로세스 간에 객체를 전달하는데에 있어서 중요한 과정입니다. 

Parcelable 프로토콜을 지원하는 클래스를 만드는 과정은 아래와 같습니다: 

1. 클래스가 Parcelable 인터페이스를 구현하도록 선언합니다(클래스 선언부에 implements Parcelable 을 추가합니다).

2. writeToParcel 메소드를 구현하여, 매개변수로 받은 Parcel 객체에 본 객체의 현재 상태들을 저장합니다(writes).

3. Parcelable.Creator 인터페이스의 구현객체를 CREATOR라는 이름의 정적 변수에 저장합니다.

4. 마지막으로, parcelable 클래스를 선언하는 .aidl 파일을 생성합니다(아래에 Rect.aidl 파일이 있습니다). 만약 자체적인 빌드 프로세스를 사용한다면, .aidl 파일을 빌드에 추가하지 않아도 됩니다. C 언어에서의 헤더 파일처럼, .aidl 파일도 컴파일 되지는 않기 때문입니다. 

AIDL은 위에서 설명한 메소드 및 변수를 이용하여 객체를 마샬링 및 언마샬링 합니다.

아래 코드는 Rect 클래스가 parcelable 하다고 선언해주는 Rect.aidl 파일의 코드입니다:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable
Rect;

그리고 아래 코드는 Rect 클래스가 Parcelable 프로토콜을 구현하는 방법을 보여줍니다:

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
   
public int left;
   
public int top;
   
public int right;
   
public int bottom;

   
public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
       
public Rect createFromParcel(Parcel in) {
           
return new Rect(in);
       
}

       
public Rect[] newArray(int size) {
           
return new Rect[size];
       
}
   
};

   
public Rect() {
   
}

   
private Rect(Parcel in) {
        readFromParcel
(in);
   
}

   
public void writeToParcel(Parcel out) {
       
out.writeInt(left);
       
out.writeInt(top);
       
out.writeInt(right);
       
out.writeInt(bottom);
   
}

   
public void readFromParcel(Parcel in) {
        left
= in.readInt();
        top
= in.readInt();
        right
= in.readInt();
        bottom
= in.readInt();
   
}
}

Rect 클래스를 마샬링하는 것은 꽤 간단합니다. Parcel 객체는 저장하려는 여러 종류의 데이터들에 대하여 타입별로 메소드를 제공합니다(writeInt(), writeLong(), writeString() 등).

경고: 다른 프로세스로부터 데이터를 받을 때는 보안적 문제가 생길 수도 있다는 점을 명심해야 합니다. 위의 예제에서, Rect는 Parcel로부터 4개의 정수를 읽어오는데, 여기서 읽어온 값들이 허용가능한 범위의 값들인지를 확인하는 것은 내 앱에서 해야할 작업입니다. 내 앱이 악성코드로부터 안전하도록 만들기 위한 자세한 내용은 보안과 퍼미션에서 학습하실 수 있습니다.


IPC 메소드 호출하기

AIDL로 정의된 인터페이스를 구현한 클래스를 클라이언트에서 호출하기까지의 과정은 아래와 같습니다:

1. 프로젝트의 src/ 디렉토리에 .aidl 파일을 추가합니다.

2. AIDL에 의해 생성된 IBinder 객체를 선언합니다.

3. ServiceConnection을 구현합니다.

4. Context.bindService() 메소드를 호출하는데, 이때 ServiceConnection의 구현객체를 인자로 넘깁니다.

5. ServiceConnectiononServiceConnected() 콜백 메소드에서 IBinder 객체를 (service라는 매개변수로) 전달 받는데, 여기서 YourInterface.Stub.asInterface((IBinder)service) 를 호출하여 YourInterface 타입의 결과값을 받아 멤버변수로 저장합니다.

6. 이제 인터페이스에 정의한 메소드들을 호출하면 됩니다. 이때 DeadObjectException에 대한 예외처리를 해줘야 하는데, 이것은 원격 메소드 호출시에만 발생하는 예외로서, 이 경우에는 예상치 못하게 연결이 끊어진 상태에서 메소드를 호출했을 때 발생합니다.

7. 연결을 종료하기 위해서는 Context.unbindService()를 호출하는데, 이때 ServiceConnection의 구현객체를 인자로 넘깁니다.  

IPC로 서비스를 호출함에 있어서 알아둬야할 사항 2가지를 추가합니다:

  • 객체들은 프로세스를 가로질러 레퍼런스 카운트(reference counted)됩니다.
  • 원격 메소드의 인자로 익명의 객체(anonymous objects, new Object { ... }와 같은 형태)를 넘길 수 있습니다.

서비스를 바인드하는 것과 관련된 더 자세한 내용은 서비스 바인드하기에서 학습하실 수 있습니다.

아래 예제 코드는 AIDL로 생성된 인터페이스를 호출하는 샘플 코드이며, ApiDemos 샘플앱의 RemoteService를 바인드하는 액티비티입니다.

public static class Binding extends Activity {
   
/** The primary interface we will be calling on the service. */
   
IRemoteService mService = null;
   
/** Another interface we use on the service. */
   
ISecondary mSecondaryService = null;

   
Button mKillButton;
   
TextView mCallbackText;

   
private boolean mIsBound;

   
/**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);

        setContentView
(R.layout.remote_service_binding);

       
// Watch for button clicks.
       
Button button = (Button)findViewById(R.id.bind);
        button
.setOnClickListener(mBindListener);
        button
= (Button)findViewById(R.id.unbind);
        button
.setOnClickListener(mUnbindListener);
        mKillButton
= (Button)findViewById(R.id.kill);
        mKillButton
.setOnClickListener(mKillListener);
        mKillButton
.setEnabled(false);

        mCallbackText
= (TextView)findViewById(R.id.callback);
        mCallbackText
.setText("Not attached.");
   
}

   
/**
     * Class for interacting with the main interface of the service.
     */

   
private ServiceConnection mConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// This is called when the connection with the service has been
           
// established, giving us the service object we can use to
           
// interact with the service.  We are communicating with our
           
// service through an IDL interface, so get a client-side
           
// representation of that from the raw service object.
            mService
= IRemoteService.Stub.asInterface(service);
            mKillButton
.setEnabled(true);
            mCallbackText
.setText("Attached.");

           
// We want to monitor the service for as long as we are
           
// connected to it.
           
try {
                mService
.registerCallback(mCallback);
           
} catch (RemoteException e) {
               
// In this case the service has crashed before we could even
               
// do anything with it; we can count on soon being
               
// disconnected (and then reconnected if it can be restarted)
               
// so there is no need to do anything here.
           
}

           
// As part of the sample, tell the user what happened.
           
Toast.makeText(Binding.this, R.string.remote_service_connected,
                   
Toast.LENGTH_SHORT).show();
       
}

       
public void onServiceDisconnected(ComponentName className) {
           
// This is called when the connection with the service has been
           
// unexpectedly disconnected -- that is, its process crashed.
            mService
= null;
            mKillButton
.setEnabled(false);
            mCallbackText
.setText("Disconnected.");

           
// As part of the sample, tell the user what happened.
           
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                   
Toast.LENGTH_SHORT).show();
       
}
   
};

   
/**
     * Class for interacting with the secondary interface of the service.
     */

   
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// Connecting to a secondary interface is the same as any
           
// other interface.
            mSecondaryService
= ISecondary.Stub.asInterface(service);
            mKillButton
.setEnabled(true);
       
}

       
public void onServiceDisconnected(ComponentName className) {
            mSecondaryService
= null;
            mKillButton
.setEnabled(false);
       
}
   
};

   
private OnClickListener mBindListener = new OnClickListener() {
       
public void onClick(View v) {
           
// Establish a couple connections with the service, binding
           
// by interface names.  This allows other applications to be
           
// installed that replace the remote service by implementing
           
// the same interface.
            bindService
(new Intent(IRemoteService.class.getName()),
                    mConnection
, Context.BIND_AUTO_CREATE);
            bindService
(new Intent(ISecondary.class.getName()),
                    mSecondaryConnection
, Context.BIND_AUTO_CREATE);
            mIsBound
= true;
            mCallbackText
.setText("Binding.");
       
}
   
};

   
private OnClickListener mUnbindListener = new OnClickListener() {
       
public void onClick(View v) {
           
if (mIsBound) {
               
// If we have received the service, and hence registered with
               
// it, then now is the time to unregister.
               
if (mService != null) {
                   
try {
                        mService
.unregisterCallback(mCallback);
                   
} catch (RemoteException e) {
                       
// There is nothing special we need to do if the service
                       
// has crashed.
                   
}
               
}

               
// Detach our existing connection.
                unbindService
(mConnection);
                unbindService
(mSecondaryConnection);
                mKillButton
.setEnabled(false);
                mIsBound
= false;
                mCallbackText
.setText("Unbinding.");
           
}
       
}
   
};

   
private OnClickListener mKillListener = new OnClickListener() {
       
public void onClick(View v) {
           
// To kill the process hosting our service, we need to know its
           
// PID.  Conveniently our service has a call that will return
           
// to us that information.
           
if (mSecondaryService != null) {
               
try {
                   
int pid = mSecondaryService.getPid();
                   
// Note that, though this API allows us to request to
                   
// kill any process based on its PID, the kernel will
                   
// still impose standard restrictions on which PIDs you
                   
// are actually able to kill.  Typically this means only
                   
// the process running your application and any additional
                   
// processes created by that app as shown here; packages
                   
// sharing a common UID will also be able to kill each
                   
// other's processes.
                   
Process.killProcess(pid);
                    mCallbackText
.setText("Killed service process.");
               
} catch (RemoteException ex) {
                   
// Recover gracefully from the process hosting the
                   
// server dying.
                   
// Just for purposes of the sample, put up a notification.
                   
Toast.makeText(Binding.this,
                            R
.string.remote_call_failed,
                           
Toast.LENGTH_SHORT).show();
               
}
           
}
       
}
   
};

   
// ----------------------------------------------------------------------
   
// Code showing how to deal with callbacks.
   
// ----------------------------------------------------------------------

   
/**
     * This implementation is used to receive callbacks from the remote
     * service.
     */

   
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
       
/**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */

       
public void valueChanged(int value) {
            mHandler
.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
       
}
   
};

   
private static final int BUMP_MSG = 1;

   
private Handler mHandler = new Handler() {
       
@Override public void handleMessage(Message msg) {
           
switch (msg.what) {
               
case BUMP_MSG:
                    mCallbackText
.setText("Received from service: " + msg.arg1);
                   
break;
               
default:
                   
super.handleMessage(msg);
           
}
       
}

   
};
}


Posted by 개발자 김태우
,