Xamarin.Android のバインドされたサービス

バインドされたサービスは、クライアント (Android アクティビティなど) が操作できるクライアント/サーバー インターフェイスを提供する Android サービスです。 このガイドでは、バインドされたサービスの作成に関連する主要なコンポーネントと、Xamarin.Android アプリケーションで使用する方法について説明します。

Bound Services の概要

クライアントがサービスと直接やり取りするためのクライアント サーバー インターフェイスを提供するサービスは、 バインドされたサービスと呼ばれます。 サービスの 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 スマートフォンで実行されているアプリケーションのスクリーンショット

バインドされたサービスの実装と使用

Android アプリケーションでバインドされたサービスを使用するには、次の 3 つのコンポーネントを実装する必要があります。

  1. クラスを Service 拡張し、ライフサイクル コールバック メソッドを実装する – このクラスには、サービスに要求される作業を実行するコードが含まれます。 これについては、以下で詳しく説明します。
  2. を実装するクラスを作成する IServiceConnection – このインターフェイスは、サービスへの接続が変更されたとき、つまりクライアントがサービスに接続または切断されたときにクライアントに通知するために Android によって呼び出されるコールバック メソッドを提供します。 サービス接続では、クライアントがサービスと直接やり取りするために使用できるオブジェクトへの参照も提供されます。 この参照は バインダーと呼ばれます。
  3. を実装するクラスを作成する IBinderバインダー 実装は、クライアントがサービスとの通信に使用する API を提供します。 バインダーは、バインドされたサービスへの参照を提供して、メソッドを直接呼び出せるようにするか、バインドされたサービスをカプセル化してアプリケーションから非表示にするクライアント API をバインダーが提供できます。 は IBinder 、リモート プロシージャ 呼び出しに必要なコードを提供する必要があります。 インターフェイスを直接実装 IBinder する必要はありません (または推奨)。 代わりに、アプリケーションは、 で Binder 必要な基本機能の大部分を提供する型を拡張する IBinder必要があります。
  4. サービスの開始とバインド – サービス 接続、バインダー、およびサービスが作成されると、Android アプリケーションはサービスを開始し、それにバインドする役割を担います。

これらの各手順については、次のセクションで詳しく説明します。

クラスを拡張するService

Xamarin.Android を使用してサービスを作成するには、 をサブクラス化 Service して クラス ServiceAttributeを装飾する必要があります。 この属性は、Xamarin.Android ビルド ツールによって使用され、アクティビティと同様にアプリの AndroidManifest.xml ファイルにサービスを適切に登録します。バインドされたサービスには、そのライフサイクル内の重要なイベントに関連付けられた独自のライフサイクルとコールバック メソッドがあります。 次の一覧は、サービスが実装する一般的なコールバック メソッドの一部の例です。

  • 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 アプリケーションでは インターフェイスを IBinder 実装しないでください。を Android.OS.Binder 拡張する必要があります。 クラスは 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 する必要があります。

サービス接続の作成

IServiceConnection will present|introduce|expose|connect the Binder object to the client. インターフェイスの 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呼び出し、バインドされているサービスの とbinder、サービス自体への参照を保持する を提供nameします。 この例では、サービス接続には 2 つのプロパティがあり、1 つは Binder への参照を保持し、クライアントがサービスに接続されている場合は ブールフラグを保持します。

メソッドは OnServiceDisconnected 、クライアントとサービスの間の接続が予期せず失われたり壊れたりした場合にのみ呼び出されます。 このメソッドを使用すると、クライアントはサービスの中断に応答できます。

明示的な意図を使用したサービスの開始とバインド

バインドされたサービスを使用するには、クライアント (Activity など) が メソッドを実装 Android.Content.IServiceConnection して呼び出すオブジェクトをインスタンス化する BindService 必要があります。 BindServiceは、サービスが にバインドされている場合は をfalse返します。バインドされていない場合は が返trueされます。 BindService メソッドは 3 つのパラメーターを受け取ります。

  • An Intent – インテントは、接続先のサービスを明示的に識別する必要があります。
  • An IServiceConnection Object – このオブジェクトは、バインドされたサービスが開始および停止されたときにクライアントに通知するコールバック メソッドを提供する中間オブジェクトです。
  • 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) 以降では、明示的な意図を持つサービスにのみバインドできます。

サービス接続とバインダーに関するアーキテクチャ ノート。

一部のOOPの純粋主義者は、クラスの以前の TimestampBinder 実装を不承認にする可能性があります。これは 、最 も単純な形で「見知らぬ人と話をしないでください。あなたの友人とだけ話す" この特定の実装では、具象 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()