안드로이드 인터페이스 정의 언어(AIDL)

Android 인터페이스 정의 언어 (AIDL)는 IDL: 프로그래밍 인터페이스를 정의할 수 있게 해 클라이언트와 서비스는 프로세스 간 통신 (IPC)

Android에서는 한 프로세스가 일반적으로 다른 프로세스의 메모리를 사용합니다 말하자면, 객체를 원하는 원시 유형으로 분해하여 해당 경계를 넘어 객체를 이해하고 마샬링할 수 있습니다. 코드는 마샬링은 작성이 지루한 작업이므로 Android가 AIDL로 대신 처리해 줍니다.

참고: AIDL은 다른 애플리케이션이 IPC를 위해 서비스에 액세스하고, 멀티스레딩을 처리하려는 경우 있습니다. 여러 네트워크 간에 동시 IPC를 수행할 필요가 없으면 인터페이스를 만들려면 Binder IPC를 수행하고 싶지만 멀티스레딩을 처리할 필요는 없는 경우 Messenger를 사용하여 인터페이스를 구현합니다. 그럼에도 불구하고 바인드된 서비스를 AIDL을 구현하는 단계이며

AIDL 인터페이스 디자인을 시작하기 전에 AIDL 인터페이스에 대한 호출은 직접적인 함수 호출입니다. 호출이 이루어지는 스레드에 대해 가정하지 않습니다. 발생합니다 발생하는 결과는 호출이 로컬 프로세스 또는 원격 프로세스 중 하나에서 시작됩니다.

  • 로컬 프로세스에서 발생한 호출은 호출한 스레드에서 실행됩니다. 만약 이것이 기본 UI 스레드이며, 이 스레드는 AIDL 인터페이스에서 계속 실행됩니다. 다음에 해당하는 경우 다른 스레드는 서비스에서 코드를 실행하는 스레드입니다. 따라서 로컬 스레드가 서비스에 액세스하고 있다면 서비스에서 실행 중인 스레드를 완전히 제어할 수 있습니다. 하지만 이 경우 AIDL을 전혀 사용하지 마세요. 대신 인터페이스를 구현하여 Binder
  • 원격 프로세스에서 오는 호출은 플랫폼이 내부에 유지관리하는 스레드 풀에서 전달됩니다. 실행할 수 있습니다 다중 호출을 통해 알 수 없는 스레드에서 수신 호출에 대비 발생할 수 있습니다. 즉, AIDL 인터페이스의 구현은 스레드로부터 완전히 안전합니다. 동일한 원격 객체의 한 스레드에서 호출 수신자 측에서 순서대로 도착합니다.
  • oneway 키워드는 원격 호출의 동작을 수정합니다. 원격 호출을 사용하면 있습니다. 트랜잭션 데이터를 전송하고 즉시 반환됩니다. 결국 인터페이스 구현은 이를 일반 원격 호출로 Binder 스레드 풀에서 일반 호출로 수신합니다. oneway를 로컬 호출에 사용하는 경우 아무런 영향이 없으며 호출은 여전히 동기식입니다.

AIDL 인터페이스 정의

Java를 사용하여 .aidl 파일에 AIDL 인터페이스를 정의합니다. 그런 다음 소스 코드의 src/ 디렉터리에 저장합니다. 서비스를 호스팅하는 애플리케이션 및 서비스에 바인딩되는 기타 애플리케이션

.aidl 파일이 포함된 각 애플리케이션을 빌드할 때 Android SDK 도구는 .aidl 파일을 기반으로 IBinder 인터페이스를 생성하고 프로젝트의 gen/ 디렉터리입니다. 서비스는 IBinder를 구현해야 합니다. 인터페이스를 적절하게 조정합니다. 그러면 클라이언트 애플리케이션이 서비스에 바인딩하여 IBinder가 IPC를 실행합니다.

AIDL을 사용하여 제한된 서비스를 만들려면 다음 단계를 따르세요. 다음 섹션에서 확인할 수 있습니다.

  1. .aidl 파일 만들기

    이 파일은 메서드 서명으로 프로그래밍 인터페이스를 정의합니다.

  2. 인터페이스 구현

    Android SDK 도구는 개발자가 작성한 앱을 기반으로 Java 프로그래밍 언어로 .aidl 파일. 이 인터페이스에는 Stub라는 내부 추상 클래스가 있으며 이 클래스를 확장합니다. Binder하고 AIDL 인터페이스에서 메서드를 구현합니다. 이 Stub 클래스를 열고 메서드를 구현합니다.

  3. 클라이언트에게 인터페이스 노출

    Service를 구현하고 onBind()를 재정의하여 Stub 구현을 반환합니다. 클래스에 대해 자세히 알아보세요.

