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

kosdjs·2025년 6월 26일
0

Android

목록 보기
11/24
  • 프로세스 간 통신(IPC)을 사용하여 통신하기 위해 클라이언트와 서비스 모두 동의하는 프로그래밍 인터페이스를 정의하는 것

  • 안드로이드에서 일반적으로는 프로세스에서 다른 프로세스의 메모리에 접근할 수 없음, 통신하려면 객체를 운영체제가 이해할 수 있게 기본형으로 분해하고 객체를 마샬링해 전해줌, 이런 마샬링하는 코드를 직접 작성하기 지루하기 때문에 안드로이드는 AIDL을 사용해 이를 대신 처리함

노트: AIDLIPC를 위해 다른 앱의 클라이언트들이 서비스에 액세스할 수 있도록 허용하고 동시에 멀티스레딩을 처리하고 싶을때만 사용하십시오. 만약 다른 앱과 동시에 IPC를 수행할 필요가 없다면 Binder를 구현해 인터페이스를 생성하십시오. 만약 IPC를 수행하고 싶지만 멀티스레딩을 처리하고 싶지 않다면, Messenger를 사용해 인터페이스를 구현하십시오. 그럼에도 AIDL을 구현해야 한다면 하기 전에 연결된 서비스를 확실하게 이해하고 하십시오.

  • AIDL 인터페이스를 설계하기 전에 AIDL 인터페이스에 대한 호출은 직접적인 함수 호출한다는 것을 알아야 함, 호출이 일어나는 곳이 로컬 프로세스의 스레드인지 원격 프로세스인지에 따라 일어나는 일이 다름

    • 로컬 프로세스에서 일어나는 호출은 호출을 하는 동일한 스레드에서 실행됨, 그 스레드가 만약 메인 UI 스레드라면, 그대로 AIDL 인터페이스에서 실행됨, 만약 다른 스레드라면 서비스에서 해당 스레드로 코드가 실행됨, 만약 로컬 스레드만 서비스에 액세스한다면, 실행될 스레드를 완전히 제어할 수 있지만 이런 경우라면 AIDL을 사용하지 말고 Binder를 구현해야 함

    • 원격 프로세스에서 일어나는 호출은 플랫폼이 프로세스 내에 유지되는 스레드 풀에서 전달됨, 모르는 스레드에서 호출이 올 수 있고, 많은 호출들이 동시에 올 수도 있으므로 대비해야 함, 그러므로 AIDL을 구현하는 인터페이스는 스레드로부터 안전하게 설계되어야 함, 한 스레드에서 동일한 원격 객체에 대해 여러번 호출하면 순서대로 수신됨

    • oneway 키워드는 원격 호출의 방식을 수정함, 이 키워드가 사용되면 원격 호출이 차단되지 않고 정보를 전달하면 즉시 반환됨, 이 인터페이스의 구현은 Binder 스레드의 일반 원격 호출에서 결국 일반 호출로 전달받음, 이 키워드가 로컬 호출에서 사용되면 아무 효과가 없고 호출이 동기적으로 작동함

AIDL 인터페이스 정의

  • AIDL 인터페이스를 정의하려면 .aidl 파일에 자바 언어로 작성해 저장하고 서비스를 호스팅하는 앱과 연결을 하려는 앱 둘다 src/ 경로에 저장해야 함
  • .aidl 파일이 내장된 앱을 빌드할 때 안드로이드 SDK 툴은 이 파일에 기반한 IBinder 인터페이스를 프로젝트의 gen/ 폴더에 저장함, 서비스는 반드시 IBinder 인터페이스를 적절하게 구현해야 함, 그러면 클라이언트 앱은 IPC를 수행하기 위해 서비스에 연결하고 메소드를 호출할 수 있음

AIDL을 사용하는 연결된 서비스를 생성하는 단계

  1. .aidl 파일 생성
  • 이 파일은 메소드 시그니처와 함께 프로그래밍 인터페이스를 정의함
  1. 인터페이스 구현
  • 안드로이드 SDK 툴은 .aidl 파일을 기반으로 자바 인터페이스를 생성함, 이 인터페이스는 Binder를 확장하고 AIDL 인터페이스의 메소드를 구현한 Stub이라고 명명된 내부 추상 클래스를 가지고 있음, 이 클래스를 상속받아 메소드를 구현해야 함
  1. 클라이언트에게 인터페이스 노출
  • 서비스를 구현하고 onBind()를 오버라이드해 Stub 클래스의 구현체를 반환함

