Services

Wstęp

Serwis jest komponentem aplikacji który:

  • wykonuje długo trwające operacje (np: update do bazy danych, pobieranie pliku z sieci)

  • nie zapewnia UI (user interface)

Serwisy są uruchamiane przez inne komponenty np: Activity - użytkownik playera naciska "play", uruchamiamy serwis, tak uruchomiony komponent będzie działał nawet gdy użytkownik przełączy się do innej aplikacji. Dodatkowo nasz serwis może zostać "związany" z komponentem który go uruchomił, w takim przypadku możemy wchodzić w interakcje z naszym serwisem czyli wykonywać metody które się w nim znajdują ( możemy przewinąć odpaloną wcześniej piosenkę).

Dla jasności, mamy dwa typy Serwisów:

  • Rozpoczęty("Started ") - serwis uruchomiony za pomocą metody**startService().**Tak uruchomiony serwis będzie działał niezależnie w tle, nawet jeżeli komponent który uruchomił ten serwis zostanie zniszczony, serwis nadal będzie działał. Kiedy serwis skończy pracę, zatrzyma się samoczynnie.
  • Związany("Bound ") - serwis wiąże się z komponentem w momencie kiedy komponent wywoła metodę bindService() . Taki komponent po wywołaniu metody **bindService()**dostaje do dyspozycji prosty interface IBinder dzięki któremu może nawiązać komunikacje z serwisem.

Dodatkowo serwis może zostać stworzony na dwa sposoby, rozszerzając klasę Service lub jedną z jej podklas np: IntentService. Żeby pokazać różnice, najpierw stworzymy serwis rozszerzając klasę IntentService następnie klasę Service.

Started service

Na pierwszy ogień pójdzie started service - czyli "odpal i zapomnij". Naszym celem jest zbudowanie aplikacji, której UI zapewnia jeden przycisk (start), którego akcja powoduje uruchomienie się serwisu - serwis ten będzie miał za zadanie poczekać 5 sekund i odpalić powiadomienie typu toast, i tak kilka razy.

A więc najpierw kod odpowiadający za UI czyliactivity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:onClick="start"
        android:text="Start" />

</RelativeLayout>

Jak widać, mamy jeden przycisk wraz z przypisaną akcją - odpowiada za to pole android:onClick="start", czyli po kliknięci w przycisk Start wywoływana jest metoda start znajdująca się w klasie MainActivity.

Następnie klasa MainActivity:

public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	public void start(View v){ 
		startService(new Intent(getBaseContext(), ProstySerwis.class));
	}
}

Nas interesuje tutaj tylko metoda start i jej zawartość, czyli wywołanie metody **startService(),**który zawiera intent(pamiętamy z lekcji o BroadCastach) oraz nasz serwis który ma zostać wywołany.

Pozostał nam jeszcze klasa serwisu:

public class ProstySerwis extends IntentService{
	private int i;
	private Handler handler = new Handler();
	public ProstySerwis() {
		super("ProstySerwis");
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onHandleIntent(Intent arg0) {

		while(i<10){
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
			handler.post(new Runnable() {
	              public void run() {
	            	  Toast.makeText(getApplicationContext(), "WITAJ W SERWISIE", Toast.LENGTH_SHORT).show();
	              }
	           });
			i++;
		}
	}

}

Tworzenie serwisu za pomocą klasy IntentService znacznie upraszcza sprawę, musimy jedynie wypełnić metodę onHandleIntent - nie wspominaliśmy sobie dotychczas nic o metodzie onBind(), jednak warto zapamiętać że w przypadku dziedziczenia po IntentService, domyślnie zwraca null. Metoda onHandleIntentzostała już przeze mnie wypełniona, więc słów o niej kilka:

  • metoda Thread.sleep(); - zatrzymuje na określony czas wykonanie aktualnego wątku.

  • toast jest małym oknem typu popup.

Żeby móc uruchomić przykład pozostaje nam tylko poinformować aplikacje że serwis o nazwie ProstySerwis istnieje,zrobimy to dodając tag <service/> do manifestu. Na przykład:

 ...
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <service android:name=".ProstySerwis" /> // informujemy o naszym ProstymSerwisie
        <activity
            android:name="com.example.javastart.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
..

Aplikacja po wciśnięciu "start" uruchomi serwis, ten co pięć sekund będzie generował toast'a z napisem***"WITAJ W SERWISIE"*** i tak 10 razy. Jeżeli wyłączysz aplikacje, zauważ że okno będzie pojawiać się aż do końca ( czyli do 10 razy). W normalnym przypadku, tak pozostawiony serwis mógłby sobie spokojnie ściągać plik lub aktualizować bazę danych.

Bound Service

Ten typ serwisu daje nam możliwość komunikacji między nami a serwisem. Jest to możliwe dzięki dostarczeniu interfejsu Binder(czyli zbioru metod) który daje możliwość odwołania się do metod które posiada nasz serwis.

Teoria brzmi zbyt "teoretycznie", więc zbudujemy sobie serwis do którego będziemy mogli się "podłączyć". Serwis o nazwie BoundService będzie posiadał jedną publiczną metodę generateToast(), po której wywołaniu pojawi się toast. Dość ważne jest to że serwis tego typu, będzie "istniał" do póki będzie istniał komponent "połączony" z nim, czyli komponent który wywołał metodębindService() ale nie wywołał metody unbindService(). Kilka komponentów na raz może być związanych z jednym serwisem.

Dobra, koniec pisania, czas na kod. Tym razem zaczniemy od klasy BoundService:

public class BoundService extends Service {
	private Handler handler = new Handler();

    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
    	BoundService getService() {
           //zwracamy instancje serwisu, przez nią odwołamy się następnie do metod.
            return BoundService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    //metoda którą zapewniamy.
    public void generateToast() {
    	handler.post(new Runnable() {
            public void run() {
          	  Toast.makeText(getApplicationContext(), "WITAJ W SERWISIE(znowu)", Toast.LENGTH_SHORT).show();
            }
         });
    }
}

Nie ma tu jak widać za dużo kodu, i za dużo też się tu nie dzieje. Jest tak ponieważ naszym zadaniem jest zapewnienie implementacji klasy Binder którą będziemy się posługiwać w Activity, robi to LocalBinder który posiada metodę **getService() -**po której wywołaniu zwracana jest aktualna instancja klasy.

public class MainActivity extends Activity {
	protected BoundService mService;
	protected boolean mBound = false;

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