주의: 이후에 AIDL 인터페이스를 변경하면 다른 애플리케이션이 중단되지 않도록 첫 번째 버전은 이전 버전과의 호환성을 유지해야 합니다. Google Cloud 리소스를 사용할 수 있습니다 즉, .aidl 파일을 다른 애플리케이션으로 복사해야 하기 때문입니다. 서비스 인터페이스에 액세스할 수 있도록 하려면 원래 API에 대한 지원을 인터페이스에 추가되었습니다.

.aidl 파일 생성

AIDL은 간단한 문법을 사용하므로 개발자가 매개변수를 사용하고 값을 반환합니다. 매개변수와 반환 값은 다른 유형을 포함하여 모든 유형이 될 수 있습니다. AIDL에서 생성한 인터페이스입니다.

.aidl 파일은 Java 프로그래밍 언어를 사용하여 구성해야 합니다. .aidl마다 파일은 단일 인터페이스를 정의해야 하며 인터페이스 선언과 메서드만 있으면 됩니다. 서명이 필요합니다

기본적으로 AIDL은 다음 데이터 유형을 지원합니다.

  • Java 프로그래밍 언어의 모든 프리미티브 유형 (예: int, long, char, boolean 등)
  • 원시 유형의 배열(예: int[])
  • String
  • CharSequence
  • List

    List의 모든 요소는 이 목록, 다른 AIDL로 생성된 인터페이스 또는 선언한 parcelable 중 하나를 선택해야 합니다. 가 List는 매개변수화된 유형 클래스로 사용할 수도 있습니다. 예를 들면 다음과 같습니다. List<String>입니다. 다른 쪽에서 수신하는 실제 구체적인 클래스는 항상 ArrayList이지만 메서드가 생성되어 List 인터페이스를 사용합니다.

  • Map

    Map의 모든 요소는 이 목록, 다른 AIDL로 생성된 인터페이스 또는 선언한 parcelable 중 하나를 선택해야 합니다. 매개변수화된 유형 맵 인코더-디코더 아키텍처를 Map<String,Integer>은 지원되지 않습니다. 다른 쪽이 실제 콘크리트 클래스 수신은 항상 HashMap입니다. 하지만 메서드는 Map 인터페이스를 사용하기 위해 생성됩니다. 다음과 같은 방법을 사용해 보세요. Map의 대안인 Bundle

이전에 나열되지 않은 추가 유형마다 import 문을 포함해야 합니다. 인터페이스와 동일한 패키지에 정의되어 있더라도

서비스 인터페이스를 정의할 때는 다음 사항에 유의하세요.

  • 메서드는 0개 이상의 매개변수를 취할 수 있으며 값 또는 void를 반환할 수 있습니다.
  • 모든 비원시 매개변수에는 데이터가 이동하는 방향을 나타내는 방향 태그가 필요합니다. in, out 또는 inout (아래 예시 참고)

    프리미티브, String, IBinder, AIDL에서 생성 인터페이스는 기본적으로 in이며 다른 경우에는 안 됩니다.

    주의: 실제 거리로만 구성되도록 매개변수를 마샬링하는 데 비용이 많이 들기 때문입니다.

  • .aidl 파일에 포함된 모든 코드 주석은 IBinder의 수익이 발생했습니다. 가져오기 및 패키지 앞의 주석을 제외한 인터페이스 합니다.
  • 문자열 및 int 상수는 const int VERSION = 1;와 같은 AIDL 인터페이스에서 정의할 수 있습니다.
  • 메서드 호출은 transact() 코드로 작성하며, 일반적으로 인터페이스의 메서드 색인에 기반합니다. 이 버전 관리가 어렵게 되면 void method() = 10; 메서드에 거래 코드를 수동으로 할당할 수 있습니다.
  • null을 허용하는 인수와 반환 유형은 @nullable를 사용하여 주석을 달아야 합니다.

다음은 .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. */
    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);
}

프로젝트의 src/ 디렉터리에 .aidl 파일을 저장합니다. 애플리케이션을 빌드하면 SDK 도구가 IBinder 인터페이스 파일을 프로젝트의 gen/ 디렉터리에 저장합니다. 생성된 파일의 이름이 .aidl 파일의 이름과 일치하지만 (.java 확장자로 전환) 예를 들어 IRemoteService.aidlIRemoteService.java이 됩니다.