주의: 서비스를 이용하는 다른 앱을 손상하지 않도록 AIDL 인터페이스를 한번 출시했다면 변경을 할 때 반드시 이전 버전과 호환되게 만들어야 합니다. 왜냐하면 .aidl 파일이 다른 앱이 서비스의 인터페이스에 액세스하기 위해 복제되었기 때문에 반드시 원래 인터페이스에 대한 지원을 유지해야 합니다.

.aidl 파일 생성

  • AIDL은 매개 변수와 반환 값을 가지는 하나 이상의 메소드를 가지는 인터페이스를 간단한 문법으로 정의할 수 있게 함, 매개 변수와 반환값은 어떤 타입이든 가능함, 다른 AIDL 기반도 가능함

  • .aidl 파일은 반드시 자바 프로그래밍 언어로 구성되어야 함, 이 파일은 반드시 하나의 인터페이스만 정의해야 하며 인터페이스 선언과 메소드 시그니처만 필요함

AIDL이 지원하는 데이터 타입

  • 모든 자바 프로그래밍 언어의 기본형, 단 short는 예외

  • 모든 타입의 배열

  • String

  • CharSequence

  • List : 모든 원소는 이 리스트에 있는 데이터 타입이거나 다른 AIDL에 의해 생성된 인터페이스나 당신이 선언한 parcelable이어야 함, List<String>과 같이 파라미터화된 타입도 사용 가능함, 메소드는 List 인터페이스를 사용하도록 생성되지만 반대편에서 실제로 받는 구체적 클래스는 ArrayList

  • Map : 모든 원소든 이 리스트에 있는 데이터 타입이거나 다른 AIDL에 의해 생성된 인터페이스나 당신이 선언한 parcelable이어야 함, Map<String, Integer>와 같이 파라미터화된 타입은 지원하지 않음, 메소드는 Map 인터페이스를 사용하도록 생성되지만 반대편에서 실제로 받는 구체적 클래스는 HashMap임, Map의 대체로 Bundle을 사용하는 것을 고려해볼 것

  • 위의 리스트에 존재하지 않는 추가 타입들은 인터페이스와 같은 패키지에 있더라도 반드시 import 구문을 포함해야 함

  • 서비스의 인터페이스를 정의해야할 때 알아야 할 것

    • 메소드는 매개 변수가 0개 이상임, 값 또는 void를 반환할 수 있음

    • 기본형이 아닌 타입들은 in, out, inout과 같은 방향 태그를 필요로 함

      • 기본형, String, IBinder, AIDL 기반 인터페이스는 기본적으로 in이며 변경할 수 없음

        주의: 매개변수를 마샬링하는 것은 비용이 많이 들기 때문에 실제로 필요한 방향으로만 제한하십시오.

    • .aidl 파일에 import와 패키지 구문 이전의 주석을 제외한 모든 주석들은 생성된 IBinder 인터페이스에 포함됨

    • const int VERSION = 1;과 같이 Stringint 상수를 정의할 수 있음

    • 메소드 호출은 인터페이스의 메소드 인덱스에 기반해 transact() 코드에 의해 전달됨, 이게 버전 관리를 어렵게 하기 때문에 void method() = 10과 같이 메소드에 트랜잭션 코드를 직접 할당할 수 있음

    • 매개 변수 및 반환 값이 NULL을 허용한다면 반드시 @nullable 주석을 달아야 함

.aidl 파일 예제

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

// 여기에 기본 타입이 아닌 타입을 import 문으로 선언할 수 있습니다.

/** 서비스 인터페이스 예제 */
interface IRemoteService {
    /** 서비스의 프로세스 ID를 요청합니다. */
    int getPid();

    // AIDL에서 매개 변수 및 반환 값으로 사용할 수 있는 기본 타입 예시.
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
  • .aidl파일을 프로젝트의 src/ 경로에 저장해야 함, 그러면 SDK 툴이 IBinder 인터페이스를 프로젝트의 gen/ 경로에 생성해줌, 생성된 파일은 .aidl파일의 이름과 같고 확장자가 .java로 저장됨, 예를 들면 IRemoteService.aidl 파일은 IRemoteService.java 파일로 생성됨
  • 안드로이드 스튜디오를 사용한다면 증분 빌드가 바인더 클래스를 즉시 생성함, 안드로이드 스튜디오를 사용하지 않는다면 Gradle 툴이 다음에 앱을 빌드할 때 바인더 클래스를 생성함, 코드와 생성된 클래스가 연결되기 위해 .aidl 파일을 작성한 직후에 gradle assembleDebug 또는 gradle assembleRelease로 프로젝트를 빌드해야 함

인터페이스 구현

