Advertisement
  1. Code
  2. Android SDK

Współbieżność w Androidzie w ramach usługi

Scroll to top
Read Time: 10 min

Polish (Polski) translation by Mateusz Kurlit (you can also view the original English article)

W tym poradniku omówimy komponent Service i jego superklasę IntentService. Nauczymy się kiedy i jak korzystać z tego komponentu w celu tworzenia rozwiązań współbieżności dla długotrwałych operacji w tle. Rzucimy również okiem na IPC (Inter Process Communication), aby dowiedzieć się jak komunikować się z usługami działającymi w różnych procesach.

Aby wykorzystać w pełni ten poradnik, powinieneś posiadać podstawową wiedzę o współbieżności w Androidzie. Jeśli nie wiesz za wiele na ten temat, zachęcam najpierw do przeczytania naszych artykułów.

1. Komponent usługi

Komponent Service jest bardzo ważną częścią struktury współbieżności w Androidzie. Realizuje konieczność przeprowadzania długotrwałych operacji w aplikacji lub wyposaża w pewną funkcjonalność inne aplikacje. W tym poradniku, skoncentrujemy się wyłącznie na uruchamianiu długotrwałych zadań w komponencie Service i omówimy jak wykorzystać je w udoskonalaniu współbieżności.

Co to jest usługa?

Service to prosty komponent uruchamiany przez system w celu przeprowadzania długotrwałych operacji, które niekoniecznie zależą od samego użytkownika. Może być niezależny od cyklu życia aktywności oraz działać w całkowicie innym procesie.

Zanim przejdziemy do dyskusji o tym, co reprezentuje Service, warto podkreślić, że usługi są powszechnie wykorzystywane w długotrwałych operacjach tła i przeprowadzaniu zadań na różnych procesach, jednak sam Service nie reprezentuje wątku ani procesu. Będzie tylko działać w wątku tła lub w innym procesie, jeśli wyraźnie zostanie to polecone.

Service posiada dwie główne funkcje:

  • Instrument dla aplikacji, który przekazuje systemowi informację, że chce coś robić w tle.
  • Instrument dla aplikacji, który przedstawia pewną funkcjonalność innym aplikacjom.

Usługi i wątki

Istnieje wiele nieporozumień związanych z usługami i wątkami. Po zadeklarowaniu Service, nie zawiera on Thread. W gruncie rzeczy, domyślnie działa bezpośrednio w głównym wątku i dowolna operacja wykonywana w tym wątku potencjalnie może zawiesić aplikację. (Chyba że użyjemy podklasy IntentService, która zawiera skonfigurowany wątek roboczy.)

Jak więc usługi oferują rozwiązanie współbieżności? Domyślnie Service nie zawiera wątku, ale może być z łatwością skonfigurowany do pracy z własnym lub pulą wątków. Za chwilę dowiemy się więcej na ten temat.

Pomijając brak wbudowanego wątku, Service jest doskonałym rozwiązaniem problemów ze współbieżnością oraz pewnych sytuacji. Oto kilka argumentów przemawiających za wyborem Service w porównaniu do innych rozwiązań współbieżności, takich jak AsyncTask lub struktura HaMeR:

  • Service może być niezależny od cyklów życia aktywności.
  • Service jest odpowiedni do przeprowadzania długotrwałych operacji.
  • Usługi nie zależą od działań użytkownika.
  • Podczas działania w różnych procesach, Android może starać się zachować usługi w pamięci, nawet gdy brakuje zasobów w systemie.
  • Service może być uruchomiony ponownie w celu wznowienia swojej pracy.

Typy usług

Istnieją dwa typy Service, uruchomiony i związany.

Context.startService() rozpoczyna pierwszy typ. Generalnie przeprowadza tylko jedną operację, działa do jej zakończenia, następnie sam się wyłącza. Zwykle nie zwraca żadnych wyników interfejsowi użytkownika.

Context.bindService() rozpoczyna drugi typ i umożliwia dwukierunkową komunikację między klientem i Service. Potrafi również łączyć się z wieloma klientami. Wyłącza się, jeśli żaden klient nie jest do niego podłączony.

Aby móc wybierać między tymi dwoma typami, Service musi zaimplementowane wywołania zwrotne: onStartCommand(), aby działać jako uruchomiona usługa oraz onBind() jako związana. Service może podjąć decyzję o wdrożeniu jednego typu, ale potrafi również bezproblemowo zaimplementować oba.