Android Studio를 사용하는 경우 증분 빌드는 거의 즉시 바인더 클래스를 생성합니다. Android 스튜디오를 사용하지 않는 경우, 다음에 개발자가 작업을 할 때 Gradle 도구가 바인더 클래스를 생성합니다. 애플리케이션을 빌드할 수 있습니다 gradle assembleDebug로 프로젝트 빌드 또는 .aidl 파일 쓰기를 마치자마자 gradle assembleRelease로 이동합니다. 코드가 생성된 클래스에 연결할 수 있도록 합니다.

인터페이스 구현

애플리케이션을 빌드할 때 Android SDK 도구는 .java 인터페이스 파일을 생성합니다. .aidl 파일의 이름을 따서 지정되었는지 확인합니다. 생성된 인터페이스에는 Stub라는 서브클래스가 포함되어 있습니다. 는 상위 인터페이스의 추상 구현(예: YourInterface.Stub)이며 .aidl 파일의 모든 메서드를 선언합니다.

참고: Stub도 몇 가지 도우미 메서드, 특히 asInterface()를 정의합니다. 이 메서드는 일반적으로 클라이언트의 onServiceConnected() 콜백 메서드에 전달되는 IBinder를 받습니다. 스텁 인터페이스의 인스턴스를 반환합니다. 이러한 전송 방법을 자세히 알아보려면 IPC 호출 메서드).

.aidl에서 생성된 인터페이스를 구현하려면 생성된 Binder를 확장합니다. 인터페이스(예: YourInterface.Stub)를 구현하고 메서드를 .aidl 파일에서 상속됩니다.

다음은 IRemoteService라는 인터페이스 구현의 예입니다. 이 인터페이스는 익명 인스턴스를 사용하는 IRemoteService.aidl 예:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

자바

private final IRemoteService.Stub binder = 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.
    }
};

이제 binderStub 클래스 (Binder)의 인스턴스입니다. - 서비스의 IPC 인터페이스를 정의합니다. 다음 단계에서 이 인스턴스는 서비스와 상호작용할 수 있습니다

AIDL 인터페이스를 구현할 때 몇 가지 규칙에 유의하세요.

  • 수신 호출이 기본 스레드에서 실행된다는 보장은 없으므로 서비스를 스레드로부터 안전하게 빌드할 수 있도록 처음부터 멀티스레딩에 관해 알아보세요.
  • 기본적으로 IPC 호출은 동기식입니다. 서비스에 몇 시간 이상 소요되는 것을 알고 있는 경우 밀리초 이내에 있어야 합니다. 활동의 기본 스레드에서 이를 호출하지 마세요. 애플리케이션이 중단되어 Android에 '애플리케이션이 응답하지 않습니다'라는 메시지가 표시될 수 있습니다. 대화상자 클라이언트의 별도 스레드에서 호출하세요.
  • 에 대한 참조 문서에 나열된 예외 유형만 Parcel.writeException() 드림 호출자에게 다시 전송됩니다.

클라이언트에게 인터페이스 노출

서비스의 인터페이스를 구현한 후에는 클라이언트가 그것에 바인드할 수 있습니다 인터페이스 노출 Service를 확장하고 onBind()를 구현하여 서비스를 구현하는 클래스의 인스턴스를 반환합니다. 이전 섹션에서 설명한 대로 생성된 Stub를 반환합니다. 예를 들면 다음과 같습니다. 클라이언트에 IRemoteService 예시 인터페이스를 노출하는 서비스입니다.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

자바

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

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

    private final IRemoteService.Stub binder = 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()를 호출하여 이 서비스에 연결하면 클라이언트의 onServiceConnected() 콜백이 서비스의 onBind()에서 반환한 binder 인스턴스 메서드를 사용하여 축소하도록 요청합니다.

클라이언트는 인터페이스 클래스에도 액세스할 수 있어야 합니다. 클라이언트와 서비스가 별도의 애플리케이션이 있는 경우 클라이언트의 애플리케이션에 .aidl 파일의 사본이 있어야 합니다. src/ 디렉터리에 저장하며 이는 android.os.Binder 인터페이스를 통해 클라이언트에게 AIDL 메서드에 대한 액세스 권한을 제공합니다.

클라이언트가 IBinder를 수신하는 경우 onServiceConnected() 콜백에서는 YourServiceInterface.Stub.asInterface(service): 반환된 매개변수를 YourServiceInterface 유형에 추가합니다.

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

자바

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = 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");
        iRemoteService = null;
    }
};

