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 を使用して制限付きサービスを作成するには、以降のセクションで説明します。
.aidl
ファイルを作成するこのファイルでは、メソッド シグネチャを使ってプログラミング インターフェースを定義します。
- インターフェースを実装する
Android SDK ツールは、
.aidl
ファイルに基づいて Java プログラミング言語でインターフェースを生成します。このインターフェースには、Binder
を拡張し、AIDL インターフェースのメソッドを実装するStub
という名前の内部抽象クラスがあります。Stub
クラスを拡張してメソッドを実装する必要があります。 - インターフェースをクライアントに公開する
注意: 最初のリリース後に AIDL インターフェースを変更する場合は、サービスを利用する他のアプリケーションで問題が発生しないように、下方互換性を持たせる必要があります。サービスのインターフェースにアクセスするアプリは .aidl
ファイルのコピーを保持しています。そのため、元のインターフェースを維持する必要があります。
.aidl ファイルを作成する
AIDL では、パラメータと戻り値を持つ 1 つまたは複数のメソッドでインターフェースを宣言できる簡単な構文を使用します。パラメータと戻り値には任意の型を指定できます。他の AIDL が生成するインターフェースを使用することもできます。
.aidl
ファイルは Java プログラミング言語で作成する必要があります。各 .aidl
ファイルでは、1 つのインターフェースを宣言します。必要となるのは、インターフェースの宣言とメソッドのシグネチャのみです。
AIDL はデフォルトで次のデータ型をサポートします。
- Java プログラミング言語のすべてのプリミティブ型(
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
文が必要になります。インターフェースと同じパッケージで定義されている場合でも、インポート宣言が必要です。
サービス インターフェースを定義するときは、次の点に注意してください。
- メソッドは、パラメータがあってもなくても構いません。また、値を戻すメソッドでも、void 型のメソッドでも構いません。
- プリミティブ以外のパラメータには、データが流れる方向を示す方向タグが必要です。これは、
in
、out
、またはinout
です(以下の例をご覧ください)。プリミティブ、
String
、IBinder
、AIDL で生成されるインターフェースは、デフォルトでin
であり、それ以外の場合はできません。注意: パラメータのマーシャリングはコストがかかるため、方向は本当に必要なものに限定してください。
- 生成された
IBinder
インターフェースには、インポート ステートメントとパッケージ ステートメントの前のコメントを除き、.aidl
ファイルに含まれるすべてのコードコメントが含まれます。 - 文字列定数と 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
インターフェース ファイルを生成します。生成されたインターフェースには、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
は Stub
クラス(Binder
)のインスタンスで、サービス用の IPC インターフェースを定義します。次のステップでは、このインスタンスをクライアントに公開して、クライアントがサービスとやり取りできるようにします。
AIDL インターフェースを実装する場合は、次のルールに注意してください。
- 呼び出しを受信しても、それがメインスレッドで実行されるとは限りません。そのため、最初からマルチスレッドを考慮して設計し、スレッドセーフなサービスを構築する必要があります。
- デフォルトでは、IPC 呼び出しは同期的です。サービスがリクエストを完了するまでに数ミリ秒以上かかることがわかっている場合は、アクティビティのメインスレッドから呼び出しないでください。アプリがハングし、Android に「アプリケーション応答なし」ダイアログが表示される可能性があります。クライアントの別のスレッドから呼び出します。
- 呼び出し元に返されるのは、
Parcel.writeException()
のリファレンス ドキュメントに記載されている例外タイプのみです。
インターフェースをクライアントに公開する
実装したサービスのインターフェースは、公開してクライアントがバインドできるようにします。サービスのインターフェースを公開するには、Service
を拡張して onBind()
を実装し、生成された Stub
を実装するクラスのインスタンスを返すようにします(前のセクションで説明したように)。IRemoteService
サンプル インターフェースをクライアントに公開するサービスの例を次に示します。
Kotlin
class RemoteService : Service() { override fun onCreate() { super.onCreate() } override fun onBind(intent: Intent): IBinder { // Return the interface. return binder } private val binder = object : IRemoteService.Stub() { override fun getPid(): Int { return Process.myPid() } override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } } }
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
インターフェースを実装します。 writeToParcel
を実装します。このメソッドは、オブジェクトの現在の状態を取得してParcel
に書き込みます。Parcelable.Creator
インターフェースを実装するオブジェクトであるクラスに、CREATOR
という静的フィールドを追加します。- 最後に、次の
Rect.aidl
ファイルで示すように、Parcelable を実装したクラスを宣言する.aidl
ファイルを作成します。カスタム ビルドプロセスを使用している場合は、ビルドに
.aidl
ファイルを追加しないでください。C 言語のヘッダー ファイルと同様に、この.aidl
ファイルはコンパイルされません。
AIDL は、生成されたコードのメソッドやフィールドを利用して、オブジェクトのマーシャルやアンマーシャルを行います。
たとえば、次の Rect.aidl
ファイルは、Parcelable な Rect
クラスを作成します。
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
を読み取る前に Bundle
で ClassLoader
が明示的に設定されます。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
を実装します。Context.bindService()
を呼び出して、ServiceConnection
の実装を渡します。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); } } } }