  • 앱을 빌드할 때 안드로이드 SDK 툴은 .aidl 인터페이스 파일과 같은 이름으로 .java 파일을 생헝함, 생성된 인터페이스는 Stub이라고 불리는 서브 클래스를 포함하고 있음, 이는 부모 인터페이스의 구현체임

노트: Stub은 몇가지 도우미 메소드도 정의하는데 대표적으로 asInterface()가 있습니다. 이 메소드는 보통 클라이언트의 onServiceConnected() 메소드에 전달되는 IBinder를 받아서 stub 인터페이스의 인스턴스를 반환합니다. 자세한 내용은 IPC 메소드 호출 부분에서 다룹니다.

  • .aidl 파일의 인터페이스를 구현하려면 생성된 Binder 인터페이스(예: YourInterface.Stub)를 확장하고 메소드를 구현하면 됨

IRemoteService.aidl에서 정의된 인터페이스를 구현하는 예제

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
    ) {
        // 아무것도 하지 않음.
    }
}

AIDL 인터페이스를 구현하기 전에 알아야 할 몇가지

  • 들어오는 호출이 메인 스레드에서 실행되는 것이 보장되지 않기 때문에, 처음부터 멀티스레딩을 생각해야 하고, 스레드로부터 안전하게 설계되어야 함
  • 기본적으로 IPC 호출은 동기식임. 서비스가 요청을 완료하는데 시간이 좀 걸린다면 메인스레드에서 호출하지 않아야 함, 그렇지 않으면 안드로이드가 ANR 대화상자를 띄울 수 있음, 이런 상황에서는 클라이언트의 별도의 스레드에서 호출해야 함
  • Parcel.writeException() 문서에 나열된 예외만 호출자에게 전달됨

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

  • 서비스의 인터페이스를 구현하면 클라이언트가 연결할 수 있게 노출해야 함, Service를 확장해 onBind()를 구현하고 생성된 Stub의 구현체를 반환해 서비스의 인터페이스를 노출할 수 있음

IRemoteService에서 인터페이스를 노출하는 예제

class RemoteService : Service() {

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

    override fun onBind(intent: Intent): IBinder {
        // 인터페이스를 반환합니다.
        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
        ) {
            // 아무것도 하지 않음.
        }
    }
}
  • 액티비티와 같은 클라이언트에서 bindService()를 호출해 서비스와 연결하면 클라이언트의 onServiceConnected() 콜백에서 서비스의 onBind() 메소드에서 반환되는 binder 인스턴스를 받음
  • 클라이언트도 인터페이스 클래스를 액세스할 수 있어야 함, 만약 서비스와 클라이언트가 다른 앱에 있다면 클라이언트의 앱에도 .aidl 파일이 있어야 함, 그래야 android.os.Binder 인터페이스를 생성해 클라이언트가 AIDL 메소드에 액세스할 수 있음
  • 클라이언트가 onServiceConnected() 콜백에서 IBinder를 받으면 인터페이스의 asInterface()를 통해 형변환을 해야 함

클라이언트가 IRemoteService와 연결했을 때 IBinder를 형변환하는 예제

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // 서비스와 연결이 성립되었을 때 호출됩니다.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // 앞서 설명한 AIDL 인터페이스 예시를 따라,
        // 서비스에 호출할 때 사용할 수 있도록 IRemoteInterface 인스턴스를 얻습니다.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // 서비스와 연결이 예기치 못하게 해제되었을 때 호출됩니다.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

IPC를 통해 객체 전달

  • 안드로이드 10(API 레벨 29) 이상에서 Parcelable 객체를 AIDL에서 직접 정의할 수 있음, AIDL 인터페이스 인자에 지원되는 타입과 다른 parcelable들도 여기서 지원됨, 이는 사용자 지정 클래스의 마샬링 코드를 작성하는 추가 작업을 할 필요가 없게 함, 하지만 이렇게 하면 빈 구조체가 생성됨, 사용자 지정 접근자나 다른 기능이 필요하다면 Parcelable을 직접 구현해야 함
package android.graphics;