추가 샘플 코드는 <ph type="x-smartling-placeholder"></ph> RemoteService.java 클래스를 사용하는 <ph type="x-smartling-placeholder"></ph> ApiDemos).

IPC를 통해 객체 전달

Android 10 (API 수준 29 이상)에서는 다음을 정의할 수 있습니다. Parcelable 객체를 AIDL AIDL 인터페이스 인수 및 기타 parcelable로 지원되는 유형도 여기에서 지원됩니다 이렇게 하면 마샬링 코드와 커스텀 API를 수동으로 작성하는 추가 작업을 할 필요가 없습니다. 클래스에 대해 자세히 알아보세요. 그러나 이 경우에도 기본 구조체가 생성됩니다. 커스텀 접근자 또는 기타 기능이 원하는 경우 대신 Parcelable를 구현합니다.

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

위의 코드 샘플은 정수 필드 left가 있는 Java 클래스를 자동으로 생성합니다. top, right, bottom 모든 관련 마샬링 코드는 자동으로 구현되며 객체를 추가하지 않고도 객체를 직접 사용할 수 있습니다. 있습니다.

IPC 인터페이스를 통해 한 프로세스에서 다른 프로세스로 사용자 지정 클래스를 보낼 수도 있습니다. 하지만 수업 코드를 IPC 채널의 다른 쪽에서 사용할 수 있는지 확인하고 클래스가 Parcelable 인터페이스를 지원해야 합니다. 지원 Parcelable이(가) 중요함 Android 시스템이 객체를 마샬링할 수 있는 프리미티브로 분해할 수 있으므로 프로세스 전반에 걸쳐 수행할 수 있습니다

Parcelable를 지원하는 맞춤 클래스를 만들려면 다음을 실행하세요. 있습니다.

  1. 클래스에서 Parcelable 인터페이스를 구현하도록 합니다.
  2. writeToParcel를 구현합니다. 이 메서드는 객체의 현재 상태를 확인하고 Parcel에 씁니다.
  3. CREATOR라는 정적 필드를 구현하는 객체인 클래스에 추가합니다. Parcelable.Creator 인터페이스
  4. 마지막으로 다음과 같이 parcelable 클래스를 선언하는 .aidl 파일을 만듭니다. Rect.aidl 파일.

    커스텀 빌드 프로세스를 사용하는 경우 .aidl 파일을 있습니다. C 언어의 헤더 파일과 마찬가지로 이 .aidl 파일은 컴파일되지 않습니다.

AIDL은 코드에서 이러한 메서드와 필드를 사용하여 마샬링 및 마샬링을 취소합니다. 객체입니다.

예를 들어 다음과 같은 Rect 클래스를 만드는 Rect.aidl 파일은 다음과 같습니다. parcelable을 적용할 수 있습니다.

package android.graphics;

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

다음은 Rect 클래스가 Parcelable 프로토콜

Kotlin

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

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

자바

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, int flags) {
        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();
    }

    public int describeContents() {
        return 0;
    }
}

Rect 클래스의 마샬링은 간단합니다. 다른 이미지 살펴보기 작성할 수 있는 다른 종류의 값을 확인하는 Parcel의 메서드 Parcel로 변경합니다.

경고: 수신 시 보안에 미치는 영향을 기억하세요. 다른 프로세스의 데이터입니다. 이 경우 RectParcel에서 숫자 4개를 읽지만 이 숫자가 허용 가능한 범위 내에 있는지 확인하는 것은 개발자의 몫입니다. 값을 제공해야 합니다. 멀웨어로부터 애플리케이션을 안전하게 보호하는 방법에 대한 자세한 내용은 보안 도움말을 참고하세요.

Parcelable이 포함된 번들 인수를 사용한 메서드

메서드가 다음을 포함할 것으로 예상되는 Bundle 객체를 허용하는 경우 parcelable을 사용하는 경우 Bundle의 클래스 로더를 다음과 같이 설정해야 합니다. 읽기 전에 Bundle.setClassLoader(ClassLoader) 호출 Bundle에서 가져옴 그러지 않으면 parcelable이 애플리케이션에 올바르게 정의되어 있어도 ClassNotFoundException이 발생합니다.

예를 들어 다음 샘플 .aidl 파일을 살펴보세요.

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

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
드림 다음 구현에서 볼 수 있듯이 ClassLoader는 다음과 같습니다. Rect를 읽기 전에 Bundle에서 명시적으로 설정합니다.

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

자바

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

IPC 메서드 호출

