1 komentarz

Services – usługi działające w tle, pierwsze kroki

Sierpień 3, 2011 Podstawowe komponenty Services Tutoriale

Android, jak większość współczesnych systemów operacyjnych, zawiera bardzo rozbudowane mechanizmy do pracy w tle. Jednym z takich mechanizmów są Usługi (Services), których zadaniem jest przetwarzanie zadań nie wymagających (lub wymagających w niewielkim stopniu) ingerencji użytkownika. O tym czym są i jak z nich skorzystać dowiemy się z dzisiejszego wpisu.Oto kilka ważnych kwestii dotyczących Usług:

  • Usługa działająca w tle ma większy priorytet niż Aktywność, która w tym samym momencie jest nieaktywna/niewidoczna. Wiąże się to z tym, że podczas zwalniania zasobów systemowych pierwszym kandydatem do zatrzymania i usunięcia z pamięci będzie niewidoczna Aktywność. Zatrzymywanie usługi następuje tylko wtedy, gdy zajmuje ona zasób niezbędny do otwarcia bieżącej Aktywności. Jednak wtedy system przywraca usługę do działania zaraz po tym, gdy zasób ten będzie ponownie dostępny.
  • Usługa nie jest osobnym procesem ani wątkiem! Należy pamiętać, że wykonywana jest w głównym wątku naszej aplikacji. Dlatego też ważnym jest by wszystkie zasobożerne czynności przenosić do osobnych wątków, tak by zapewnić głównemu wątkowi ciągłą gotowość na odpowiedź.
  • Brak interfejsu usług sprawia, że sterowaniem ich cyklem życia zajmują się inne komponenty systemu.

Przykładowa aplikacja

Zajmiemy się teraz zbudowaniem bardzo prostej aplikacji, która pozwoli nam na uruchomienie i zatrzymanie naszej Usługi.

Przygotowania

Oto kompletny kod źródłowy klasy głównej Aktywności ClientActivity, za pomocą której będziemy sterować naszą Usługą:

package pl.froger.helloservices;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class ClientActivity extends Activity {
	private Button btnStartService;
	private Button btnStopService;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnStartService = (Button) findViewById(R.id.btnStartService);
		btnStopService = (Button) findViewById(R.id.btnStopService);
		initButtonsOnClick();
	}

	private void initButtonsOnClick() {
		OnClickListener listener = new OnClickListener() {
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnStartService:
					startMyService();
					break;
				case R.id.btnStopService:
					stopMyService();
					break;
				default:
					break;
				}
			}
		};
		btnStartService.setOnClickListener(listener);
		btnStopService.setOnClickListener(listener);
	}

	private void startMyService() {
		Intent serviceIntent = new Intent(this, MyService.class);
		startService(serviceIntent);
	}

	private void stopMyService() {
		Intent serviceIntent = new Intent(this, MyService.class);
		stopService(serviceIntent);
	}
}

Jedyną ciekawą rzeczą są tutaj metody pozwalające na uruchomienie Usługi. Jak widać, wystarczy jedynie przekazać jawną Intencję wskazująca na naszą usługę do metod startService(Intent intent) lub stopService(Intent intent). I to wszystko. :)

Kod źródłowy Usługi

Nieco ciekawiej prezentuje się kod naszej Usługi. Klasa, za pomocą której implementujemy ten komponent posiada bowiem własny cykl życia.

Cykl życia Usługi

Podobnie do Aktywności, Usługi posiadają szereg metod związanych z ich cyklem życia. Mamy tutaj zatem:

  • onCreate() wywoływane w momencie tworzenia Usługi,
  • onStartCommand() wywoływane w momencie startu usługi (uwaga – występuje ona w zamian za onStart(), które od wersji 2.0 Androida zostało oznaczone jako deprecated – ciekawy artykuł na ten temat można znaleźć pod tym adresem).
  • onDrestroy() wywoływane w momencie kończenia działania Usługi.

Należy pamiętać o bardzo ważnej kwestii. W systemie może działa tylko jedna instancja konkretnej usługi. W związku z tym pierwsze jej uruchomienie (metoda startService()) wywoła kolejno metody: onCreate()-> onStartCommand(). Drugie uruchomienie wywoła jedynie metodę onStartCommand() wewnątrz działającej już Usługi. Dla potwierdzenia sprawdzimy to w naszej aplikacji.

