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 プログラミング言語でインターフェースを生成します。このインターフェースには、Stub
という名前の内部抽象クラスがあります。これはBinder
を拡張したクラスで、AIDL インターフェースで定義されたメソッドを実装しています。このStub
クラスを拡張してメソッドを実装します。 - インターフェースをクライアントに公開する
注意:最初のリリース後に AIDL インターフェースを変更する場合は、サービスを利用する他のアプリで問題が発生しないように、下方互換性を持たせる必要があります。サービスのインターフェースにアクセスするアプリは .aidl
ファイルのコピーを保持しています。そのため、元のインターフェースのサポートを維持する必要があります。
1 .aidl ファイルを作成する
AIDL では、パラメータと戻り値を持つ 1 つまたは複数のメソッドでインターフェースを宣言できる簡単な構文を使用します。パラメータと戻り値には任意の型を指定できます。AIDL が生成する他のインターフェースを使用することもできます。
まず、Java プログラミング言語で記述した .aidl
ファイルを作成する必要があります。.aidl
ファイルでは、1 つのインターフェースを宣言します。必要となるのは、インターフェースの宣言とメソッドのシグネチャのみです。
AIDL はデフォルトで次のデータ型をサポートします。
- Java プログラミング言語のすべてのプリミティブ型(
int
、long
、char
、boolean
など) String
CharSequence
List
List
内のすべての要素は、このリストに記載されているサポートされるデータ型、AIDL によって生成された他のインターフェース、宣言済みの Parcelable のいずれかである必要があります。List
は、「総称型」のクラス(たとえばList<String>
)として使用することもできます。List
インターフェースを使用するメソッドが生成されますが、受信側が実際に受け取る具象クラスは常にArrayList
になります。Map
Map
内のすべての要素は、このリストに記載されているサポートされるデータ型、AIDL によって生成された他のインターフェース、宣言済みの Parcelable のいずれかである必要があります。総称型の Map(たとえばMap<String,Integer>
といった形式)はサポートされません。Map
インターフェースを使用するメソッドが生成されますが、受信側が実際に受け取る具象クラスは常にHashMap
になります。
ここに記載されていないその他の型を使用する場合は、import
文が必要になります。インターフェースと同じパッケージで定義されている場合でも、インポート宣言が必要です。
サービス インターフェースを定義する際には、次の点に注意してください。
- メソッドは、パラメータがあってもなくても構いません。また、値を戻すメソッドでも、void 型のメソッドでも構いません。
- プリミティブ以外のパラメータには、データが流れる方向を示す方向タグが必要です。これは、
in
、out
、inout
のいずれかです(下記の例をご覧ください)。プリミティブはデフォルトで
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 } };
これによって、binder
に Stub
クラスのインスタンスが代入されます。このクラスは 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; } };
他のサンプルコードについては、ApiDemos の RemoteService.java
クラスをご覧ください。
IPC によるオブジェクトの受け渡し
IPC インターフェースでは、プロセス間でクラスを受け渡しすることもできます。その場合、クラスは Parcelable
インターフェースを実装し、IPC の通信相手側もクラスのコードを利用できる必要があります。Parcelable
インターフェースを実装すると、Android システムはオブジェクトをプリミティブに分解し、プロセスをまたいでからマーシャルすることができるようになるため、この実装は重要です。
Parcelable
プロトコルをサポートするクラスを作成するには、以下の手順に従います。
- クラスで
Parcelable
インターフェースを実装します。 writeToParcel
メソッドを実装します。このメソッドで、オブジェクトの現在の状態を取得してParcel
に書き込みます。- クラスに
CREATOR
という静的フィールドを追加します。これは、Parcelable.Creator
インターフェースを実装するオブジェクトです。 - 最後に、
.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
の他のメソッドをご確認ください。
警告:他のプロセスからデータを受け取ることによるセキュリティ面での影響も考慮するようにしてください。この場合、Rect
は Parcel
から 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
を読み取る前に 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 で定義されたリモート インターフェースを呼び出す際に、呼び出し側のクラスが従う必要がある手順を示します。
- プロジェクトの
src/
ディレクトリに.aidl
ファイルを置きます。 - AIDL に基づいて生成される
IBinder
インターフェースのインスタンスを宣言します。 ServiceConnection
を実装します。Context.bindService()
を呼び出して、ServiceConnection
の実装を渡します。- 実装した
onServiceConnected()
にIBinder
インスタンス(service
パラメータ)が渡されます。YourInterfaceName.Stub.asInterface((IBinder)service)
を呼び出して、受け取ったパラメータを YourInterface 型にキャストします。 - インターフェースで定義したメソッドを呼び出します。接続が切れたときにスローされる
DeadObjectException
例外は、常にトラップする必要があります。また、IPC メソッドの呼び出しに含まれる 2 つのプロセスで AIDL の定義が競合しているときにスローされるSecurityException
例外もトラップする必要があります。 - 接続を切断するには、インターフェースのインスタンスを指定して
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); } } } }