Omówienie powiązanych usług

Usługa powiązana to serwer w interfejsie klient-serwer. Dzięki niemu komponenty takie jak działania mogą wiązać się z usługą, wysyłać żądania, odbierać odpowiedzi i wykonywać komunikację międzyprocesową (IPC). Powiązana usługa działa zwykle tylko wtedy, gdy obsługuje inny komponent aplikacji, i nie działa w tle przez cały czas.

W tym dokumencie opisano, jak utworzyć powiązaną usługę, w tym jak utworzyć powiązanie z tą usługą z innych komponentów aplikacji. Więcej ogólnych informacji o usługach, takich jak sposób dostarczania powiadomień z usługi i ustawiania jej działania na pierwszym planie, znajdziesz w omówieniu usług.

Podstawy

Usługa powiązana to implementacja klasy Service, która umożliwia innym aplikacjom powiązanie z nią i interakcje z nią. Aby udostępnić powiązanie dla usługi, implementujesz metodę wywołania zwrotnego onBind(). Ta metoda zwraca obiekt IBinder określający interfejs programowania, za pomocą którego klienty mogą korzystać z usługi.

Powiązanie z uruchomioną usługą

Jak wspomnieliśmy w opisie usług, możesz utworzyć usługę, która jest zarówno uruchomiona, jak i powiązana. Oznacza to, że możesz uruchomić usługę, wywołując startService(), co umożliwia jej działanie bezterminowo. Możesz też pozwolić klientowi na powiązanie z usługą, wywołując bindService().

Jeśli zezwolisz na uruchomienie i powiązanie usługi, to po uruchomieniu usługi system nie zniszczy jej po usunięciu powiązania wszystkich klientów. Zamiast tego musisz wyraźnie zatrzymać usługę, wywołując stopSelf() lub stopService().

Chociaż zwykle implementujesz onBind() lub onStartCommand(), czasami konieczne jest zaimplementowanie obu. Na przykład odtwarzacz muzyki może chcieć, aby jego usługa działała w nieskończoność i mogła też określić powiązania. W ten sposób aktywność może uruchomić usługę i odtworzyć muzykę, która będzie odtwarzana nawet wtedy, gdy użytkownik opuści aplikację. Następnie, gdy użytkownik wróci do aplikacji, aktywność może zostać powiązana z usługą, aby odzyskać kontrolę nad odtwarzaniem.

Więcej informacji o cyklu życia usługi podczas dodawania powiązania do uruchomionej usługi znajdziesz w sekcji Zarządzanie cyklem życia powiązanej usługi.

Klient łączy się z usługą, wywołując bindService(). W takich przypadkach musi udostępniać implementację ServiceConnection, która monitoruje połączenie z usługą. Zwracana wartość bindService() wskazuje, czy żądana usługa istnieje i czy klient ma do niej dostęp.

Gdy system Android tworzy połączenie między klientem a usługą, wywołuje onServiceConnected() na ServiceConnection. Metoda onServiceConnected() zawiera argument IBinder, którego klient używa następnie do komunikowania się z powiązaną usługą.

Z usługą możesz połączyć wielu klientów jednocześnie. System zapisuje jednak w pamięci podręcznej kanał komunikacji usługi IBinder. Inaczej mówiąc, system wywołuje metodę onBind() usługi, aby wygenerować IBinder tylko po powiązaniu pierwszego klienta. System będzie następnie dostarczać ten sam IBinder do wszystkich dodatkowych klientów, które łączą się z tą samą usługą, bez ponownego wywoływania metody onBind().

Gdy ostatni klient usunie powiązanie z usługą, system niszczy usługę, chyba że została uruchomiona przy użyciu startService().

Najważniejszym elementem implementacji powiązanej usługi jest zdefiniowanie interfejsu zwracanego przez metodę wywołania zwrotnego onBind(). W tej sekcji opisujemy kilka sposobów definiowania interfejsu IBinder usługi.

Utwórz powiązaną usługę

