Android インターフェース定義言語(AIDL)

Android インターフェース定義言語(AIDL)は、広く使用されている IDL の一種で、クライアントとサービスがプロセス間通信(IPC)で相互に通信するために双方が合意する必要があるプログラミング インターフェースを定義するものです。通常、Android のプロセスは、別のプロセスのメモリにアクセスすることはできません。そのため、相互に通信を行うには、オペレーティング システムが理解できるプリミティブにオブジェクトを分解し、プロセスの境界をまたいでからオブジェクトとしてまとめなおす(マーシャルする)必要があります。このマーシャル処理を実行するコードを毎回記述するのは効率的でないので、Android が AIDL を利用してその処理を代行するようになっています。

注:AIDL の利用が必須となるのは、クライアントが別のアプリからサービスにアクセスして IPC を行い、かつサービスがマルチスレッド対応である場合のみです。別のアプリから並列で IPC を行う必要がない場合は、Binder を実装してインターフェースを作成します。また、IPC を実行してもマルチスレッドに対応する必要がない場合は、Messenger を使用してインターフェースを実装します。いずれの場合も、AIDL を実装する前にバインドされたサービスについて理解するようにしてください。

AIDL インターフェースの設計を始める前に、AIDL インターフェースの呼び出しは直接関数呼び出しであることに注意する必要があります。どのスレッドから呼び出されるかを仮定することはできません。ローカル プロセスのスレッドからの呼び出しか、リモート プロセスのスレッドからの呼び出しかによって、実行される処理は異なります。特に次の点に注意が必要です。

  • ローカル プロセスからの呼び出しは、呼び出しを行ったスレッドと同じスレッドで実行されます。メイン UI スレッドから呼び出した場合、メイン UI スレッド上で AIDL インターフェースの実行が継続されます。別のスレッドから呼び出した場合、そのスレッドでサービスのコードが実行されます。そのため、サービスにアクセスするのがローカル スレッドのみの場合は、どのスレッドで実行されるかを完全に制御することができます(ただし、その場合はそもそも AIDL を使用するべきではなく、Binder を実装してインターフェースを作成するべきです)。
  • リモート プロセスから呼び出される場合、サービス側のプロセス内でプラットフォームが管理するスレッドプールからディスパッチされます。この場合、未知のスレッドから同時に着信する複数の呼び出しに対応できる必要があります。つまり、AIDL インターフェースの実装は完全にスレッドセーフでなければなりません。同じリモート オブジェクトに対する 1 つのスレッドからの呼び出しは、順序どおりに受信側に着信します。
  • oneway キーワードによって、リモート呼び出しの動作を変更できます。このキーワードを使用すると、リモート呼び出しの際に、トランザクション データを送信した後すぐに処理が戻されるため、処理がブロックされることはありません。その後、インターフェースの実装は Binder スレッドプールからの通常の呼び出しとして結果を受信します。これは通常のリモート呼び出しとして扱われます。oneway はローカル呼び出しには影響しません。ローカル呼び出しは同期的に処理されます。

AIDL インターフェースの定義

AIDL インターフェースは、Java プログラミング言語の構文を使用して .aidl ファイルで定義します。このファイルは、サービスをホストするアプリと、サービスにバインドするすべてのアプリのソースコード(src/ ディレクトリ内)に保存します。

.aidl ファイルを含むアプリをビルドすると、Android SDK ツールが .aidl ファイルに基づいて IBinder インターフェースを生成し、その結果をプロジェクトの gen/ ディレクトリに保存します。サービスは、必要に応じて IBinder インターフェースを実装します。その後、クライアント アプリをサービスにバインドし、IBinder からメソッドを呼び出して IPC を実行できるようになります。

AIDL を使用してバインドされたサービスを作成するには、次の手順に従います。

  1. .aidl ファイルを作成する

    このファイルでは、メソッド シグネチャを使ってプログラミング インターフェースを定義します。

  2. インターフェースを実装する

    Android SDK ツールが .aidl ファイルに基づいて Java プログラミング言語でインターフェースを生成します。このインターフェースには、Stub という名前の内部抽象クラスがあります。これは Binder を拡張したクラスで、AIDL インターフェースで定義されたメソッドを実装しています。この Stub クラスを拡張してメソッドを実装します。

  3. インターフェースをクライアントに公開する

    Service を実装して onBind() をオーバーライドし、Stub クラスの実装を返すようにします。

注意:最初のリリース後に AIDL インターフェースを変更する場合は、サービスを利用する他のアプリで問題が発生しないように、下方互換性を持たせる必要があります。サービスのインターフェースにアクセスするアプリは .aidl ファイルのコピーを保持しています。そのため、元のインターフェースのサポートを維持する必要があります。

1 .aidl ファイルを作成する

