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

Android インターフェース定義言語(AIDL)は他の IDL と類似しており、プロセス間通信(IPC)を使用して相互に通信するためにクライアントとサービスが合意するプログラミング インターフェースを定義できます。

Android では、通常、あるプロセスが別のプロセスのメモリにアクセスできません。通信するには、オブジェクトをオペレーティング システムが理解できるプリミティブに分解し、その境界を越えてオブジェクトをマーシャリングする必要があります。このマーシャリングのコードを書くのは面倒なため、Android が AIDL を使用してマーシャリングを処理します。

注: AIDL は、異なるアプリケーションのクライアントが IPC 用のサービスにアクセスできるようにし、サービスでマルチスレッド処理を処理する場合にのみ必要です。異なるアプリ間で同時に IPC を実行する必要がない場合は、Binder を実装してインターフェースを作成します。IPC を実行したいがマルチスレッド処理が不要な場合は、Messenger を使用してインターフェースを実装します。いずれにせよ、AIDL を実装する前に、バインドされたサービスを十分に理解するようにしてください。

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

  • ローカル プロセスからの呼び出しは、その呼び出しを行っている同じスレッドで実行されます。これがメイン UI スレッドである場合、そのスレッドは引き続き AIDL インターフェースで実行されます。別のスレッドの場合は、そのスレッドがサービス内のコードを実行します。したがって、ローカル スレッドのみがサービスにアクセスしている場合は、サービス内で実行するスレッドを完全に制御できます。ただし、そのような場合は AIDL を一切使用しないでください。代わりに、Binder を実装してインターフェースを作成します。
  • リモート プロセスからの呼び出しは、プラットフォームが独自のプロセス内で維持しているスレッドプールからディスパッチされます。不明なスレッドからの着信に備え、複数の呼び出しが同時に行われるようにします。つまり、AIDL インターフェースの実装は完全にスレッドセーフでなければなりません。同じリモート オブジェクトに対する 1 つのスレッドからの呼び出しは、レシーバ側に順番に到着します。
  • oneway キーワードは、リモート呼び出しの動作を変更します。これを使用すると、リモート呼び出しでブロックは発生しません。トランザクション データを送信し、すぐに結果を返します。 インターフェースの実装は、最終的には、通常のリモート呼び出しとして、Binder スレッドプールからの通常の呼び出しとしてこれを受け取ります。oneway がローカル呼び出しで使用されている場合、影響はなく、呼び出しは引き続き同期されます。

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

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

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

AIDL を使用して制限付きサービスを作成する手順は次のとおりです。

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

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

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

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

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

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

注意: 最初のリリース後に AIDL インターフェースに変更を加える場合は、そのサービスを使用する他のアプリケーションが中断しないように、下位互換性を維持する必要があります。つまり、サービスのインターフェースにアクセスできるように .aidl ファイルを他のアプリにコピーする必要があるため、元のインターフェースのサポートを維持する必要があります。

.aidl ファイルを作成する

AIDL では、パラメータを受け取り値を返すことができる 1 つ以上のメソッドでインターフェースを宣言できるようにするシンプルな構文を使用します。パラメータと戻り値は任意の型にできます。他の AIDL 生成インターフェースを使用することもできます。

.aidl ファイルは、Java プログラミング言語を使用して作成する必要があります。各 .aidl ファイルでは単一のインターフェースを定義する必要があり、インターフェース宣言とメソッド シグネチャのみが必要です。

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

  • Java プログラミング言語のすべてのプリミティブ型(intlongcharboolean など)
  • プリミティブ型の配列(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 を返すことができます。
  • 非プリミティブ パラメータには、データの向きを示す方向タグ(inoutinout のいずれか)が必要です(下記の例を参照)。

    プリミティブ、StringIBinder、AIDL によって生成されたインターフェースは、デフォルトでは in であり、他の指定はできません。

    注意: パラメータのマーシャリングにはコストがかかるため、方向は本当に必要なものに限定してください。

  • .aidl ファイル内のすべてのコードコメントは、import ステートメントと package ステートメントの前のコメントを除き、生成された IBinder インターフェースに含まれます。
  • 文字列定数と int 定数は、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 */
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);
}