Gdy tworzysz usługę, która udostępnia wiązanie, musisz udostępnić interfejs IBinder udostępniający interfejs programowania, za pomocą którego klienty mogą wchodzić w interakcje z tą usługą. Interfejs można definiować na 3 sposoby:

Rozszerz klasę Binder
Jeśli Twoja usługa jest prywatna dla Twojej aplikacji i działa w tym samym procesie co klient, co jest powszechne, utwórz interfejs, rozszerzając klasę Binder i zwracając jej instancję z onBind(). Klient otrzymuje Binder i może go użyć, aby uzyskać bezpośredni dostęp do metod publicznych dostępnych w implementacji Binder lub Service.

Jest to preferowana technika, gdy Twoja usługa jest tylko elementem roboczym Twojej aplikacji. Jedynym przypadkiem użycia, w którym nie jest preferowany sposób tworzenia interfejsu, jest sytuacja, gdy usługa jest używana przez inne aplikacje lub różne procesy.

Korzystanie z komunikatora
Jeśli chcesz, aby Twój interfejs działał w różnych procesach, możesz utworzyć interfejs usługi za pomocą Messenger. W ten sposób usługa definiuje element Handler, który odpowiada na różne typy obiektów Message.

Handler jest podstawą obiektu Messenger, który może następnie współużytkować IBinder z klientem, umożliwiając mu wysyłanie poleceń do usługi za pomocą obiektów Message. Dodatkowo klient może zdefiniować własny Messenger, aby usługa mogła wysyłać wiadomości z powrotem.

Jest to najprostszy sposób komunikacji międzyprocesowej (IPC), ponieważ Messenger umieszcza wszystkie żądania w jednym wątku, więc nie musisz projektować usługi w taki sposób, aby była bezpieczna dla wątków.

Użyj AIDL
Android Interface Definition Language (AIDL) rozkłada obiekty na podstawowe, które system operacyjny może zrozumieć, i łączy je w różne procesy na potrzeby IPC. Poprzednia metoda, wykorzystująca Messenger, w rzeczywistości opiera się na AIDL.

Jak wspomnieliśmy w poprzedniej sekcji, Messenger tworzy kolejkę wszystkich żądań klienta w 1 wątku, więc usługa odbiera żądania pojedynczo. Jeśli jednak chcesz, aby Twoja usługa jednocześnie obsługiwała wiele żądań, możesz użyć AIDL bezpośrednio. W takim przypadku usługa musi być przystosowana do obsługi wątków i umożliwiać wielowątkowość.

Aby bezpośrednio korzystać z AIDL, utwórz plik .aidl definiujący interfejs programowania. Narzędzia pakietu Android SDK używają tego pliku do wygenerowania klasy abstrakcyjnej, która implementuje interfejs i obsługuje protokół IPC, którą możesz potem rozszerzyć w swojej usłudze.

Uwaga: w przypadku większości aplikacji AIDL nie jest najlepszym rozwiązaniem do utworzenia usługi powiązanej, ponieważ może wymagać funkcji wielowątkowości i prowadzić do bardziej skomplikowanej implementacji. Dlatego w tym dokumencie nie omawiamy sposobu ich używania w Twojej usłudze. Jeśli masz pewność, że musisz bezpośrednio korzystać z AIDL, zapoznaj się z dokumentem AIDL.

Rozszerz klasę Binder

Jeśli tylko aplikacja lokalna korzysta z Twojej usługi i nie musi pracować między procesami, możesz wdrożyć własną klasę Binder, która zapewni klientowi bezpośredni dostęp do metod publicznych w usłudze.

Uwaga: działa to tylko wtedy, gdy klient i usługa znajdują się w tej samej aplikacji i procesie, co jest najczęstsze. Takie rozwiązanie sprawdza się np. w przypadku aplikacji muzycznej, która musi powiązać działanie z własną usługą, która odtwarza muzykę w tle.