// Rect를 선언하여 AIDL이 이를 찾고, parcelable 프로토콜을 구현함을 알립니다
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}
  • 위의 예제 코드는 정수 필드 left, top, right, bottom을 가지는 자바 클래스를 자동으로 생성해줌, 추가 구현이 필요 없이 마샬링 코드가 자동으로 구현됨으로 객체를 직접 사용할 수 있음
  • IPC 인터페이스를 통해 다른 프로세스에 사용자 지정 클래스를 전송할 수 있음, 하지만 해당 클래스의 코드가 IPC 채널의 반대편에 있어야 하고 안드로이드 시스템이 객체를 기본형으로 분해하고 마샬링해 프로세스간 전송이 가능하도록 Parcelable 인터페이스를 지원해야 함

Parcelable을 지원하는 클래스를 만드는 방법

  1. 클래스가 Parcelable 인터페이스를 구현
  2. 객체의 현재 상태를 Parcel에 쓰기 위한 writeToParcel 구현
  3. CREATOR라는 정적 필드를 추가함, 값은 Parcelable.Creator을 구현하는 객체
  4. 마지막으로 parcelable 클래스를 선언한 .aidl파일 생성
  • 만약 사용자 지정 빌드 프로세스를 사용한다면 .aidl 파일을 추가하지 않아야 함, C 언어의 헤더 파일처럼 .aidl 파일은 컴파일되지 않음
  • AIDL은 코드에서 생성되는 메소드와 필드를 사용해 객체를 마샬링 및 언마샬링함

parcelableRect 클래스를 생성하는 Rect.aidl 파일의 예제

package android.graphics;

// Rect를 선언하여 AIDL이 이를 찾고, parcelable 프로토콜을 구현함을 알립니다.
parcelable Rect;

Parcelable 인터페이스 구현하는 Rect 클래스의 예제

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): Ar<rayRect?> {
            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
    }
}
  • Rect 클래스를 마샬링하는 것은 간단함, Parcel에 쓸 수 있는 다양한 값을 확인하려면 Parcel의 다른 메소드를 확인해야 함

주의: 다른 프로세스에서 받는 데이터의 보안 문제를 항상 염두에 두세요. Rect의 경우 Parcel에서 네 숫자를 읽지만, 이 값의 범위가 호출자가 하려는 작업에 적합한지는 개발자에게 달려있습니다. 애플리케이션을 멀웨어로부터 안전하게 보호하는 방법에 관한 자세한 내용은 보안 도움말을 참고하세요.

Parcelable이 포함된 Bundle 인수를 사용하는 메소드

  • 만약 메소드가 parcelable이 포함될 것으로 예상되는 Bundle 객체를 허용한다면 Bundle.setClassLoader(ClassLoader)를 호출해 번들에서 값을 읽기 전에 Bundle의 클래스로더를 설정해야 함, 그러지 않으면 앱에 parcelable이 제대로 정의되어 있어도 ClassNotFoundException이 발생할 수 있음

Rect를 포함한 Bundle 인수를 사용하는 .aidl 파일 예제

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

/** 서비스 인터페이스 예제 */
interface IRectInsideBundle {
    /** Rect parcelable은 bundle의 "rect" 키로 저장됩니다. */
    void saveRect(in Bundle bundle);
}  

위 예제에 따라 Bundle에서 Rect를 읽기 전에 ClassLoader를 설정하는 예제

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // parcelable로 추가 작업 수행.
    }
}

IPC 메소드 호출

AIDL로 정의된 원격 인터페이스를 호출하는 방법

  1. 프로젝트의 src/ 경로에 .aidl 파일을 포함시키기
  2. AIDL을 기반으로 생성된 IBinder 인터페이스의 인스턴스 선언
  3. ServiceConnection 구현
  4. Context.bindService()를 호출해 ServiceConnection 구현체를 전달
  5. onServiceConnected()의 구현에서 service라고 불리는 IBinder 객체를 받아서 YourInterfaceName.Stub.asInterface((IBinder) service)를 호출해 받은 매개변수를 YourInterface 타입으로 형변환
  6. 인터페이스에 선언된 메소드 호출, 연결이 끊겼을 때 발생하는 DeadObjectException, 두 프로세스간의 AIDL 정의가 충돌할 때 발생하는 SecurityException 예외를 항상 처리해야 함
  7. 연결을 해제하려면 인터페이스의 인스턴스를 가지고 Context.unbindService()를 호출
  • IPC 서비스를 호출할 때, 프로세스 간에 객체는 참조 횟수가 계산되고, 메소드 인수로 익명 객체를 전송할 수 있다는 점을 염두에 두어야 함

