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 はバインドされたサービスを停止して破棄します。

この図は、アクティビティ、サービス接続、バインダー、およびサービスのすべてが相互にどのように関連しているかを示しています。

A diagram showing how the service components relate to each other

このガイドでは、バインドされたサービスを実装するためにクラスを Service 拡張する方法について説明します。 また、クライアントがサービスと通信できるように、実装 IServiceConnection と拡張 Binder についても説明します。 サンプル アプリは、 BoundServiceDemo という 1 つの Xamarin.Android プロジェクトを含むソリューションを含むこのガイドに付属しています。 これは、サービスを実装する方法とアクティビティをバインドする方法を示す非常に基本的なアプリケーションです。 バインドされたサービスには、1 つのメソッドのみを含む非常に単純な API があり、 GetFormattedTimestampサービスが開始されたときと実行されている期間をユーザーに伝える文字列を返します。 また、このアプリを使用すると、ユーザーは手動でサービスのバインドを解除してバインドできます。

Screenshot of the application running on an Android phone

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

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

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

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

クラスを拡張するService

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

  • OnCreate – このメソッドは、サービスのインスタンス化中に Android によって呼び出されます。 これは、サービスの有効期間中にサービスに必要なすべての変数またはオブジェクトを初期化するために使用されます。 このメソッドは省略可能です。
  • OnBind – このメソッドは、すべてのバインドされたサービスによって実装される必要があります。 最初のクライアントがサービスに接続しようとしたときに呼び出されます。 クライアントがサービスと IBinder 対話できるように、インスタンスが返されます。 サービスが実行されている限り、オブジェクトはサービス IBinder にバインドする今後のクライアント要求を満たすために使用されます。
  • OnUnbind – このメソッドは、バインドされているすべてのクライアントがバインドされていない場合に呼び出されます。 このメソッドから戻trueると、サービスは後で、新しいクライアントがバインドされるときに渡されたOnUnbind意図を使用して呼び出されますOnRebind。 これは、サービスがバインド解除された後も実行を継続するときに行います。 これは、最近バインドされていないサービスも開始されたサービスであり、 StopServiceStopSelf び出されなかった場合に発生します。 このようなシナリオでは、 OnRebind 意図を取得できます。 既定値は何も返 false しません。 省略可能。
  • OnDestroy – このメソッドは、Android がサービスを破棄するときに呼び出されます。 リソースの解放など、必要なクリーンアップは、このメソッドで実行する必要があります。 省略可能。

バインドされたサービスの主要なライフサイクル イベントを次の図に示します。

A diagram showing the order in which the lifecycle methods are called

このガイドに付属するコンパニオン アプリケーションの次のコード スニペットは、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.BinderIBinder実装しないでください。拡張する必要があります。 このクラスは 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.IServiceConnectionBindService必要があります。 BindServiceは、サービスがバインドされている場合は返されます。そうでない場合は返falseされますtrueBindService メソッドは 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) 以降では、明示的な意図を持つサービスにのみバインドできます。

サービス接続とバインダーに関するアーキテクチャ上の注意事項。

一部のTimestampBinderOOPの純粋主義者は、それが最も簡単な形で「見知らぬ人と話をしないでください。あなたの友人にのみ話す" この特定の実装では、具象 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()