2. Wdrożenie usługi

Aby użyć usługi, rozszerz klasę Service i nadpisz jej metody wywołania zwrotnego według typu Service. Jak wspomniano wcześniej, należy zaimplementować metodę onStartCommand() dla uruchomionej usługi oraz onBind() dla związanej. Tak naprawdę, metoda onBind() musi być zadeklarowana dla obu typów, ale może zwracać wartość zerową uruchomionym usługom.

1
public class CustomService extends Service {
2
    @Override
3
    public int onStartCommand(Intent intent, int flags, int startId) {
4
        // Execute your operations

5
        // Service wont be terminated automatically

6
        return Service.START_NOT_STICKY;
7
    }
8
9
    @Nullable
10
    @Override
11
    public IBinder onBind(Intent intent) {
12
        // Creates a connection with a client

13
        // using a interface implemented on IBinder

14
        return null;
15
    }
16
}
  • onStartCommand(): rozpoczęta przez Context.startService(). Zazwyczaj wywoływana z aktywności. Po wywołaniu, usługa może działać w nieskończoność, da się ją zatrzymać wywołując stopSelf() lub stopService().
  • onBind() pojawia się, kiedy komponent chce połączyć się z usługą. Wywoływana w systemie przez Context.bindService(). Zwraca IBinder, który wyświetla interfejs komunikujący się z klientem.

Warto również wziąć pod uwagę cykl życia usługi. Metody onCreate() i onDestroy() powinny być zaimplementowane w celu inicjowania i wyłączania zasobów lub operacji w ramach usługi.

Deklarowanie usługi w manifeście

Komponent Service musi być zadeklarowany w manifeście z elementem <service>. W deklaracji można, ale nie trzeba ustawiać innego procesu dla uruchamiania Service.

1
<manifest ... >
2
  ...
3
  <application ... >
4
      <service 
5
        android:name=".ExampleService"
6
        android:process=":my_process"/>
7
      ...
8
  </application>
9
</manifest>

2.2. Praca z uruchomionymi usługami

Aby zainicjować uruchomioną usługę, musisz wywołać metodę Context.startService(). Intent należy utworzyć razem z klasami Context i Service. Również wszystkie istotne informacje lub dane powinny zostać przekazane do Intent.

1
Intent serviceIntent = new Intent(this, CustomService.class);
2
// Pass data to be processed on the Service

3
Bundle data = new Bundle();
4
data.putInt("OperationType", 99);
5
data.putString("DownloadURL", "https://mydownloadurl.com");
6
serviceIntent.putExtras(data);
7
// Starting the Service

8
startService(serviceIntent);

W klasie Service, powinniśmy zająć się metodą onStartCommand(). Należy w niej wywołać konkretną operację, którą chcesz wykonać za pomocą uruchomionej usługi. W ten sposób, przetworzysz Intent, aby odebrać informacje wysłane przez klienta. startId reprezentuje unikalne ID tworzone automatycznie dla tego żądania, a flags może zawierać dodatkowe informacje.

1
    @Override
2
    public int onStartCommand(Intent intent, int flags, int startId) {
3
4
        Bundle data = intent.getExtras();
5
        if (data != null) {
6
            int operation = data.getInt(KEY_OPERATION);
7
            // Check what operation to perform and send a msg

8
            if ( operation == OP_DOWNLOAD){
9
                // make a download

10
            }
11
        }
12
13
        return START_STICKY;
14
    }

onStartCommand() zwraca stałą int, która kontroluje zachowanie.

  • Service.START_STICKY: usługa jest uruchamiana ponownie, jeśli zostanie zamknięta.
  • Service.START_NOT_STICKY: usługa nie jest uruchamiana ponownie.
  • Service.START_REDELIVER_INTENT: usługa jest uruchamiana ponownie po awarii, a przetwarzane intencje zostają ponownie dostarczone.

Jak wspomniano wcześniej, uruchomioną usługę należy zatrzymać, inaczej będzie działać w nieskończoność. Wystarczy, że w Service wywoła się stopSelf() lub w kliencie stopService().

1
void someOperation() {
2
        // do some long-running operation

3
        // and stop the service when it is done

4
        stopSelf();
5
    }

Łączenie z usługami