Ustawienia należy skonfigurować w następujący sposób:

  1. W swojej usłudze utwórz instancję Binder, która wykona jedną z tych czynności:
    • Zawiera metody publiczne, które może wywoływać klient.
    • Zwraca bieżącą instancję Service, która zawiera metody publiczne, które może wywoływać.
    • Zwraca instancję innej klasy hostowanej przez usługę z publicznymi metodami, które może wywołać klient.
  2. Zwróć to wystąpienie Binder z metody wywołania zwrotnego onBind().
  3. W kliencie odbierz atrybut Binder z metody wywołania zwrotnego onServiceConnected() i wykonaj wywołania powiązanej usługi, korzystając z podanych metod.

Uwaga: usługa i klient muszą znajdować się w tej samej aplikacji, aby klient mógł rzutować zwrócony obiekt i prawidłowo wywoływać jego interfejsy API. Usługa i klient również muszą być w tym samym procesie, ponieważ ta technika nie łączy żadnych procesów.

Oto przykład usługi zapewniającej klientom dostęp do metod w tej usłudze za pomocą implementacji Binder:

Kotlin

class LocalService : Service() {
    // Binder given to clients.
    private val binder = LocalBinder()

    // Random number generator.
    private val mGenerator = Random()

    /** Method for clients.  */
    val randomNumber: Int
        get() = mGenerator.nextInt(100)

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    inner class LocalBinder : Binder() {
        // Return this instance of LocalService so clients can call public methods.
        fun getService(): LocalService = this@LocalService
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

Java

public class LocalService extends Service {
    // Binder given to clients.
    private final IBinder binder = new LocalBinder();
    // Random number generator.
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods.
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    /** Method for clients. */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

LocalBinder udostępnia klientom metodę getService(), która może pobrać bieżącą instancję LocalService. Dzięki temu klienci mogą wywoływać w usłudze metody publiczne. Na przykład klienci mogą wywołać getRandomNumber() z usługi.

Oto działanie, które wiąże się z elementem LocalService i wywołuje po kliknięciu przycisku getRandomNumber():

Kotlin

class BindingActivity : Activity() {
    private lateinit var mService: LocalService
    private var mBound: Boolean = false

    /** Defines callbacks for service binding, passed to bindService().  */
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance.
            val binder = service as LocalService.LocalBinder
            mService = binder.getService()
            mBound = true
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
            mBound = false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }

    override fun onStart() {
        super.onStart()
        // Bind to LocalService.
        Intent(this, LocalService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
        mBound = false
    }

    /** Called when a button is clicked (the button in the layout file attaches to
     * this method with the android:onClick attribute).  */
    fun onButtonClick(v: View) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call is something that might hang, then put this request
            // in a separate thread to avoid slowing down the activity performance.
            val num: Int = mService.randomNumber
            Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
        }
    }
}

Java

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService.
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
        mBound = false;
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute). */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call is something that might hang, then put this request
            // in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService(). */
    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance.
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

Poprzedni przykład pokazuje, jak klient łączy się z usługą za pomocą implementacji ServiceConnection i wywołania zwrotnego onServiceConnected(). Więcej informacji o procesie łączenia z usługą znajdziesz w następnej sekcji.

Uwaga: w poprzednim przykładzie metoda onStop() usuwa powiązanie klienta z usługą. Usuń powiązanie klientów z usługami w odpowiednich momentach, zgodnie z opisem w sekcji Dodatkowe uwagi.

Więcej przykładowego kodu znajdziesz w klasach LocalService.java i LocalServiceActivities.java w ApiDemos.

Korzystaj z komunikatora

Jeśli chcesz, aby Twoja usługa komunikowała się z procesami zdalnymi, możesz udostępnić jej interfejs za pomocą Messenger. Ta technika umożliwia przeprowadzanie komunikacji międzyprocesowej (IPC) bez konieczności korzystania z AIDL.

Użycie interfejsu Messenger jest łatwiejsze niż używanie AIDL, ponieważ Messenger umieszcza wszystkie wywołania usługi w kolejce. Interfejs czysty AIDL wysyła jednoczesne żądania do usługi, która musi obsługiwać wielowątkowość.