AIDL では、パラメータと戻り値を持つ 1 つまたは複数のメソッドでインターフェースを宣言できる簡単な構文を使用します。パラメータと戻り値には任意の型を指定できます。AIDL が生成する他のインターフェースを使用することもできます。

まず、Java プログラミング言語で記述した .aidl ファイルを作成する必要があります。.aidl ファイルでは、1 つのインターフェースを宣言します。必要となるのは、インターフェースの宣言とメソッドのシグネチャのみです。

AIDL はデフォルトで次のデータ型をサポートします。

  • Java プログラミング言語のすべてのプリミティブ型(intlongcharboolean など)
  • String
  • CharSequence
  • List

    List 内のすべての要素は、このリストに記載されているサポートされるデータ型、AIDL によって生成された他のインターフェース、宣言済みの Parcelable のいずれかである必要があります。List は、「総称型」のクラス(たとえば List<String>)として使用することもできます。List インターフェースを使用するメソッドが生成されますが、受信側が実際に受け取る具象クラスは常に ArrayList になります。

  • Map

    Map 内のすべての要素は、このリストに記載されているサポートされるデータ型、AIDL によって生成された他のインターフェース、宣言済みの Parcelable のいずれかである必要があります。総称型の Map(たとえば Map<String,Integer> といった形式)はサポートされません。Map インターフェースを使用するメソッドが生成されますが、受信側が実際に受け取る具象クラスは常に HashMap になります。

ここに記載されていないその他の型を使用する場合は、import 文が必要になります。インターフェースと同じパッケージで定義されている場合でも、インポート宣言が必要です。

サービス インターフェースを定義する際には、次の点に注意してください。

  • メソッドは、パラメータがあってもなくても構いません。また、値を戻すメソッドでも、void 型のメソッドでも構いません。
  • プリミティブ以外のパラメータには、データが流れる方向を示す方向タグが必要です。これは、inoutinout のいずれかです(下記の例をご覧ください)。

    プリミティブはデフォルトで in であり、他の指定はできません。

    注意:パラメータのマーシャルは負荷が大きい処理であるため、実際に必要となる方向のみを指定してください。

  • 生成される IBinder インターフェースには、.aidl ファイル内のすべてのコードコメントが含まれます。ただし、インポート宣言とパッケージ宣言の前にあるコメントは除きます。
  • 文字列定数と整数定数を AIDL インターフェースで定義できます。たとえば const int VERSION = 1; のようにします。
  • メソッド呼び出しは、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 */
internal interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    val pid:Int

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    fun basicTypes(anInt:Int, aLong:Long, aBoolean:Boolean, aFloat:Float,
                 aDouble:Double, aString:String)
}

.aidl ファイルは、プロジェクトの src/ ディレクトリに保存します。アプリをビルドすると、SDK ツールがプロジェクトの gen/ ディレクトリに IBinder インターフェース ファイルを生成します。生成されたファイル名は .aidl ファイルの名前と同じですが、拡張子は .java になります。たとえば、IRemoteService.aidl から IRemoteService.java が生成されます。

Android Studio を使用している場合、増分ビルドによりバインダクラスがほぼ瞬時に生成されます。Android Studio を使用していない場合は、次にアプリをビルドする際に Gradle ツールがバインダクラスを生成します。生成されるクラスにコードをリンクできるように、.aidl ファイルの記述後、できるだけ早くプロジェクトのビルドを行ってください。プロジェクトのビルドは、gradle assembleDebug(または gradle assembleRelease)で実行します。

2 インターフェースを実装する

アプリをビルドすると、Android SDK ツールは .aidl ファイルと同じ名前の .java インターフェース ファイルを生成します。生成されたインターフェースには、Stub という名前のサブクラスが含まれています。このクラス(たとえば、YourInterface.Stub)は、親インターフェースを実装する抽象クラスであり、.aidl ファイルのすべてのメソッドが宣言されています。

注: Stub には、いくつかのヘルパー メソッドも定義されています。中でも有用なのが asInterface() です。このメソッドは、IBinder(通常は、クライアントの onServiceConnected() コールバック メソッドに渡されるもの)を受け取り、スタブ インターフェースのインスタンスを返します。このメソッドの詳細については、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
    }
}

Java

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 を継承しており、サービスへのリモート プロシージャ コール インターフェースが定義されています。次のステップでは、このインスタンスをクライアントに公開し、クライアントがサービスと通信できるようにします。

AIDL インターフェースを実装する場合、次のルールに注意する必要があります。

  • 呼び出しを受信しても、それがメインスレッドで実行されるとは限りません。そのため、最初からマルチスレッドを考慮して設計し、スレッドセーフなサービスをビルドする必要があります。
  • デフォルトで、リモート プロシージャ コールは同期的に呼び出されます。サービスがリクエストを完了するまでに数ミリ秒以上かかることが予想される場合、アクティビティのメインスレッドからは呼び出しを行わないようにしてください。メインスレッドから呼び出すと、アプリがハングする(「アプリは応答していません」というダイアログが表示される)可能性があります。通常は、クライアントの別のスレッドから呼び出します。
  • 例外がスローされても、呼び出し元に戻されることはありません。