.aidl ファイルをプロジェクトの src/ ディレクトリに保存します。アプリケーションをビルドすると、SDK Tools によってプロジェクトの gen/ ディレクトリに IBinder インターフェース ファイルが生成されます。生成されるファイルの名前は、.aidl ファイルの名前と一致していますが、.java 拡張子が付いています。たとえば、IRemoteService.aidlIRemoteService.java になります。

Android Studio を使用している場合、増分ビルドではほぼ即座にバインダー クラスが生成されます。Android Studio を使用しない場合は、次回アプリをビルドするときに、Gradle ツールによってバインダクラスが生成されます。.aidl ファイルの記述が完了したらすぐに gradle assembleDebug または gradle assembleRelease を使用してプロジェクトをビルドし、生成されたクラスにコードをリンクできるようにします。

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

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

注: Stub は、いくつかのヘルパー メソッドも定義します。特に asInterface() は、IBinder(通常はクライアントの onServiceConnected() コールバック メソッドに渡されるもの)を受け取り、スタブ インターフェースのインスタンスを返します。このキャストを行う方法について詳しくは、IPC メソッドを呼び出すをご覧ください。

.aidl から生成されたインターフェースを実装するには、生成された Binder インターフェース(YourInterface.Stub など)を拡張し、.aidl ファイルから継承されたメソッドを実装します。

以下は、匿名インスタンスを使用した、上記の IRemoteService.aidl の例で定義した IRemoteService というインターフェースの実装例です。

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

これで、binder は、サービスの IPC インターフェースを定義する Stub クラス(Binder)のインスタンスになりました。次のステップでは、このインスタンスをクライアントに公開して、サービスとやり取りできるようにします。

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

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

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

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

IPC 経由でオブジェクトを渡す

Android 10(API レベル 29 以降)では、AIDL で直接 Parcelable オブジェクトを定義できます。AIDL インターフェース引数としてサポートされている型やその他の Parcelable も、ここではサポートされています。これにより、マーシャリング コードやカスタムクラスを手動で記述する余分な作業を回避できます。ただし、この場合、ベアな構造体も作成されます。カスタム アクセサやその他の機能が必要な場合は、代わりに 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;
}

上記のコードサンプルでは、整数フィールド lefttoprightbottom を持つ Java クラスが自動的に生成されます。関連するマーシャリング コードはすべて自動的に実装され、実装を追加しなくてもオブジェクトを直接使用できます。

IPC インターフェースを介して、あるプロセスから別のプロセスにカスタムクラスを送信することもできます。ただし、クラスのコードは IPC チャネルの反対側から利用でき、クラスは Parcelable インターフェースをサポートしている必要があります。Parcelable のサポートは重要です。これにより、Android システムがオブジェクトをプリミティブに分解して、プロセス間で整列化できるようになります。

Parcelable をサポートするカスタムクラスを作成する手順は次のとおりです。

  1. クラスに Parcelable インターフェースを実装します。
  2. writeToParcel を実装します。これはオブジェクトの現在の状態を取得して Parcel に書き込みます。
  3. Parcelable.Creator インターフェースを実装するオブジェクトであるクラスに、CREATOR という静的フィールドを追加します。
  4. 最後に、以下の Rect.aidl ファイルに示すように、Parcelable クラスを宣言する .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) { 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
    }
}

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 オブジェクトを受け入れる場合は、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() の実装では、service という IBinder インスタンスを受け取ります。YourInterfaceName.Stub.asInterface((IBinder)service) を呼び出して、返されたパラメータを YourInterface 型にキャストします。
  6. インターフェースで定義したメソッドを呼び出します。接続が切断されたときにスローされる DeadObjectException 例外を常にトラップします。また、SecurityException 例外をトラップします。この例外は、IPC メソッド呼び出しに関与する 2 つのプロセスに競合する 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);
            }
        }
    }
}