Vázané služby v Xamarin.Androidu

Vázané služby jsou služby Androidu, které poskytují rozhraní klienta-server, se kterým může klient (například aktivita Androidu) pracovat. Tato příručka popisuje klíčové komponenty spojené s vytvářením vázané služby a jejím použitím v aplikaci Xamarin.Android.

Přehled služby Bound Services

Služby, které poskytují rozhraní klienta-server pro klienty, aby přímo komunikovali se službou, se označují jako vázané služby. K jedné instanci služby může být současně připojeno více klientů. Svázaná služba a klient jsou navzájem izolované. Android místo toho poskytuje řadu zprostředkujících objektů, které spravují stav připojení mezi těmito dvěma objekty. Tento stav je udržován objektem, který implementuje Android.Content.IServiceConnection rozhraní. Tento objekt je vytvořen klientem a předán jako parametr metodě BindService . Je BindService k dispozici u libovolného Android.Content.Context objektu (například aktivity). Jedná se o požadavek na spuštění služby v operačním systému Android a vytvoření vazby klienta k němu. Klient může vytvořit vazbu ke službě pomocí BindService této metody třemi způsoby:

  • Pořadač služby – pořadač služby je třída, která implementuje Android.OS.IBinder rozhraní. Většina aplikací nebude toto rozhraní implementovat přímo, místo toho rozšíří Android.OS.Binder třídu. Jedná se o nejběžnější přístup a je vhodný pro případy, kdy služba a klient existují ve stejném procesu.
  • Použití messengeru – Tato technika je vhodná pro případy, kdy může služba existovat v samostatném procesu. Místo toho se žádosti o služby zařaďují mezi klientem a službou prostřednictvím příkazu Android.OS.Messenger. Vytvoří Android.OS.Handler se ve službě, která bude zpracovávat Messenger požadavky. To se bude probít v jiné příručce.
  • Použití jazyka AIDL (Android Interface Definition Language)AIDL je pokročilá technika, která se v této příručce nezabývá.

Jakmile je klient vázán na službu, komunikace mezi těmito dvěma objekty probíhá prostřednictvím Android.OS.IBinder objektu. Tento objekt je zodpovědný za rozhraní, které klientovi umožní interakci se službou. Pro každou aplikaci Xamarin.Android není nutné implementovat toto rozhraní úplně od začátku, Sada Android SDK poskytuje Android.OS.Binder třídu, která se postará o většinu kódu potřebného při zařazování objektu mezi klientem a službou.

Když je klient se službou hotový, musí se od něj zrušit vazby voláním UnbindService metody. Jakmile bude poslední klient ze služby nevázaný, Android zastaví a odstraní vázanou službu.

Tento diagram znázorňuje, jak mezi sebou souvisí aktivita, připojení služby, pořadač a služba:

A diagram showing how the service components relate to each other

Tento průvodce probere, jak rozšířit Service třídu, aby implementovaly vázanou službu. Bude se také zabývat implementací IServiceConnection a rozšířením Binder , aby klient mohl komunikovat se službou. Ukázková aplikace doprovází tuto příručku, která obsahuje řešení s jedním projektem Xamarin.Android s názvem BoundServiceDemo . Toto je velmi základní aplikace, která ukazuje, jak implementovat službu a jak s ní svázat aktivitu. Vázaná služba má velmi jednoduché rozhraní API pouze s jednou metodou, která vrací řetězec, který uživateli řekne, GetFormattedTimestampkdy byla služba spuštěna a jak dlouho byla spuštěna. Aplikace také umožňuje uživateli ručně zrušit vazbu a vytvořit vazbu ke službě.

Screenshot of the application running on an Android phone

Implementace a využívání vázané služby