Komponenty mogą tworzyć połączenia z usługami, ustanawiając z nimi dwukierunkową komunikację. Klient musi wywołać Context.bindService(), przekazując do Intent, interfejs ServiceConnection i flags jako patrametry. Service może być związany z wieloma klientami i zostanie wyłączony, gdy żaden z nich nie będzie do niego podłączony.

1
void bindWithService() {
2
        Intent intent = new Intent(this, PlayerService.class);
3
        // bind with Service

4
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
5
    }

Istnieje możliwość przesyłania obiektów Message do usług. Wystarczy utworzyć Messenger po stronie klienta w implementacji interfejsu ServiceConnection.onServiceConnected i użyć go do przesyłania obiektów Message do Service.

1
private ServiceConnection mConnection = new ServiceConnection() {
2
        @Override
3
        public void onServiceConnected(ComponentName className,
4
                                       IBinder service) {
5
            // use the IBinder received to create a Messenger

6
            mServiceMessenger = new Messenger(service);
7
            mBound = true;
8
        }
9
10
        @Override
11
        public void onServiceDisconnected(ComponentName arg0) {
12
            mBound = false;
13
            mServiceMessenger = null;
14
        }
15
    };

Można również przekazać odpowiedź Messenger do Service, aby klient otrzymywał wiadomości. Ale uważaj, klient może być niedostępny, aby otrzymywać wiadomości usługi. Możesz również użyć BroadcastReceiver lub dowolnego rozwiązania dotyczącego powiadomień.

1
    private Handler mResponseHandler = new Handler() {
2
        @Override
3
        public void handleMessage(Message msg) {
4
            // handle response from Service

5
        }
6
    };
7
    Message msgReply = Message.obtain();
8
    msgReply.replyTo = new Messenger(mResponseHandler);
9
    try {
10
        mServiceMessenger.send(msgReply);
11
    } catch (RemoteException e) {
12
        e.printStackTrace();
13
    }

Ważne, aby rozwiązać połączenie z usługą po wyłączeniu klienta.

1
@Override
2
    protected void onDestroy() {
3
        super.onDestroy();
4
        // disconnect from service

5
        if (mBound) {
6
            unbindService(mConnection);
7
            mBound = false;
8
        }
9
    }

Po stronie Service, musisz zaimplementować metodę Service.onBind(), dostarczając IBinder za pośrednictwem Messenger. W ten sposób przekażesz odpowiedź Handler do obsługi obiektów Message otrzymanych od klienta.

1
    IncomingHandler(PlayerService playerService) {
2
            mPlayerService = new WeakReference<>(playerService);
3
        }
4
5
        @Override
6
        public void handleMessage(Message msg) {
7
            // handle messages

8
        }
9
    }
10
    
11
    public IBinder onBind(Intent intent) {
12
        // pass a Binder using the Messenger created

13
        return mMessenger.getBinder();
14
    }
15
    
16
    final Messenger mMessenger = new Messenger(new IncomingHandler(this));

3. Współbieżność w ramach usług

Nadszedł czas, aby omówić rozwiązywanie problemów ze współbieżnością w ramach usług. Jak wspomniano wcześniej, standardowy Service nie zawiera żadnych dodatkowych wątków i domyślnie działa w głównym Thread. Aby pozbyć się tego problemu, musisz dodać roboczy Thread, pulę wątków lub wykonać Service w innym procesie. Możesz również skorzystać z podklasy o nazwie IntentService, która już zawiera Thread.

Uruchamianie usług w wątku roboczym

Aby wykonać Service w Thread tła, wystarczy stworzyć dodatkowy Thread i uruchomić tam zadanie. Jednakże, Android oferuje nam lepsze rozwiązanie. Jednym z najlepszych sposobów na wykorzystanie systemu jest zaimplementowanie struktury HaMeR wewnątrz Service, na przykład zapętlając Thread za pomocą kolejki komunikatów, która może przetwarzać wiadomości w nieskończoność.

Warto zrozumieć, że ta implementacja przetworzy zadania sekwencyjnie. Jeśli chcesz jednocześnie otrzymywać i przetwarzać wiele zadań, powinieneś użyć puli wątków. Kwestia wykorzystania puli wątków nie wchodzi w zakres tego poradnika.

Aby użyć HaMeR musisz dostarczyć Service z Looper, Handler i HandlerThread.