W przypadku większości aplikacji usługa nie musi obsługiwać wielowątkowości, więc użycie interfejsu Messenger umożliwia usłudze obsługę pojedynczych wywołań. Jeśli Twoja usługa ma być wielowątkowa, zdefiniuj interfejs za pomocą AIDL.

Oto jak należy korzystać z elementu Messenger:

  1. Usługa implementuje element Handler, który otrzymuje wywołanie zwrotne o każdym wywołaniu od klienta.
  2. Usługa używa Handler do tworzenia obiektu Messenger (który jest odwołaniem do Handler).
  3. Messenger tworzy IBinder, który usługa zwraca klientom z onBind().
  4. Klienty używają IBinder do tworzenia instancji Messenger (która odwołuje się do Handler usługi), której używa do wysyłania obiektów Message do usługi.
  5. Usługa otrzymuje każdy element Message w elemencie Handler, a konkretnie w metodzie handleMessage().

Dzięki temu nie ma metod, które klient mógłby wywołać w usłudze. Zamiast tego klient dostarcza wiadomości (obiekty Message), które usługa odbiera w swoim pakiecie Handler.

Oto prosta przykładowa usługa, która korzysta z interfejsu Messenger:

Kotlin

/** Command to the service to display a message.  */
private const val MSG_SAY_HELLO = 1

class MessengerService : Service() {

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    private lateinit var mMessenger: Messenger

    /**
     * Handler of incoming messages from clients.
     */
    internal class IncomingHandler(
            context: Context,
            private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO ->
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                else -> super.handleMessage(msg)
            }
        }
    }

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

Java

public class MessengerService extends Service {
    /**
     * Command to the service to display a message.
     */
    static final int MSG_SAY_HELLO = 1;

    /**
     * Handler of incoming messages from clients.
     */
    static class IncomingHandler extends Handler {
        private Context applicationContext;

        IncomingHandler(Context context) {
            applicationContext = context.getApplicationContext();
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    Messenger mMessenger;

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        mMessenger = new Messenger(new IncomingHandler(this));
        return mMessenger.getBinder();
    }
}

W metodzie handleMessage() w Handler usługa otrzymuje przychodzące Message i decyduje, co zrobić, na podstawie elementu what.

Klient musi tylko utworzyć Messenger na podstawie wartości IBinder zwróconej przez usługę i wysłać wiadomość za pomocą send(). Oto przykład działania, które wiąże się z usługą i dostarcza do niej komunikat MSG_SAY_HELLO:

Kotlin

class ActivityMessenger : Activity() {
    /** Messenger for communicating with the service.  */
    private var mService: Messenger? = null

    /** Flag indicating whether we have called bind on the service.  */
    private var bound: 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 has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected—that is, its process crashed.
            mService = null
            bound = false
        }
    }

    fun sayHello(v: View) {
        if (!bound) return
        // Create and send a message to the service, using a supported 'what' value.
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }

    override fun onStart() {
        super.onStart()
        // Bind to the service.
        Intent(this, MessengerService::class.java).also { intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // Unbind from the service.
        if (bound) {
            unbindService(mConnection)
            bound = false
        }
    }
}

