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 を使用して制限付きサービスを作成する手順は次のとおりです。
.aidl
ファイルを作成するこのファイルは、メソッド シグネチャを使用してプログラミング インターフェースを定義します。
- インターフェースを実装する
Android SDK Tools は、
.aidl
ファイルに基づいて Java プログラミング言語でインターフェースを生成します。このインターフェースには、Binder
を拡張して AIDL インターフェースのメソッドを実装するStub
という内部抽象クラスがあります。Stub
クラスを拡張してメソッドを実装する必要があります。 - インターフェースをクライアントに公開する
注意: 最初のリリース後に AIDL インター���ェースに変更を加える場合は、そのサービスを使用する他のアプリケーションが中断しないように、下位互換性を維持する必要があります。つまり、サービスのインターフェースにアクセスできるように .aidl
ファイルを他のアプリにコピーする必要があるため、元のインターフェースのサポートを維持する必要があります。
.aidl ファイルを作成する
AIDL では、パラメータを受け取り値を返すことができる 1 つ以上のメソッドでインターフェースを宣言できるようにするシンプルな構文を使用します。パラメータと戻り値は任意の型にできます。他の AIDL 生成インターフェースを使用することもできます。
.aidl
ファイルは、Java プログラミング言語を使用して作成する必要があります。各 .aidl
ファイルでは単一のインターフェースを定義する必要があり、インターフェース宣言とメソッド シグネチャのみが必要です。
デフォルトでは、AIDL は次のデータ型をサポートしています。
- Java プログラミング言語のすべてのプリミティブ型(
int
、long
、char
、boolean
など) - プリミティブ型の配列(
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 を返すことができます。
- 非プリミティブ パラメータには、データの向きを示す方向タグ(
in
、out
、inout
のいずれか)が必要です(下記の例を参照)。プリミティブ、
String
、IBinder
、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.aidl
は IRemoteService.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; }
上記のコードサンプルでは、整数フィールド 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 は生成するコードでこれらのメソッドとフィールドを使用して、オブジェクトのマーシャリングとマーシャリング解除を行います。
たとえば、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
の他のメソッドをご覧ください。
警告: 他のプロセスからデータを受信する場合のセキュリティへの影響に注意してください。この場合、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 で定義されたリモート インターフェースを呼び出すには、呼び出し側のクラスで次の手順を行います。
- プロジェクトの
src/
ディレクトリに.aidl
ファイルを含めます。 - 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 プロジェクトの 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—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); } } } }