Wróćmy więc do tworzenia klasy naszej Usługi, która w krótkich odstępach czasu będzie sygnalizowała fakt, że działa (za pomocą obiektu Toast). :)

Po dodaniu nowej klasy dziedziczącej po android.app.Service powinniśmy ujrzeć podobny do poniższego kod:

public class MyService extends Service {
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
}

Po utworzeniu klasy należy pamiętać o dodaniu jej deklaracji do pliku AndroidManifest.xml. Odbywa się to poprzez dodanie linijki kodu wewnątrz węzła <application>:

<service android:name=".MyService"></service>

Wracając do kodu źródłowego, na razie zignorujemy metodę onBind(), która została wygenerowana w kodzie naszej Usługi. Warto jednak wspomnieć, że służy ona do podłączenia Usługi do Aktywności, dzięki czemu Aktywność ma dostęp do jej instancji, co pozwala m.in. na wywoływanie jej metod publicznych.

Nadpiszemy teraz wszystkie metody związane cyklem życia Usługi:

public class MyService extends Service {
	private void writeToLogs(String message) {
		Log.d("HelloServices", message);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		writeToLogs("Called onCreate() method.");
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		writeToLogs("Called onStartCommand() methond");
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		writeToLogs("Called onDestroy() method");
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
}

W tym momencie możemy uruchomić naszą aplikację i wypróbować w praktyce zachowanie Usługi podczas uruchamiania i zatrzymywania.

Zatem, po pierwszym uruchomieniu w logach otrzymamy wpis:

DEBUG/HelloServices(806): Called onCreate() method.
DEBUG/HelloServices(806): Called onStartCommand() methond

Kiedy spróbujemy uruchomić naszą Usługę ponownie, utrzymamy jedynie:

DEBUG/HelloServices(806): Called onStartCommand() methond

Zatrzymanie Usługi natomiast zwróci informację:

DEBUG/HelloServices(806): Called onDestroy() method

Jak widać wszystko zgodnie z teorią na temat cyklu życia Usługi.

Cykliczne zadanie sterowane z poziomu Usługi

Rozbudujemy teraz naszą Usługę o mechanizm cyklicznego wykonywania jakiejś czynności. Całość oprzemy o Timer/TimerTask wykorzystywane w Javie do wykonywania zadań w osobnym wątku, w równych odstępach czasu.

public class MyService extends Service {
	private Toast toast;
	private Timer timer;
	private TimerTask timerTask;
	private class MyTimerTask extends TimerTask {
		@Override
		public void run() {
			showToast("Your service is still working");
		}
	}

	private void showToast(String text) {
		toast.setText(text);
		toast.show();
	}
	...

Do naszej klasy dodaliśmy niezbędne pola oraz metodę, która będzie wykonywana co jakiś czas. Następnie:

	@Override
	public void onCreate() {
		super.onCreate();
		writeToLogs("Called onCreate() method.");
		timer = new Timer();
		toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
	}

inicjalizujemy odpowiednie obiekty (odbywa się to raz – tylko w momencie tworzenia Usługi).

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		writeToLogs("Called onStartCommand() methond");
		clearTimerSchedule();
		initTask();
		timer.scheduleAtFixedRate(timerTask, 4 * 1000, 4 * 1000);
		showToast("Your service has been started");
		return super.onStartCommand(intent, flags, startId);
	}

	private void clearTimerSchedule() {
		if(timerTask != null) {
			timerTask.cancel();
			timer.purge();
		}
	}

	private void initTask() {
		timerTask = new MyTimerTask();
	}
  • linijka 43 – anulujemy istniejące zadanie i resetujemy Timer,
  • linijka 44 – tworzymy nowy obiekt naszego zadania cyklicznego,
  • linijka 45 – wstawiamy nasze zadanie do Timera, dzięki czemu będzie ono wykonywane w odstępach 4 sekundowych (opis metody scheduleAtFixedRate(…)).

Linijki 43 oraz 44 zostały wprowadzone na wypadek ponownego uruchomienia naszej Usługi. Dzięki temu zabiegowi uruchamiamy nasze zadanie cykliczne od nowa.

Na koniec jeszcze czyścimy nasz timer przed zakończeniem działania Usługi:

	@Override
	public void onDestroy() {
		writeToLogs("Called onDestroy() method");
		clearTimerSchedule();
		showToast("Your service has been stopped");
		super.onDestroy();
	}

I w tym momencie pozostało nam tylko zaprezentowanie kompletnego kodu źródłowego Usługi MyService:

package pl.froger.helloservices;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class MyService extends Service {
	private Toast toast;
	private Timer timer;
	private TimerTask timerTask;
	private class MyTimerTask extends TimerTask {
		@Override
		public void run() {
			showToast("Your service is still working");
		}
	}

	private void showToast(String text) {
		toast.setText(text);
		toast.show();
	}

	private void writeToLogs(String message) {
		Log.d("HelloServices", message);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		writeToLogs("Called onCreate() method.");
		timer = new Timer();
		toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		writeToLogs("Called onStartCommand() methond");
		clearTimerSchedule();
		initTask();
		timer.scheduleAtFixedRate(timerTask, 4 * 1000, 4 * 1000);
		showToast("Your service has been started");
		return super.onStartCommand(intent, flags, startId);
	}

	private void clearTimerSchedule() {
		if(timerTask != null) {
			timerTask.cancel();
			timer.purge();
		}
	}

	private void initTask() {
		timerTask = new MyTimerTask();
	}

	@Override
	public void onDestroy() {
		writeToLogs("Called onDestroy() method");
		clearTimerSchedule();
		showToast("Your service has been stopped");
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
}

Screeny

Usługa działa nawet po wyłączeniu Aktywności, która nią steruje:

Kompletny kod źródłowy

Cały projekt dostępny jest na naszym githubie – HelloServices.

ClientActivity.java

package pl.froger.helloservices;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class ClientActivity extends Activity {
	private Button btnStartService;
	private Button btnStopService;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnStartService = (Button) findViewById(R.id.btnStartService);
		btnStopService = (Button) findViewById(R.id.btnStopService);
		initButtonsOnClick();
	}

	private void initButtonsOnClick() {
		OnClickListener listener = new OnClickListener() {
			public void onClick(View v) {
				switch (v.getId()) {
				case R.id.btnStartService:
					startMyService();
					break;
				case R.id.btnStopService:
					stopMyService();
					break;
				default:
					break;
				}
			}
		};
		btnStartService.setOnClickListener(listener);
		btnStopService.setOnClickListener(listener);
	}

	private void startMyService() {
		Intent serviceIntent = new Intent(this, MyService.class);
		startService(serviceIntent);
	}

	private void stopMyService() {
		Intent serviceIntent = new Intent(this, MyService.class);
		stopService(serviceIntent);
	}
}

MyService.java

package pl.froger.helloservices;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class MyService extends Service {
	private Toast toast;
	private Timer timer;
	private TimerTask timerTask;
	private class MyTimerTask extends TimerTask {
		@Override
		public void run() {
			showToast("Your service is still working");
		}
	}

	private void showToast(String text) {
		toast.setText(text);
		toast.show();
	}

	private void writeToLogs(String message) {
		Log.d("HelloServices", message);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		writeToLogs("Called onCreate() method.");
		timer = new Timer();
		toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		writeToLogs("Called onStartCommand() methond");
		clearTimerSchedule();
		initTask();
		timer.scheduleAtFixedRate(timerTask, 4 * 1000, 4 * 1000);
		showToast("Your service has been started");
		return super.onStartCommand(intent, flags, startId);
	}

	private void clearTimerSchedule() {
		if(timerTask != null) {
			timerTask.cancel();
			timer.purge();
		}
	}

	private void initTask() {
		timerTask = new MyTimerTask();
	}

	@Override
	public void onDestroy() {
		writeToLogs("Called onDestroy() method");
		clearTimerSchedule();
		showToast("Your service has been stopped");
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView
		android:id="@+id/tvServiceControl"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_gravity="center"
		android:text="Service control"
		android:textSize="18dp" />
	<Button
		android:id="@+id/btnStartService"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Start"
		android:layout_gravity="center" />
	<Button
		android:id="@+id/btnStopService"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Stop"
		android:layout_gravity="center" />
</LinearLayout>

Komentarze (1) Subskrybuj

 

  1. [...] Services – usługi działające w tle, pierwsze kroki [...]

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.