Java

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */
    boolean bound;

    /**
     * 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 has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = new Messenger(service);
            bound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected—that is, its process crashed.
            mService = null;
            bound = false;
        }
    };

    public void sayHello(View v) {
        if (!bound) return;
        // Create and send a message to the service, using a supported 'what' value.
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to the service.
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service.
        if (bound) {
            unbindService(mConnection);
            bound = false;
        }
    }
}

Ten przykład nie pokazuje, jak usługa może odpowiedzieć klientowi. Jeśli chcesz, by usługa odpowiadała, musisz też utworzyć w kliencie Messenger. Gdy klient otrzyma wywołanie zwrotne onServiceConnected(), wysyła do usługi parametr Message, który zawiera Messenger klienta w parametrze replyTo metody send().

Przykład obsługi dwukierunkowej komunikacji znajdziesz w przykładach MessengerService.java (usługa) i MessengerServiceActivities.java (klient).

Powiązanie z usługą

Komponenty aplikacji (klienty) mogą powiązać się z usługą, wywołując bindService(). System Android wywołuje następnie metodę onBind() usługi, która zwraca IBinder w celu interakcji z usługą.

Powiązanie jest asynchroniczne, a bindService() zwraca natychmiast bez zwracania klientowi IBinder. Aby otrzymać IBinder, klient musi utworzyć instancję ServiceConnection i przekazać ją do bindService(). Element ServiceConnection zawiera metodę wywołania zwrotnego, którą system wywołuje, aby przesłać IBinder.

Uwaga: powiązanie z usługą można powiązać tylko z działaniami, usługami i dostawcami treści – nie można powiązać ich z usługą od odbiornika.

Aby utworzyć powiązanie z usługą z poziomu klienta, wykonaj te czynności:

  1. Wdróż ServiceConnection.

    Twoja implementacja musi zastąpić 2 metody wywołania zwrotnego:

    onServiceConnected()
    System wywołuje to działanie, aby dostarczyć element IBinder zwrócony przez metodę onBind() usługi.
    onServiceDisconnected()
    System Android wywołuje tę czynność, gdy połączenie z usługą zostanie nieoczekiwanie utracone, na przykład gdy usługa ulegnie awarii lub zostanie przerwana. Ta czynność nie jest wywoływana, gdy klient usuwa powiązanie.
  2. Wywołaj bindService(), przekazując implementację ServiceConnection.

    Uwaga: jeśli metoda zwraca wartość false (fałsz), oznacza to, że klient nie ma prawidłowego połączenia z usługą. Wywołaj jednak unbindService() w kliencie. W przeciwnym razie klient zapobiega zamykaniu usługi w przypadku bezczynności.

  3. Gdy system wywoła metodę wywołania zwrotnego onServiceConnected(), możesz rozpocząć wykonywanie wywołań usługi, korzystając z metod zdefiniowanych w interfejsie.
  4. Aby odłączyć się od usługi, zadzwoń pod numer unbindService().

    Jeśli klient jest nadal powiązany z usługą w momencie jego zniszczenia przez aplikację, zniszczenie go powoduje usunięcie powiązania. Lepiej jest usuwać powiązanie klienta natychmiast po zakończeniu interakcji z usługą. Spowoduje to wyłączenie nieaktywnej usługi. Więcej informacji o odpowiednich momentach tworzenia powiązania znajdziesz w sekcji Dodatkowe uwagi.

Poniższy przykład łączy klienta z usługą utworzoną wcześniej przez rozszerzenie klasy Binder. Wystarczy więc rzutować zwrócony IBinder na klasę LocalBinder i zażądać instancji LocalService:

Kotlin

var mService: LocalService

val mConnection = object : ServiceConnection {
    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        val binder = service as LocalService.LocalBinder
        mService = binder.getService()
        mBound = true
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "onServiceDisconnected")
        mBound = false
    }
}

Java

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

Za pomocą tego kodu ServiceConnection klient może powiązać się z usługą, przekazując ją do interfejsu bindService(), jak w tym przykładzie:

Kotlin

Intent(this, LocalService::class.java).also { intent ->
    bindService(intent, connection, Context.BIND_AUTO_CREATE)
}

Java

Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
  • Pierwszym parametrem bindService() jest Intent jawnie nazwa usługi, która ma zostać powiązana.

    Uwaga: jeśli chcesz utworzyć powiązanie z obiektem Service, upewnij się, że aplikacja jest bezpieczna, używając intencji wyraźnej. Korzystanie z niejawnej intencji uruchomienia usługi stanowi zagrożenie dla bezpieczeństwa, ponieważ nie można mieć pewności, która usługa odpowiada na intencję, a użytkownik nie może zobaczyć, która usługa się uruchamia. Począwszy od Androida 5.0 (poziom interfejsu API 21) system zgłasza wyjątek, jeśli wywołujesz bindService() z ukrytą intencją.

  • Drugi parametr to obiekt ServiceConnection.
  • Trzeci parametr to flaga wskazująca opcje wiązania – zwykle BIND_AUTO_CREATE – do utworzenia usługi, jeśli jeszcze nie jest aktywna. Inne możliwe wartości to BIND_DEBUG_UNBIND, BIND_NOT_FOREGROUND lub 0 (brak).

Uwagi dodatkowe

Oto kilka ważnych uwag na temat powiązania z usługą:

  • Zawsze blokujej wyjątki DeadObjectException, które są wysyłane po zerwaniu połączenia. Jest to jedyny wyjątek zgłaszany przez metody zdalne.
  • Obiekty są zliczane w procesach.
  • Wiązanie i usuwanie powiązania jest zwykle łączone w czasie wyświetlania i rozciągania w cyklu życia klienta, jak opisano w tych przykładach:
    • Jeśli chcesz korzystać z usługi tylko wtedy, gdy Twoja aktywność jest widoczna, utwórz powiązanie w czasie onStart(), a usuń powiązanie podczas onStop().
    • Jeśli chcesz, aby Twoja aktywność otrzymywała odpowiedzi nawet wtedy, gdy jest zatrzymana w tle, utwórz powiązanie w trakcie onCreate() i usuń powiązanie podczas onDestroy(). Pamiętaj, że oznacza to, że usługa musi korzystać z usługi przez cały czas jej działania, nawet w tle, więc gdy usługa jest w innym procesie, zwiększasz wagę tego procesu, co zwiększa prawdopodobieństwo jego zakończenia przez system.

    Uwaga: nie wiąże się zwykle z powiązaniami ani usuwaniem podczas wywołań zwrotnych onResume() i onPause() aktywności, ponieważ te wywołania zwrotne mają miejsce w każdym kroku cyklu życia. Ogranicz przetwarzanie do minimum.

    Poza tym, jeśli wiele działań w aplikacji jest powiązanych z tą samą usługą i nastąpi przejście między 2 z nich, usługa może zostać zniszczona i odtworzona jako bieżąca aktywność usuwa powiązanie (w trakcie wstrzymania) przed kolejnym wiązaniem (podczas wznowienia). Ta zmiana dotyczy sposobu, w jaki działania koordynują swoje cykle życia, opisaną w artykule Cykl życia aktywności.

Więcej przykładowego kodu pokazujący, jak utworzyć powiązanie z usługą, znajdziesz w klasie RemoteService.java w ApiDemos.

Zarządzanie cyklem życia powiązanej usługi

Gdy usługa jest niepowiązana ze wszystkimi klientami, system Android ją niszczy (chyba że zaczął korzystać z startService()). Nie musisz więc zarządzać cyklem życia usługi, jeśli jest ona czysto powiązana. System Android zarządza nim za Ciebie w zależności od tego, czy dotyczy on klientów.

Jeśli jednak zdecydujesz się na wdrożenie metody wywołania zwrotnego onStartCommand(), musisz samodzielnie zatrzymać usługę, ponieważ usługa jest teraz uznawana za rozpoczętą. W tym przypadku usługa działa, dopóki nie zatrzyma się samodzielnie przy użyciu stopSelf() lub innego komponentu wywołała element stopService(), niezależnie od tego, czy jest powiązana z jakimiś klientami.

Dodatkowo, jeśli usługa jest uruchomiona i akceptuje wiązanie, to gdy system wywoła metodę onUnbind(), możesz opcjonalnie zwrócić metodę true, jeśli chcesz odebrać wywołanie onRebind(), gdy klient następnym razem powiąże się z usługą. onRebind() zwraca wartość nieważną, ale klient nadal otrzymuje wartość IBinder w wywołaniu zwrotnym onServiceConnected(). Na rysunku poniżej przedstawiono logikę tego rodzaju cyklu życia.

Rysunek 1. Cykl życia usługi, która została uruchomiona i umożliwia też powiązanie.

Więcej informacji o cyklu życia uruchomionej usługi znajdziesz w artykule Omówienie usług.