1
    private Looper mServiceLooper;
2
    private ServiceHandler mServiceHandler;
3
    // Handler to receive messages from client

4
    private final class ServiceHandler extends Handler {
5
        ServiceHandler(Looper looper) {
6
            super(looper);
7
        }
8
9
        @Override
10
        public void handleMessage(Message msg) {
11
            super.handleMessage(msg);
12
            // handle messages

13
14
            // stopping Service using startId

15
            stopSelf( msg.arg1 );
16
        }
17
    }
18
    
19
    @Override
20
    public void onCreate() {
21
        HandlerThread thread = new HandlerThread("ServiceThread",
22
                Process.THREAD_PRIORITY_BACKGROUND);
23
        thread.start();
24
25
        mServiceLooper = thread.getLooper();
26
        mServiceHandler = new ServiceHandler(mServiceLooper);
27
28
    } 

Jeśli nie znasz struktury HaMeR, przeczytaj nasze poradniki o HaMeR dla współbieżności w Androidzie.

IntentService

Jeśli nie ma potrzeby, aby Service działał przez dłuższy czas, możesz użyć podklasy IntentService, która potrafi uruchamiać zadania wewnątrz wątków tła. Tak naprawdę, IntentService to Service z implementacją bardzo podobną do zaproponowanej powyżej.

Aby użyć tej klasy, wystarczy ją rozszerzyć i zaimplementować metodę onHandleIntent(), która będzie wywoływana za każdym razem, gdy klient wezwie startService() w Service. Należy zwrócić uwagę, że IntentService zostanie zatrzymana zaraz po zakończeniu zadania.

1
public class MyIntentService extends IntentService {
2
    
3
    public MyIntentService() {
4
        super("MyIntentService");
5
    }
6
7
    @Override
8
    protected void onHandleIntent(Intent intent) {
9
        // handle Intents send by startService

10
    }
11
}

IPC (Inter Process Communication)

Service może działać w całkowicie innym Process, niezależnie od wszystkich zadań znajdujących się w głównym wątku. Proces posiada własny przydział pamięci, grupę wątków oraz priorytety przetwarzania. Takie podejście może być bardzo przydatne, gdy trzeba działać niezależnie od głównego procesu.

Komunikacja między różnymi procesami nosi nazwę IPC (Inter Process Communication). W Service istnieją dwa sposoby na wykonanie IPC: wykorzystując Messenger lub implementując interfejs AIDL.

Nauczyliśmy się jak wysyłać i odbierać wiadomości między usługami. Wszystko co należy zrobić to stworzyć Messenger za pomocą instancji IBinder otrzymaną podczas połączenia i wykorzystać go do przesłania odpowiedzi obiektowi Messenger z powrotem do Service.

1
    private Handler mResponseHandler = new Handler() {
2
        @Override
3
        public void handleMessage(Message msg) {
4
            // handle response from Service

5
        }
6
    };
7
8
    private ServiceConnection mConnection = new ServiceConnection() {
9
        @Override
10
        public void onServiceConnected(ComponentName className,
11
                                       IBinder service) {
12
            // use the IBinder received to create a Messenger

13
            mServiceMessenger = new Messenger(service);
14
15
            Message msgReply = Message.obtain();
16
            msgReply.replyTo = new Messenger(mResponseHandler);
17
            try {
18
                mServiceMessenger.send(msgReply);
19
            } catch (RemoteException e) {
20
                e.printStackTrace();
21
            }
22
        }

Interfejs AIDL jest bardzo rozbudowany oraz umożliwiający bezpośrednie wywoływanie metod Service działających w oddzielnych procesach i warto skorzystać z niego, gdy mamy do czynienia ze złożonym Service. AIDL jest bardzo trudny w implementacji oraz rzadko wykorzystywany, dlatego nie zostanie omówiony w tym poradniku.

4. Podsumowanie

Usługi mogą być proste lub złożone. Wszystko zależy od wymagań danej aplikacji. W tym poradniku, starałem się wyjaśnić jak najwięcej kwestii, jednakże skupiłem się tylko na wykorzystaniu usług w celach współbieżności, ale istnieje więcej możliwości dla tego komponentu. Jeśli chcesz poszerzyć wiedzę na ten temat, przejrzyj dokumentację i poradniki Androida.

Do zobaczenia wkrótce!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.