Android インターフェース定義言語(AIDL)は他の IDL と類似しています。プロセス間通信(IPC)を使用して相互に通信するために、クライアントとサービスの両方が合意するプログラミング インターフェースを定義できます。
Android では、通常、1 つのプロセスが別のプロセスのメモリにアクセスすることはできません。通信するには、オブジェクトをオペレーティング システムが理解できるプリミティブに分解し、その境界を越えてオブジェクトをマーシャルする必要があります。マウントリングを行うコードは記述が面倒なため、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 ツールは .aidl
ファイルに基づいて IBinder
インターフェースを生成し、プロジェクトの gen/
ディレクトリに保存します。サービスは、必要に応じて IBinder
インターフェースを実装する必要があります。クライアント アプリケーションは、サービスにバインドし、IBinder
からメソッドを呼び出して IPC を実行できます。
AIDL を使用して境界付きサービスを作成する手順は次のとおりです。以降のセクションで詳しく説明します。
.aidl
ファイルを作成するこのファイルでは、メソッド シグネチャを使用してプログラミング インターフェースを定義します。
- インターフェースを実装する
Android SDK ツールは、
.aidl
ファイルに基づいて Java プログラミング言語でインターフェースを生成します。このインターフェースには、Binder
を拡張し、AIDL インターフェースのメソッドを実装するStub
という内部抽象クラスがあります。Stub
クラスを拡張してメソッドを実装する必要があります。 - インターフェースをクライアントに公開する
注意: 最初のリリース後に AIDL インターフェースに加えた変更は、サービスを使用している他のアプリが破損しないように、下位互換性を維持する必要があります。つまり、.aidl
ファイルは、サービス インターフェースにアクセスできるように他のアプリにコピーする必要があるため、元のインターフェースのサポートを維持する必要があります。
.aidl ファイルを作成する
AIDL では、パラメータを取り、値を返す 1 つ以上のメソッドを持つインターフェースを宣言できるシンプルな構文が使用されます。パラメータと戻り値は、他の AIDL 生成インターフェースを含め、任意の型にできます。
.aidl
ファイルは Java プログラミング言語を使用して作成する必要があります。各 .aidl
ファイルで定義するインターフェースは 1 つだけです。インターフェースの宣言とメソッド シグネチャのみが必要です。
デフォルトでは、AIDL は次のデータ型をサポートしています。
- Java プログラミング言語の
short
を除くすべてのプリミティブ型(int
、long
、char
、boolean
など) - 任意の型の配列(
int[]
、MyParcelable[]
など) String
CharSequence
List
List
内のすべての要素は、このリストでサポートされているデータ型のいずれか、または宣言した他の AIDL 生成インターフェースまたは Parcelable のいずれかである必要があります。List
は、必要に応じて、List<String>
などのパラメータ化された型クラスとして使用できます。メソッドはList
インターフェースを使用するように生成されますが、相手側が受け取る実際の具象クラスは常にArrayList
です。Map
Map
内のすべての要素は、このリストでサポートされているデータ型のいずれか、または宣言した他の AIDL 生成インターフェースまたは Parcelable のいずれかである必要があります。Map<String,Integer>
形式のパラメータ化された型マップはサポートされていません。メソッドはMap
インターフェースを使用するように生成されますが、相手側が受け取る実際のコンクリート クラスは常にHashMap
です。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); }
.aidl
ファイルをプロジェクトの src/
ディレクトリに保存します。アプリケーションをビルドすると、SDK ツールによってプロジェクトの gen/
ディレクトリに IBinder
インターフェース ファイルが生成されます。生成されたファイルの名前は .aidl
ファイルの名前と一致しますが、拡張子は .java
です。たとえば、IRemoteService.aidl
は IRemoteService.java
になります。
Android Studio を使用している場合、増分ビルドによってバインダー クラスがほぼ即座に生成されます。Android Studio を使用していない場合は、次回アプリをビルドするときに Gradle ツールによってバインダー クラスが生成されます。.aidl
ファイルの作成が完了したらすぐに gradle assembleDebug
または gradle assembleRelease
でプロジェクトをビルドし、コードが生成されたクラスに対してリンクできるようにします。
インターフェースを実装する
アプリをビルドすると、Android SDK ツールは .aidl
ファイルの名前を付けて .java
インターフェース ファイルを生成します。生成されたインターフェースには、YourInterface.Stub
などの親インターフェースの抽象実装である Stub
というサブクラスが含まれ、.aidl
ファイルのすべてのメソッドが宣言されます。
注: Stub
には、いくつかのヘルパー メソッドも定義されています。特に asInterface()
は、通常はクライアントの onServiceConnected()
コールバック メソッドに渡される IBinder
を受け取り、スタブ インターフェースのインスタンスを返します。このキャストを行う方法について詳しくは、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; }
上記のコードサンプルでは、整数フィールド left
、top
、right
、bottom
を持つ Java クラスが自動的に生成されます。関連するすべてのマーシャリング コードが自動的に実装され、実装を追加しなくてもオブジェクトを直接使用できます。
IPC インターフェースを介して、カスタムクラスをプロセス間で送信することもできます。ただし、クラスのコードが IPC チャネルの反対側で使用可能であること、およびクラスが Parcelable
インターフェースをサポートしていることを確認してください。Parcelable
をサポートすることは重要です。これにより、Android システムはオブジェクトをプロセス間でマーシャリングできるプリミティブに分解できます。
Parcelable
をサポートするカスタムクラスを作成するには、次の操作を行います。
- クラスが
Parcelable
インターフェースを実装するようにします。 - オブジェクトの現在の状態を取得して
Parcel
に書き込むwriteToParcel
を実装します。 Parcelable.Creator
インターフェースを実装するオブジェクトであるクラスに、CREATOR
という静的フィールドを追加します。- 最後に、次の
Rect.aidl
ファイルに示すように、Parcelable クラスを宣言する.aidl
ファイルを作成します。カスタム ビルドプロセスを使用している場合は、
.aidl
ファイルをビルドに追加しないでください。C 言語のヘッダー ファイルと同様に、この.aidl
ファイルはコンパイルされません。
AIDL は、生成したコード内のこれらのメソッドとフィールドを使用して、オブジェクトのマーシャリングとアンマーシャリングを行います。
たとえば、以下は、パーセル可能な 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
に書き込むことができる他の種類の値を確認します。
警告: 他のプロセスからデータを受信することのセキュリティ上の影響を念頭に置いてください。この場合、Rect
は Parcel
から 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
の読み取り前に ClassLoader
が 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. } }
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 で定義されたリモート インターフェースを呼び出すには、呼び出し元のクラスで次の手順を実施します。
.aidl
ファイルをプロジェクトのsrc/
ディレクトリに含めます。- AIDL に基づいて生成される
IBinder
インターフェースのインスタンスを宣言します。 ServiceConnection
を実装します。ServiceConnection
の実装を渡してContext.bindService()
を呼び出します。onServiceConnected()
の実装では、service
というIBinder
インスタンスを受け取ります。YourInterfaceName.Stub.asInterface((IBinder)service)
を呼び出して、返されたパラメータをYourInterface
型にキャストします。- インターフェースで定義したメソッドを呼び出します。接続が切れたときにスローされる
DeadObjectException
例外は、常にトラップする必要があります。また、SecurityException
例外をトラップします。これは、IPC メソッド呼び出しに関与する 2 つのプロセスに競合する AIDL 定義がある場合にスローされます。 - 接続を切断するには、インターフェースのインスタンスで
Context.unbindService()
を呼び出します。
IPC サービスを呼び出す際は、次の点に注意してください。
- オブジェクトはプロセス間で有効な参照です。
- 匿名オブジェクトはメソッド引数として送信できます。
サービスへのバインディングの詳細については、バインドされたサービスの概要をご覧ください。
以下は、AIDL で作成されたサービスを呼び出す方法を示すサンプルコードです。これは、ApiDemos プロジェクトのリモート サービス サンプルから抜粋したものです。
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—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—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); } } } }