	@Override
	protected void onStart() {
		super.onStart();
		Intent intent = new Intent(this, BoundService.class);
		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
	}

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

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	public void start(View v){ 
		if (mBound) {
			mService.generateToast();
        }
	}

	private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {

            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

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

}

Tu będziemy musieli chwilkę przystanąć, żeby ogarnąć to co się dzieje w klasie MainActivity.

Na pierwszy ogień metody onStop() ionStart() (co robią wiemy ponieważ taki art. się już na łamach javastart'u pojawił). Jednak interesują nas one z innego powodu, w momencie kiedy wywołujemy metodę bindService() musimy także wywołać unbindService()- żeby wszystko było ładne, dobieramy te wywołania w "parach", połączonych ze sobą w pewien sposób metodach cyklu życia. Czyli:

  • jeżeli chcemy żeby nas serwis był "związany" z naszym komponentem tylko kiedy jest on widoczny, wywołujemy metody bindService() oraz unbindService() w metodach onStop() ionStart().

  • natomiast jeżeli chcemy wchodzić w interakcje z naszym serwisem nawet kiedy aplikacja działa w tle, odwołujemy się odpowiednio do metodonCreate() orazonDestroy().

Oczywiście powyższe zalecenie jest tylko dobrą praktyką a nie obowiązkiem, równie dobrze może nas ponieść ułańska fantazja w planowaniu bindowania i unbindowania.

Dalej mamy metodę bindService() - odpowiedzialna za "złączenie" z serwisem (możemy powiedzieć że odpowiednik startService() ) - przyjmuje ona trzy parametry są to: Intent który wskazuje nam serwis, obiekt ServiceConnection - o nim za chwilkę - oraz trzeci parametr to flaga wskazująca opcje bindowania("łączenia") i zazwyczaj będzie ona przez nas ustawiana na BIND_AUTO_CREATE - czyli tworzymy nowy serwis ( taki który jeszcze nie istnieje jako instancja). Dość analogicznie działa metoda **unbindService()**która nas najzwyczajniej "rozłącza".

Do pełni szczęścia zostaje nam jeszcze chyba tylko obiekt mConnection - jest to właśnie obiekt klasy ServiceConnection który daje nam możliwość odwołania się do metod serwisu. Metoda onServiceConnected() wywoływana jest przez System by dostarczyć nam obiekt IBinder zwrócony przez metodęonBind() - tak tą z serwisu :) - metoda onServiceDisconnected() informuje nas o rozłączeniu.

Mając już instancje serwisu który pobraliśmy wewnątrz metody onServiceConnected() możemy spokojnie odwołać się do metod które oferuje serwis, czyli np:mService.generateToast();

Nie zapomnij dodać informacji o serwisie do pliku manifest - tak jak to zostało zrobione wcześniej.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.

Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.

Johnny

Artykuł spk, ale całe życie myślałem, że "service(s)" w tym kontekście to "usługa"/"usługi"... Po za tym parę brakujących literek, np. "Tak uruchomiony serwis będzie działa[Ł? Ć?] niezależnie w tle".

Bartek

Literówkę poprawiłem. Odnośnie tego tłumaczenia to może masz rację - moje spolszczenie może być mylące. Ale wydaje mi się że usługa("świadczyć usługi") jest synonimem słowa serwować. Jednak dzięki - na pewno następnym razem przysiądę dłużej nad tłumaczeniem.

hee.?

To jest twój autorki artykuł czy tłumaczenie? w jakim celu jest zastosowany while(i&lt;10){ try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO AUTO-GENERATED CATCH BLOCK e.printStackTrace(); } można prosić o wyjaśnienie?

Bartek

Jeżeli nie wiesz do czego służy metoda sleep() polecam zapoznać się z dokumentacją, np tu: http://developer.android.com/reference/java/lang/Thread.html#sleep%28long%29 . Odnośnie znajomości pętli polecam najpierw zapoznać się z podstawami języka Java przed rozpoczęciem nauki Androida.

vegomuaythai

Jak dodac klase do projektu w android studio?

lolo

Daj w oknie project na folder w którym masz klasę z ActivityMain i wciśnij alt+insert i daj java class. A jak chcesz wrzucić plik z klasą to przeciągasz go do folderu z ActivityMain