Aby aplikace pro Android mohla využívat vázanou službu, musí být implementovány tři komponenty:

  1. Service Rozšiřte třídu a implementujte metody zpětného volání životního cyklu – tato třída bude obsahovat kód, který bude provádět práci, která bude vyžadovat službu. Toto se podrobněji probírá níže.
  2. Vytvořte třídu, která implementuje IServiceConnection – toto rozhraní poskytuje metody zpětného volání, které android vyvolá, aby informoval klienta, když se změnilo připojení ke službě, tj. klient se připojil nebo odpojil ke službě. Připojení služby také poskytne odkaz na objekt, který může klient použít k přímé interakci se službou. Tento odkaz se označuje jako pořadač.
  3. Vytvořte třídu, která implementuje IBinderimplementace Binderu poskytuje rozhraní API, které klient používá ke komunikaci se službou. Binder může buď poskytnout odkaz na vázanou službu, což umožňuje přímé vyvolání metod, nebo Binder může poskytnout klientské rozhraní API, které zapouzdřuje a skryje vázanou službu z aplikace. Musí IBinder obsahovat potřebný kód pro vzdálená volání procedur. Není nutné (nebo doporučeno) implementovat IBinder rozhraní přímo. Místo toho by aplikace měly rozšířit Binder typ, který poskytuje většinu základních funkcí vyžadovaných funkcí IBinder.
  4. Spuštění a vazba na službu – po vytvoření připojení, pořadače a služby pro Android je aplikace pro Android zodpovědná za spuštění služby a vytvoření vazby na ni.

Každý z těchto kroků bude podrobněji popsán v následujících částech.

Service Rozšíření třídy

Chcete-li vytvořit službu pomocí Xamarin.Android, je nutné podtřídu Service a doplnit třídu pomocí ServiceAttributetřídy . Atribut používá nástroje sestavení Xamarin.Android k správné registraci služby v souboru AndroidManifest.xml aplikace podobně jako aktivita, vázaná služba má vlastní životní cyklus a metody zpětného volání spojené s významnými událostmi v životním cyklu aplikace. Následující seznam je příkladem některých běžnějších metod zpětného volání, které služba implementuje:

  • OnCreate – Tato metoda je vyvolána Androidem při vytváření instance služby. Slouží k inicializaci všech proměnných nebo objektů, které služba během životnosti vyžaduje. Tato metoda je nepovinná.
  • OnBind – Tuto metodu musí implementovat všechny vázané služby. Vyvolá se, když se první klient pokusí připojit ke službě. Vrátí instanci IBinder tak, aby klient mohl se službou pracovat. Pokud je služba spuštěná, IBinder objekt se použije ke splnění jakýchkoli budoucích požadavků klienta na vazbu ke službě.
  • OnUnbind – Tato metoda je volána, když všichni vázané klienty mají nevázaný. true Vrácením z této metody bude služba později volat OnRebind se záměrem předaným OnUnbind při vytvoření vazby nových klientů. To byste udělali, když služba bude dál spuštěná, jakmile bude nevázaná. K tomu může dojít, pokud nedávno nevázaná služba byla také spuštěná služba nebo StopServiceStopSelf nebyla volána. V takovém scénáři OnRebind umožňuje načtení záměru. Výchozí hodnota vrátí false , což nic nedělá. Nepovinné.
  • OnDestroy – Tato metoda se volá, když Android zničí službu. V této metodě by se mělo provést jakékoli nezbytné vyčištění, například uvolnění prostředků. Nepovinné.

Klíčové události životního cyklu vázané služby jsou znázorněny v tomto diagramu:

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

Následující fragment kódu z doprovodné aplikace, která doprovází tuto příručku, ukazuje, jak implementovat vázanou službu v 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();
        }
    }
}

V ukázce metoda inicializuje objekt, OnCreate který obsahuje logiku pro načtení a formátování časového razítka, které by bylo požadováno klientem. Když se první klient pokusí vytvořit vazbu ke službě, Android vyvolá metodu OnBind . Tato služba vytvoří instanci objektu TimestampBinder , který klientům umožní přístup k této instanci spuštěné služby. Třída TimestampBinder je popsána v další části.

Implementace IBinderu

Jak už bylo zmíněno, IBinder objekt poskytuje komunikační kanál mezi klientem a službou. Aplikace pro Android by neměly implementovat IBinder rozhraní, Android.OS.Binder mělo by být rozšířeno. Třída Binder poskytuje velkou část nezbytné infrastruktury, která je nezbytná zařazování objektu pořadače ze služby (která může být spuštěna v samostatném procesu) klientovi. Ve většině případů Binder je podtřídou pouze několik řádků kódu a zabalí odkaz na službu. V tomto příkladu má vlastnost, TimestampBinder která zveřejňuje TimestampService klienta:

public class TimestampBinder : Binder
{
    public TimestampBinder(TimestampService service)
    {
        this.Service = service;
    }

    public TimestampService Service { get; private set; }
}

To Binder umožňuje vyvolat veřejné metody ve službě, například:

string currentTimestamp = serviceConnection.Binder.Service.GetFormattedTimestamp()

Po Binder rozšíření je nutné implementovat IServiceConnection propojení všeho dohromady.

Vytvoření Připojení služby

Zobrazí se|představení|expose|expose|connect the Binder object to the client.|IServiceConnectionthe will present|introduce|expose|connect the object to the client. Kromě implementace IServiceConnection rozhraní musí třída rozšířit Java.Lang.Object. Připojení služby by také mělo poskytnout nějaký způsob, jak může klient přistupovat k Binder (a proto komunikovat s vázanou službou).

Tento kód pochází z doprovodného ukázkového projektu je jedním z možných způsobů implementace IServiceConnection:

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();
        }
    }
}

Jako součást procesu vazby android vyvolá OnServiceConnected metodu, která poskytuje name službu, která je vázána a binder která obsahuje odkaz na samotnou službu. V tomto příkladu má připojení služby dvě vlastnosti, jednu, která obsahuje odkaz na Binder a logický příznak pro případ, že je klient připojen ke službě nebo ne.

Metoda OnServiceDisconnected je vyvolána pouze v případech, kdy je připojení mezi klientem a službou neočekávaně ztraceno nebo přerušeno. Tato metoda umožňuje klientovi reagovat na přerušení služby.

Spuštění a vazba na službu s explicitním záměrem

Aby bylo možné použít vázanou službu, klient (například aktivita) musí vytvořit instanci objektu, který implementuje Android.Content.IServiceConnection a vyvolá metodu BindService . BindService vrátí true , pokud je služba vázána, false pokud není. Metoda BindService přebírá tři parametry:

  • An Intent – Záměr by měl explicitně identifikovat službu, ke které se má připojit.
  • Objekt IServiceConnection – Tento objekt je zprostředkovatel, který poskytuje metody zpětného volání, které klientovi oznámí, když je vázaná služba spuštěna a zastavena.
  • Android.Content.Bind enum – Tento parametr je sada příznaků, které systém používá k vytvoření vazby objektu. Nejčastěji používanou hodnotou je Bind.AutoCreate, která automaticky spustí službu, pokud ještě není spuštěná.

Následující fragment kódu je příkladem spuštění vázané služby v aktivitě pomocí explicitního záměru:

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);

}

Důležité

Od Verze Android 5.0 (úroveň rozhraní API 21) je možné svázat se službou pouze s explicitním záměrem.

Architektonické poznámky ke službě Připojení ion a Binderu

Někteří puristé OOP mohou nesouhlasit s předchozí implementací TimestampBinder třídy, protože jde o porušení zákona Demeter , který v nejjednodušší podobě uvádí :Nemluvte s cizími lidmi; jen mluvit se svými přáteli". Tato konkrétní implementace zveřejňuje konkrétní TimestampService třídu všem klientům.

Přesněji řečeno, není nutné, aby klient věděl o TimestampService konkrétní třídě a vystavil, že konkrétní třída klientům může znesnadnit a znesnadnit zachování aplikace v průběhu jeho životnosti. Alternativním přístupem je použití rozhraní, které metodu GetFormattedTimestamp() zpřístupňuje, a volání proxy serveru službě Binder prostřednictvím třídy připojení služby (nebo možné):

public class TimestampBinder : Binder, IGetTimestamp
{
    TimestampService service;
    public TimestampBinder(TimestampService service)
    {
        this.service = service;
    }

    public string GetFormattedTimestamp()
    {
        return service?.GetFormattedTimestamp();
    }
}

Tento konkrétní příklad umožňuje aktivitě vyvolat metody samotné služby:

// 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()