AIDL로 정의된 원격 인터페이스를 호출하려면 다음 단계를 따르세요. 호출 클래스:

  1. 프로젝트 src/ 디렉터리에 .aidl 파일을 포함합니다.
  2. 다음을 기반으로 생성된 IBinder 인터페이스의 인스턴스를 선언합니다. AIDL
  3. ServiceConnection를 구현합니다.
  4. Context.bindService()번으로 전화해 주세요. ServiceConnection 구현을 전달합니다.
  5. onServiceConnected() 구현에서 IBinder 지급 service라는 인스턴스를 보여줍니다 전화걸기 YourInterfaceName.Stub.asInterface((IBinder)service)부터 반환된 매개변수를 YourInterface 유형으로 변환합니다.
  6. 인터페이스에서 정의한 메서드를 호출합니다. 항상 함정 DeadObjectException 예외는 다음과 같은 경우에 발생합니다. 연결이 끊어집니다. 또한 SecurityException 예외를 트랩합니다. 이 예외는 IPC 메서드 호출과 관련된 두 프로세스에 충돌하는 AIDL 정의가 있을 때 발생합니다.
  7. 연결을 해제하려면 인터페이스의 인스턴스를 사용하여 Context.unbindService()를 호출합니다.

IPC 서비스를 호출할 때는 다음 사항에 유의하세요.

  • 객체는 여러 프로세스에 걸쳐 카운트되는 참조입니다.
  • 익명의 객체를 전송하여 메서드 인수로 사용합니다.

서비스에 바인딩하는 방법에 관한 자세한 내용은 바인드된 서비스 개요를 참고하세요.

다음은 AIDL로 생성된 서비스 호출을 보여주는 몇 가지 샘플 코드입니다. ApiDemos 프로젝트의 Remote Service 샘플에서 가져옵니다.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // 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)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // 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(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

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

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            secondaryService = null
            killButton.isEnabled = false
        }
    }

    private val mBindListener = View.OnClickListener {
        // Establish a couple connections with the service, binding
        // by interface names. This lets other applications be
        // installed that replace the remote service by implementing
        // the same interface.
        val intent = Intent(this@Binding, RemoteService::class.java)
        intent.action = IRemoteService::class.java.name
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        intent.action = ISecondary::class.java.name
        bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE)
        isBound = true
        callbackText.text = "Binding."
    }

    private val unbindListener = View.OnClickListener {
        if (isBound) {
            // If we have received the service, and hence registered with
            // it, then now is the time to unregister.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // There is nothing special we need to do if the service
                // crashes.
            }

            // Detach our existing connection.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // To kill the process hosting the service, we need to know its
        // PID.  Conveniently, the service has a call that returns
        // that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API lets us request to
                // kill any process based on its PID, the kernel
                // still imposes standard restrictions on which PIDs you
                // can actually 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 are also able to kill each
                // other's processes.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // For purposes of this sample, put up a notification.
            Toast.makeText(this@Binding, 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 val mCallback = object : 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 is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button taps.
        var button: Button = findViewById(R.id.bind)
        button.setOnClickListener(mBindListener)
        button = findViewById(R.id.unbind)
        button.setOnClickListener(unbindListener)
        killButton = findViewById(R.id.kill)
        killButton.setOnClickListener(killListener)
        killButton.isEnabled = false

        callbackText = findViewById(R.id.callback)
        callbackText.text = "Not attached."
        handler = InternalHandler(callbackText)
    }

    private class InternalHandler(
            textView: TextView,
            private val weakTextView: WeakReference<TextView> = WeakReference(textView)
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}"
                else -> super.handleMessage(msg)
            }
        }
    }
}

Java

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

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity. Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * 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 is
            // 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);
            killButton.setEnabled(true);
            callbackText.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 crashes before we can 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 is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null;
            killButton.setEnabled(false);
            callbackText.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 secondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service);
            killButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            secondaryService = null;
            killButton.setEnabled(false);
        }
    };

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

    private OnClickListener unbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (isBound) {
                // 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
                        // crashes.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(secondaryConnection);
                killButton.setEnabled(false);
                isBound = false;
                callbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener killListener = 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 returns
            // that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API lets us request to
                    // kill any process based on its PID, the kernel
                    // still imposes standard restrictions on which PIDs you
                    // can actually 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 are also able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    callbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // For purposes of this 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 is
         * NOT 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) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private static class InternalHandler extends Handler {
        private final WeakReference<TextView> weakTextView;

        InternalHandler(TextView textView) {
            weakTextView = new WeakReference<>(textView);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    TextView textView = weakTextView.get();
                    if (textView != null) {
                        textView.setText("Received from service: " + msg.arg1);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}