아래는 ApiDemos 프로젝트의 Remote Service 샘플에서 가져온, AIDL로 생성된 서비스를 호출하는 샘플 코드입니다.

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** 서비스의 기본 인터페이스입니다.  */
    private var mService: IRemoteService? = null

    /** 서비스의 또 다른 인터페이스입니다.  */
    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

    /**
     * 서비스의 메인 인터페이스와 상호작용하는 클래스입니다.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // 서비스와의 연결이 성립되면 호출되며,
            // 서비스와 상호작용할 수 있는 서비스 객체를 제공합니다.
  			// IDL 인터페이스를 통해 서비스와 통신하므로,
            // 원시 서비스 객체에서 클라이언트 측 표현을 얻습니다.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // 서비스에 연결된 동안 서비스를 모니터링하려고 합니다.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // 이 경우, 서비스가 무언가를 하기도 전에 크래시가 발생합니다.
                // 곧 연결이 끊길 것이라 확신하기 때문에
  				// (재시작될 수 있다면 다시 연결될 것입니다.)
                // 여기서는 특별히 할 일이 없습니다.
            }

            // 샘플의 일부로 사용자에게 상황을 알립니다.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // 이 메소드는 서비스의 연결이 예상하지 못하게
            // 끊겼을 때(예: 서비스 프로세스가 중단되었을 때) 호출됩니다.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

            // 샘플의 일부로 사용자에게 상황을 알립니다.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * 서비스의 두번째 인터페이스와 상호작용하기 위한 클래스입니다.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // 두번째 인터페이스에 연결하는 것도 다른 인터페이스와 동일합니다.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

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

    private val mBindListener = View.OnClickListener {
        // 서비스와 몇 가지 연결을 설정합니다.
        // 인터페이스 이름으로 바인딩하면,
        // 동일한 인터페이스를 구현하는 다른 애플리케이션이
        // 원격 서비스를 대체할 수 있습니다.
        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) {
            // 서비스를 받았고, 등록했다면 이제 등록 해제할 시간입니다.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // 서비스가 크래시되면 특별히 할 일이 없습니다.
            }

            // 기존 연결을 해제합니다.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // 서비스를 호스팅하는 프로세스를 종료하려면 PID를 알아야 합니다.
        // 편리하게도 서비스에는 이 정보를 반환하는 호출이 있습니다.
        try {
       >     secondaryService?.pid?.also { pid -
                // 이 API를 사용하면 PID 기반으로 어떤 프로세스든 종료를 요청할 수 있지만,
                // 커널은 실제로 종료할 수 있는 PID에 대해 표준 제한을 둡니다.
                // 일반적으로 이는 애플리케이션을 실행하는 프로세스와
                // 그 애플리케이션이 생성한 추가 프로세스만 해당됩니다.
                // 공통 UID를 공유하는 패키지도 서로의 프로세스를 종료할 수 있습니다.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // 서버를 호스팅하는 프로세스가 죽었을 때도 정상적으로 복구합니다.
            // 이 샘플에서는 알림을 표시합니다.
            Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show()
        }
    }

    // ----------------------------------------------------------------------
    // 콜백을 어떻게 처리하는지 보여주는 코드입니다.
    // ----------------------------------------------------------------------

    /**
     * 이 구현은 원격 서비스로부터 콜백을 받는 데 사용됩니다.
     */
    private val mCallback = object : IRemoteServiceCallback.Stub() {
        /**
         * 원격 서비스가 새로운 값을 알리기 위해 정기적으로 호출합니다.
         * IPC 호출은 각 프로세스에서 실행되는 스레드 풀을 통해 디스패치되므로,
         * 여기서 실행되는 코드는 대부분의 경우와 달리 메인 스레드에서 실행되지 않습니다.
         * UI를 업데이트하려면 Handler를 사용하여 메인 스레드로 넘어가야 합니다.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * 이 액티비티의 표준 초기화입니다. UI를 설정한 후,
     * 사용자가 상호작용할 때까지 아무것도 하지 않습니다.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // 버튼 탭을 감시합니다.
        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: WeakReferenceTextView = 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)
            }
        }
    }
}

원문: https://developer.android.com/develop/background-work/services/aidl

0개의 댓글