3 インターフェースをクライアントに公開する

サービスのインターフェースを実装したら、それをクライアントに公開してバインドできるようにする必要があります。サービスのインターフェースを公開するには、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
        }
    }
}

Java

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 インスタンスを受信します。

クライアントはインターフェースのクラスにアクセスできる必要があります。そのため、クライアントとサービスが別のアプリである場合は、クライアント アプリの src/ ディレクトリに .aidl ファイルのコピーを置く必要があります。そうすることによって android.os.Binder インターフェースが生成され、クライアントが AIDL のメソッドにアクセスできるようになります。

onServiceConnected() コールバックで IBinder を受信した後、クライアントは 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 example above 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
    }
}

Java

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 example above 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;
    }
};

他のサンプルコードについては、ApiDemosRemoteService.java クラスをご覧ください。

IPC によるオブジェクトの受け渡し

IPC インターフェースでは、プロセス間でクラスを受け渡しすることもできます。その場合、クラスは Parcelable インターフェースを実装し、IPC の通信相手側もクラスのコードを利用できる必要があります。Parcelable インターフェースを実装すると、Android システムはオブジェクトをプリミティブに分解し、プロセスをまたいでからマーシャルすることができるようになるため、この実装は重要です。

Parcelable プロトコルをサポートするクラスを作成するには、以下の手順に従います。

  1. クラスで Parcelable インターフェースを実装します。
  2. writeToParcel メソッドを実装します。このメソッドで、オブジェクトの現在の状態を取得して Parcel に書き込みます。
  3. クラスに CREATOR という静的フィールドを追加します。これは、Parcelable.Creator インターフェースを実装するオブジェクトです。
  4. 最後に、.aidl ファイルを作成し、Parcelable を実装したクラスを宣言します(下記の Rect.aidl ファイルを参照)。

    カスタム ビルドプロセスを使用している場合は、ビルド対象に .aidl ファイルを追加しないでください。C 言語のヘッダー ファイルと同様に、.aidl ファイルはコンパイルするものではありません。

AIDL は、生成されたコードのメソッドやフィールドを利用して、オブジェクトのマーシャルやアンマーシャルを行います。

たとえば、次の例は Parcelable な Rect クラスを作成する Rect.aidl ファイルです。

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) { Rect() }
        }
    }

    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
    }
}

Java

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 を引数として受け取るメソッドが AIDL インターフェースに含まれる場合は、必ず、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);
}
下記の実装からわかるように、Rect を読み取る前に ClassLoaderBundle で明示的に設定されます。

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
    }
}

Java

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. AIDL に基づいて生成される IBinder インターフェースのインスタンスを宣言します。
  3. ServiceConnection を実装します。
  4. Context.bindService() を呼び出して、ServiceConnection の実装を渡します。
  5. 実装した onServiceConnected()IBinder インスタンス(service パラメータ)が渡されます。YourInterfaceName.Stub.asInterface((IBinder)service) を呼び出して、受け取ったパラメータを YourInterface 型にキャストします。
  6. インターフェースで定義したメソッドを呼び出します。接続が切れたときにスローされる DeadObjectException 例外は、常にトラップする必要があります。また、IPC メソッドの呼び出しに含まれる 2 つのプロセスで AIDL の定義が競合しているときにスローされる SecurityException 例外もトラップする必要があります。
  7. 接続を切断するには、インターフェースのインスタンスを指定して Context.unbindService() を呼び出します。

IPC サービスを呼び出す際は、次の点に注意してください。

  • オブジェクトはプロセス間で有効な参照です。
  • メソッドの引数として匿名オブジェクトを渡すことも可能です。

サービスへのバインドの詳細については、バインドされたサービスをご覧ください。

次に示すのは、AIDL によって作成されたサービスを呼び出すサンプルコードです。このコードは、ApiDemos プロジェクトのリモート サービス サンプルからの抜粋です。

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface we will be calling on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface we 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 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)
            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 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(
                    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 has been
            // unexpectedly disconnected -- 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 allows other applications to 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
                // has crashed.
            }

            // 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 our service, we need to know its
        // PID.  Conveniently our service has a call that will return
        // to us that information.
        try {
            secondaryService?.pid?.also { pid ->
                // 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)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // Just for purposes of the 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 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.
         */
        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 poke it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button clicks.
        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 will be 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 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(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 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);
            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 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;
            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 allows other applications to 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
                        // has crashed.
                    }
                }

                // 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 will return
            // to us that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.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);
                    callbackText.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) {
            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);
            }
        }
    }
}