Xamarin.Android のバインドされたサービス
バインドされたサービスは、クライアント (Android アクティビティなど) が操作できるクライアント/サーバー インターフェイスを提供する Android サービスです。 このガイドでは、バインドされたサービスの作成に関連する主要なコンポーネントと、Xamarin.Android アプリケーションでそれを使用する方法について説明します。
バインドされたサービスの概要
クライアントがサービスと直接やり取りするためのクライアント/サーバー インターフェイスを提供するサービスは、 バインドされたサービスと呼ばれます。 1 つのサービスの 1 つのインスタンスに同時に複数のクライアントを接続できます。 バインドされたサービスとクライアントは、互いに分離されます。 代わりに、Android は、2 つの間の接続の状態を管理する一連の中間オブジェクトを提供します。 この状態は、インターフェイスを実装 Android.Content.IServiceConnection
するオブジェクトによって維持されます。 このオブジェクトは、クライアントによって作成され、パラメーターとしてメソッドに BindService
渡されます。 これは BindService
、任意 Android.Content.Context
のオブジェクト (Activity など) で使用できます。 これは、Android オペレーティング システムに対して、サービスを起動してクライアントをバインドするための要求です。 クライアントがメソッドを使用して BindService
サービスにバインドするには、次の 3 つの方法があります。
- サービス バインダー – サービス バインダーは、インターフェイスを実装する
Android.OS.IBinder
クラスです。 ほとんどのアプリケーションでは、このインターフェイスを直接実装するのではなく、クラスを拡張しますAndroid.OS.Binder
。 これは最も一般的な方法であり、サービスとクライアントが同じプロセスに存在する場合に適しています。 - Messenger の使用 – この手法は、サービスが別のプロセスに存在する可能性がある場合に適しています。 代わりに、サービス要求はクライアントとサービスの間で
Android.OS.Messenger
マーシャリングされます。 要求Android.OS.Handler
を処理Messenger
するサービスに作成されます。 これについては、別のガイドで説明します。 - Android インターフェイス定義言語 (AIDL) の使用 – AIDL は、このガイドでは説明しない高度な手法です。
クライアントがサービスにバインドされると、2 つの間の通信はオブジェクトを介して Android.OS.IBinder
行われます。 このオブジェクトは、クライアントがサービスと対話できるようにするインターフェイスを担当します。 Xamarin.Android アプリケーションごとにこのインターフェイスを最初から実装する必要はありません。Android SDK は、クライアントとサービスの間でオブジェクトをマーシャリングするために必要なほとんどのコードを処理するクラスを提供 Android.OS.Binder
します。
クライアントがサービスで完了したら、メソッドを呼び出してバインドを解除する UnbindService
必要があります。 最後のクライアントがサービスからバインド解除されると、Android はバインドされたサービスを停止して破棄します。
この図は、アクティビティ、サービス接続、バインダー、およびサービスのすべてが相互にどのように関連しているかを示しています。
このガイドでは、バインドされたサービスを実装するためにクラスを Service
拡張する方法について説明します。 また、クライアントがサービスと通信できるように、実装 IServiceConnection
と拡張 Binder
についても説明します。 サンプル アプリは、 BoundServiceDemo という 1 つの Xamarin.Android プロジェクトを含むソリューションを含むこのガイドに付属しています。 これは、サービスを実装する方法とアクティビティをバインドする方法を示す非常に基本的なアプリケーションです。 バインドされたサービスには、1 つのメソッドのみを含む非常に単純な API があり、 GetFormattedTimestamp
サービスが開始されたときと実行されている期間をユーザーに伝える文字列を返します。 また、このアプリを使用すると、ユーザーは手動でサービスのバインドを解除してバインドできます。
バインドされたサービスの実装と使用
Android アプリケーションがバインドされたサービスを使用するには、次の 3 つのコンポーネントを実装する必要があります。
- クラスを
Service
拡張し、ライフサイクル コールバック メソッドを実装する – このクラスには、サービスの要求される作業を実行するコードが含まれます。 これについては、以下で詳しく説明します。 - 実装するクラスを作成する
IServiceConnection
– このインターフェイスは、サービスへの接続が変更されたとき、つまりクライアントがサービスに接続または切断されたときにクライアントに通知するために、Android によって呼び出されるコールバック メソッドを提供します。 サービス接続では、クライアントがサービスと直接対話するために使用できるオブジェクトへの参照も提供されます。 この参照は バインダーと呼ばれます。 - 実装するクラスを作成する
IBinder
– Binder 実装は、クライアントがサービスとの通信に使用する API を提供します。 バインダーは、バインドされたサービスへの参照を提供し、メソッドを直接呼び出せるようにするか、バインダーは、バインドされたサービスをカプセル化してアプリケーションから非表示にするクライアント API を提供できます。 リモートIBinder
プロシージャ呼び出しに必要なコードを指定する必要があります。 インターフェイスを直接実装IBinder
する必要はありません (または推奨)。 代わりに、アプリケーションで必要な基本機能の大部分をBinder
提供する型を拡張するIBinder
必要があります。 - サービスの開始とバインド – サービス 接続、バインダー、およびサービスが作成されると、Android アプリケーションはサービスを開始し、サービスにバインドする役割を担います。
これらの各手順については、以下のセクションで詳しく説明します。
クラスを拡張するService
Xamarin.Android を使用してサービスを作成するには、サブクラス化 Service
してクラス ServiceAttribute
を装飾する必要があります。 この属性は、アプリの AndroidManifest.xml ファイルにサービスを適切に登録するために Xamarin.Android ビルド ツールによって使用されます。バインドされたサービスには、そのライフサイクル内の重要なイベントに関連付けられた独自のライフサイクルとコールバック メソッドがあります。 次の一覧は、サービスが実装する一般的なコールバック メソッドの一部の例です。
OnCreate
– このメソッドは、サービスのインスタンス化中に Android によって呼び出されます。 これは、サービスの有効期間中にサービスに必要なすべての変数またはオブジェクトを初期化するために使用されます。 このメソッドは省略可能です。OnBind
– このメソッドは、すべてのバインドされたサービスによって実装される必要があります。 最初のクライアントがサービスに接続しようとしたときに呼び出されます。 クライアントがサービスとIBinder
対話できるように、インスタンスが返されます。 サービスが実行されている限り、オブジェクトはサービスIBinder
にバインドする今後のクライアント要求を満たすために使用されます。OnUnbind
– このメソッドは、バインドされているすべてのクライアントがバインドされていない場合に呼び出されます。 このメソッドから戻true
ると、サービスは後で、新しいクライアントがバインドされるときに渡されたOnUnbind
意図を使用して呼び出されますOnRebind
。 これは、サービスがバインド解除された後も実行を継続するときに行います。 これは、最近バインドされていないサービスも開始されたサービスであり、StopService
呼StopSelf
び出されなかった場合に発生します。 このようなシナリオでは、OnRebind
意図を取得できます。 既定値は何も返false
しません。 省略可能。OnDestroy
– このメソッドは、Android がサービスを破棄するときに呼び出されます。 リソースの解放など、必要なクリーンアップは、このメソッドで実行する必要があります。 省略可能。
バインドされたサービスの主要なライフサイクル イベントを次の図に示します。
このガイドに付属するコンパニオン アプリケーションの次のコード スニペットは、Xamarin.Android でバインドされたサービスを実装する方法を示しています。
using Android.App;
using Android.Util;
using Android.Content;
using Android.OS;
namespace BoundServiceDemo
{
[Service(Name="com.xamarin.ServicesDemo1")]
public class TimestampService : Service, IGetTimestamp
{
static readonly string TAG = typeof(TimestampService).FullName;
IGetTimestamp timestamper;
public IBinder Binder { get; private set; }
public override void OnCreate()
{
// This method is optional to implement
base.OnCreate();
Log.Debug(TAG, "OnCreate");
timestamper = new UtcTimestamper();
}
public override IBinder OnBind(Intent intent)
{
// This method must always be implemented
Log.Debug(TAG, "OnBind");
this.Binder = new TimestampBinder(this);
return this.Binder;
}
public override bool OnUnbind(Intent intent)
{
// This method is optional to implement
Log.Debug(TAG, "OnUnbind");
return base.OnUnbind(intent);
}
public override void OnDestroy()
{
// This method is optional to implement
Log.Debug(TAG, "OnDestroy");
Binder = null;
timestamper = null;
base.OnDestroy();
}
/// <summary>
/// This method will return a formatted timestamp to the client.
/// </summary>
/// <returns>A string that details what time the service started and how long it has been running.</returns>
public string GetFormattedTimestamp()
{
return timestamper?.GetFormattedTimestamp();
}
}
}
このサンプルでは、メソッドは OnCreate
、クライアントによって要求されるタイムスタンプを取得および書式設定するためのロジックを保持するオブジェクトを初期化します。 最初のクライアントがサービスにバインドしようとすると、Android によってメソッドが OnBind
呼び出されます。 このサービスは、実行中の TimestampBinder
サービスのこのインスタンスにクライアントがアクセスできるようにするオブジェクトをインスタンス化します。 この TimestampBinder
クラスについては、次のセクションで説明します。
IBinder の実装
前述のように、オブジェクトは IBinder
クライアントとサービスの間の通信チャネルを提供します。 Android アプリケーションではインターフェイスAndroid.OS.Binder
をIBinder
実装しないでください。拡張する必要があります。 このクラスは Binder
、必要なインフラストラクチャの多くを提供します。これは、(別のプロセスで実行されている可能性がある) サービスからクライアントにバインダー オブジェクトをマーシャリングするために必要です。 ほとんどの場合、 Binder
サブクラスは数行のコードのみで、サービスへの参照をラップします。 この例では、 TimestampBinder
クライアントに公開 TimestampService
するプロパティがあります。
public class TimestampBinder : Binder
{
public TimestampBinder(TimestampService service)
{
this.Service = service;
}
public TimestampService Service { get; private set; }
}
これにより Binder
、サービスでパブリック メソッドを呼び出すことができます。次に例を示します。
string currentTimestamp = serviceConnection.Binder.Service.GetFormattedTimestamp()
拡張が完了したら Binder
、すべてを一緒に接続するために実装 IServiceConnection
する必要があります。
サービス接続の作成
will IServiceConnection
present|introduce|expose|クライアントにオブジェクトを Binder
接続します。 インターフェイスの IServiceConnection
実装に加えて、クラスを拡張 Java.Lang.Object
する必要があります。 また、サービス接続は、クライアントがアクセス Binder
できる (したがって、バインドされたサービスと通信する) 何らかの方法も提供する必要があります。
このコードは、付属のサンプル プロジェクトからのコードであり、実装 IServiceConnection
する方法の 1 つです。
using Android.Util;
using Android.OS;
using Android.Content;
namespace BoundServiceDemo
{
public class TimestampServiceConnection : Java.Lang.Object, IServiceConnection, IGetTimestamp
{
static readonly string TAG = typeof(TimestampServiceConnection).FullName;
MainActivity mainActivity;
public TimestampServiceConnection(MainActivity activity)
{
IsConnected = false;
Binder = null;
mainActivity = activity;
}
public bool IsConnected { get; private set; }
public TimestampBinder Binder { get; private set; }
public void OnServiceConnected(ComponentName name, IBinder service)
{
Binder = service as TimestampBinder;
IsConnected = this.Binder != null;
string message = "onServiceConnected - ";
Log.Debug(TAG, $"OnServiceConnected {name.ClassName}");
if (IsConnected)
{
message = message + " bound to service " + name.ClassName;
mainActivity.UpdateUiForBoundService();
}
else
{
message = message + " not bound to service " + name.ClassName;
mainActivity.UpdateUiForUnboundService();
}
Log.Info(TAG, message);
mainActivity.timestampMessageTextView.Text = message;
}
public void OnServiceDisconnected(ComponentName name)
{
Log.Debug(TAG, $"OnServiceDisconnected {name.ClassName}");
IsConnected = false;
Binder = null;
mainActivity.UpdateUiForUnboundService();
}
public string GetFormattedTimestamp()
{
if (!IsConnected)
{
return null;
}
return Binder?.GetFormattedTimestamp();
}
}
}
バインド プロセスの一環として、Android はメソッドを OnServiceConnected
呼び出し name
、バインドされているサービスと binder
、サービス自体への参照を保持するサービスを提供します。 この例では、サービス接続には 2 つのプロパティがあります。1 つは Binder への参照を保持し、クライアントがサービスに接続されているかどうかを示すブール型フラグです。
この OnServiceDisconnected
メソッドは、クライアントとサービスの間の接続が予期せず失われたり、切断されたりした場合にのみ呼び出されます。 このメソッドを使用すると、クライアントはサービスの中断に応答できます。
明示的な意図を使用したサービスの開始とバインド
バインドされたサービスを使用するには、クライアント (Activity など) がメソッドを実装して呼び出すオブジェクトをインスタンス化するAndroid.Content.IServiceConnection
BindService
必要があります。 BindService
は、サービスがバインドされている場合は返されます。そうでない場合は返false
されますtrue
。 BindService
メソッドは 3 つのパラメーターを受け取ります。
- An
Intent
– 意図は、接続するサービスを明示的に識別する必要があります。 - An
IServiceConnection
オブジェクト – このオブジェクトは、バインドされたサービスが開始および停止されたときにクライアントに通知するコールバック メソッドを提供する仲介者です。 Android.Content.Bind
enum – このパラメーターは、オブジェクトをバインドするときにシステムによって使用されるフラグのセットです。 最も一般的に使用される値はBind.AutoCreate
、サービスがまだ実行されていない場合に自動的に開始されます。
次のコード スニペットは、明示的な意図を使用してアクティビティでバインドされたサービスを開始する方法の例です。
protected override void OnStart ()
{
base.OnStart ();
if (serviceConnection == null)
{
this.serviceConnection = new TimestampServiceConnection(this);
}
Intent serviceToStart = new Intent(this, typeof(TimestampService));
BindService(serviceToStart, this.serviceConnection, Bind.AutoCreate);
}
重要
Android 5.0 (API レベル 21) 以降では、明示的な意図を持つサービスにのみバインドできます。
サービス接続とバインダーに関するアーキテクチャ上の注意事項。
一部のTimestampBinder
OOPの純粋主義者は、それが最も簡単な形で「見知らぬ人と話をしないでください。あなたの友人にのみ話す" この特定の実装では、具象 TimestampService
クラスがすべてのクライアントに公開されます。
厳密に言えば、クライアントが具体的なクラスを TimestampService
クライアントに知り、公開することは、アプリケーションをより脆弱にし、有効期間にわたって維持するのが困難になる可能性があります。 別の方法として、メソッドを公開 GetFormattedTimestamp()
するインターフェイスと、(または可能なサービス接続クラス) を介した Binder
サービスへのプロキシ呼び出しを使用します。
public class TimestampBinder : Binder, IGetTimestamp
{
TimestampService service;
public TimestampBinder(TimestampService service)
{
this.service = service;
}
public string GetFormattedTimestamp()
{
return service?.GetFormattedTimestamp();
}
}
この特定の例では、アクティビティがサービス自体でメソッドを呼び出すことができます。
// In this example the Activity is only talking to a friend, i.e. the IGetTimestamp interface provided by the Binder.
string currentTimestamp = serviceConnection.Binder.GetFormattedTimestamp()