Usługi i AsyncTask

Transkrypt

Usługi i AsyncTask
I.
Usługi
Usługa (Service) jest komponentem aplikacji, który pozwala wykonywać w tle długodziałające
operacje, które nie dostarczają żadnego interfejsu użytkownika. Komponent aplikacji może
uruchomić usługę, która będzie kontynuowała swoje działanie nawet gdy użytkownik przełączy się do
innej aplikacji. Dodatkowo, komponent aplikacji może związać się z usługą aby komunikować się z nią
oraz wymieniać z nią dane. Dla przykładu, usługa może obsługiwać transakcje w sieci, odtwarzać
muzykę, wykonywać przetwarzanie plików lub komunikować się z dostawcą treści.
Usługa może przyjąć dwie formy:
Uruchomionej – tzn. w przypadku gdy komponent aplikacji wywoła usługę metodą
startService(). Raz uruchomiona, usługa może działać w tle bez końca, nawet gdy
komponent ją uruchamiający zostanie zniszczony. Zwykle usługi uruchomione w tej
postaci wykonują prostą operację i nie zwracają żadnego rezultatu do komponentu
wywołującego. W takiej formie usługa może służyć do pobierania lub wysyłania
plików przez sieć. W przypadku gdy operacje zostaje zakończona, usługa powinna
zatrzymać się samoczynnie.
Związanej – tzn. w przypadku gdy komponent aplikacji wywoła usługę metodą
bindService(). Usługa związana oferuje interfejs klient-serwer, który pozwala mu
komunikować się z komponentami, wysyłać żądania, pobierać rezultaty. Usługa
związana działa tak długo, jak długo inny komponent aplikacji jest z nią związany. Z
jedną usługą mogą być związane wiele komponentów, jednak gdy wszystkie one
zostaną odłączone od usługi, usługa jest niszczona.
W praktyce jednak usługa może przyjąć zarówno postać uruchomionej jak i związanej. Może
być zarówno uruchomiona jak i związana – wszystko zależy od tego, jakie metody implementuje:
onStartCommand(), który służy do uruchamiania usługi czy onBind(), która służy do jej związania z
komponentem. Niezależnie do tego w jakiej formie usługa została zaimplementowana, komponent
aplikacji może jej używać w takich sam sposób jak używane są aktywności – poprzez uruchomienie jej
z udziałem intencji.
Należy zwrócić uwagę na pewne ograniczenia związane z mechanizmem usługi. Usługa
funkcjonuje w głównym wątku procesu, który ją wywołał – nie tworzy zatem własnego wątku ani nie
funkcjonuje w osobnym procesie. Oznacza to, że jeżeli usługa będzie służyła do wykonywania
pewnych czynności mocno obciążających procesor lub operacji blokujących aplikację (jak np.
odtwarzanie plików MP3), powinien być dla niej utworzony osobny wątek. Poprzez wykorzystanie
osobnych wątków dla usług, zmniejsza się ryzyko wystąpienia tzw. błędów ANR (Application Not
Responding), polegających na nie odpowiadaniu na akcje użytkownika (potocznie zwane
zawieszeniem aplikacji).
W celu utworzenia usługi konieczne jest stworzenie klasy dziedziczącej po klasie Service (lub
jej klasach potomnych). Implementacja powinna zawierać przesłonięte metody, które pozwolą
obsłużyć cykl życia usługi oraz zapewnią, w razie konieczności, powiązanie usługi z komponentem
aplikacji. Najważniejszymi metodami, które powinny zostać przesłonięte są:
onStartCommand() – system wywołuje tę metodę, w przypadku gdy inny komponent
(np. aktywność) wywoła metodę startService(). Gdy metoda ta zostanie wykonana,
usługa zostaje uruchomiona i może działać w tle bez końca. Jeżeli metoda
onStartCommand() zostanie zaimplementowana, obowiązek zatrzymania usług
1
spoczywa na programiście, który musi w odpowiedni sposób wywołać metodę
stopSelf() bądź stopService().
onBind() - system wywołuje tę metodę, w przypadku gdy inny komponent (np.
aktywność) wywoła metodę bindService(). We własnej implementacji metody,
programista musi dostarczyć interfejs dla klienta, który pozwoli mu komunikować się
z usługą, zwracają tzw. obiekt klasy IBinder. Ta metoda zawsze powinna być
implementowana, jednak gdy usługa w zamierzeniu ma nie przyjmować formy
związanej, metoda onBind() powinna zwracać wartość null.
onCreate() – system wywołuje tę metodę, kiedy usługa jest pierwszy raz tworzona. W
tej metodzie zapewnia się procedury ustawienia funkcjonowania usługi (zanim
zostaną wywołane metod onStartCommand() bądź onBind()). Jeżeli usługa jest już
uruchomiona, ta metoda nie zostanie wywołana.
onDestroy() – system wywołuje tę metodę, kiedy usługa nie jest już wykorzystywana i
należy ją zniszczyć. Własna implementacja tej metody powinna wprowadzać
procedury oczyszczania zasobów wykorzystywanych przez usługę takich, jak wątki,
zarejestrowani słuchacze, odbiorcy komunikatów, itd.
System Android może wymusić zatrzymanie usługi tylko w przypadku niskich zasobów
pamięci, które muszą zostać pozyskane dla aktywności, na której skupiony jest użytkownik. Jeżeli
usługa została uruchomiona i działa już przez dłuższy czas, system obniża jej pozycję na liście zadań w
tle, co powoduje zwiększenie prawdopodobieństwa jej usunięcia. Jeżeli usługa zostanie zniszczona,
zostanie ponownie uruchomiona tak szybko jak to możliwe, tzn. gdy będą dostępne odpowiednie dla
niej zasoby. Więcej informacji o usługach można znaleźć pod adresem:
http://developer.android.com/guide/components/services.html
Przedstawiony tutaj przykład jest implementacją usługi pracującej w głównym procesie aplikacji
oraz usługi, która obsługuje tryb wielu żądań i tworzy wiele oddzielnych wątków, które wykonują
czynności i niezależnie samoczynnie się zatrzymują. Implementacja głównej klasy aplikacji została
przedstawiona poniżej.
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "Aktywność";
Button buttonStart, buttonStop, buttonStart2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonStart = (Button) findViewById(R.id.button1);
buttonStop = (Button) findViewById(R.id.button2);
buttonStart2 = (Button) findViewById(R.id.button3);
buttonStart.setOnClickListener(this);
buttonStop.setOnClickListener(this);
buttonStart2.setOnClickListener(this);
}
2
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:
Log.d(TAG, "onClick: uruchomienie usługi");
startService(new Intent(this, MyService.class));
break;
case R.id.button2:
Log.d(TAG, "onClick: zatrzymanie usługi");
stopService(new Intent(this, MyService.class));
break;
case R.id.button3:
startService(new Intent(this, HelloService.class));
break;
}
}
}
Główna klasa przykładowej aplikacji (aktywność MainActivity) implementuje interfejs
OnClickListener, który posłuży do obsługi 3 przycisków znajdujących się w układzie aktywności.
Przyciski „Uruchom usługę” i „Zatrzymaj usługę” (pola „buttonStart” i „buttonStop”). Pole
„buttonStart2” związane jest z przyciskiem wywołującym usługę w nowym niezależnym wątku. W
metodzie onClick dodatkowo wykorzystano klasę Log, która służy do wypisywania w zakładce LogCat
(Eclipse) komunikatów pochodzących z urządzenia. Klasa posiada kilka metod, a wykorzystana tu
metoda „d” wyświetla informacje debuggera, oznaczone poprzez wartość znajdującą się w stałej
TAG, o treści przedstawionej w kodzie.
Do klasy dodane zostały dwie usługi – klasy MyService i HelloService, rozszerzające klasę
Service. Klasa MyService implementuje usługę działającą w głównym wątku procesu aplikacji.
Implementacja klasy HelloService opisuje usługę pracującą w trybie wielowątkowym, a jej struktura
pochodzi ze strony: http://developer.android.com/guide/components/services.html. Poniżej
zaprezentowana została implementacja klasy MySerivce.
public class MyService extends Service {
private static final String TAG = "Usługa";
private String mydate;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
mydate =
java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime()
);
Toast.makeText(this, "Usługa utworzona " + mydate,
Toast.LENGTH_LONG).show();
Log.d(TAG, "onCreate");
}
@Override
public void onDestroy() {
mydate =
java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime()
);
3
Toast.makeText(this, "Usługa zniszczona " + mydate,
Toast.LENGTH_LONG).show();
Log.d(TAG, "onDestroy");
}
@Override
public void onStart(Intent intent, int startid) {
Toast.makeText(this, "Usługa uruchomiona", Toast.LENGTH_LONG).show();
Log.d(TAG, "onStart");
}
}
Usługa MyService nie obsługuje trybu powiązania z komponentem, dlatego metoda onBind
zwraca wartość null. W metodzie onCreate w kontrolce Toast wyświetlany jest odpowiedni
komunikat o utworzeniu usługi, zawierający datę i czas tego zdarzenia (zmienna „mydate”). W
metodzie onDestroy i onStart również generowana jest kontrolka Toast, wyświetlająca stosowne
informacje.
Bardziej złożoną strukturę prezentuje klasa HelloService, reprezentująca usługę
wielowątkową. W pierwszej kolejności pokazana została sekcja pól danych, w których zdefiniowano
obiekty klas Looper i ServiceHandler. Klasa Looper reprezentuje pętle komunikatów związanych z
danym wątkiem, w której pojawiają się różne komunikaty obsługiwane przez wątek. Klasa
ServiceHandler jest w tym wypadku klasą rozszerzająca Handler, która obsługuje pętle komunikatu
wątku, w którym powstał obiekt ServiceHandler.
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler który odbiera wiadomości z wątku
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Uśpienie wątku na 5 sekund.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// Usługa zostanie zatrzymana przy wykorystaniu startId
// ,aby nie zatrzymywać jej w trakcie obsługi innej czynności
stopSelf(msg.arg1);
}
}
Aby zasymulować jakąś czynność w metodzie handleMessage (która obsługuje komunikaty)
zaimplementowano pętlę, w której za pomocą metody wait następuje zastopowanie wątku na czas
wyrażony w postaci milisekund wprowadzonych jako argument metody. W pętli został wykorzystany
tzw. blok zsynchronizowany – (słówko „synchronized”). Za pomocą tego wyrażenia możliwe jest
blokowanie dostępu do bloku instrukcji znajdujących się dalej pomiędzy klamrami (metoda wait)
przed referencją na obiekt podany jako argument metody synchronized. A więc synchronized(this)
4
{..blok…} blokuje dostęp do bloku przed obiektem klasy ServiceHandler. Po spełnieniu warunku pętli
(odczekaniu 5 sekund) następuje zatrzymanie usługi o przekazanym argumencie strartID z
komunikatu (klasa Message).
Poniżej w metodzie onCreate tworzony jest wątek posiadający pętle komunikatów (Looper),
będący obiektem klasy HandlerThread. Dla wątku ustawiany jest priorytet wykonywania w tle
(THREAD_PRIORITY_BACKGROUND), a następnie za pomocą metody start, wątek jest uruchamiany.
Kolejno tworzona jest pętla komunikatów (mServiceLooper) oraz nowy obiekt klasy ServiceHandler,
wykorzystujący wcześniej utworzoną pętlę komunikatów.
Komunikat tworzony
zaprezentowano poniżej.
jest
w
metodzie
onStartCommand,
której
implementację
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Usługa uruchomiona w oddzielnym wątku",
Toast.LENGTH_SHORT).show();
// Przy każdorazowym uruchomieniu, wysyłany jest komunikat o
rozpoczęciu pracy
// oraz przesyłany jest identyfikator startu (startId), który
jednoznacznie
// pozwala stwierdzić, które usługi mają zostać zatrzymane w przypadku
żądania.
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// Stała zapewniająca ponowny restart od tego miejsca, w przypadku,
gdy
// usługa zostanie zniszczona.
return START_STICKY;
}
Obiekt klasy Message tworzony jest na podstawie komunikatu pozyskanego z pętli
komunikatów wątku (mServiceHandler). Następnie do zmiennej arg1 komunikatu, zapisywany jest
identyfikator uruchomienia wątku (startId) i tak zmodyfikowana wiadomość (msg) trafia z powrotem
do ServiceHandlera za pomocą metody sendMessage. Metoda onStartComman zwraca stałą
START_STICKY, która zapewnia restart usługi, w przypadku jej zniszczenia.
Ostatnimi zaimplementowanymi metodami w klasie HelloService są metody onBind i
onDestroy, przedstawione poniżej.
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "Zakończono wykonywanie usługi",
Toast.LENGTH_SHORT).show();
}
5
Przykładowy ekran opisanej aplikacji przedstawiono na rys. 1. Uwidoczniono na nim moment
generowania kontrolki Toast podczas wywołania metody onCreate dla usługi klasy MyService
(uruchomionej w wątku aplikacji).
Rys. 1 Przykładowy ekran aplikacji wykorzystującej usługi. Utworzenie usługi w głównym wątku aplikacji.
Parokrotne kliknięcie w przycisk „Uruchom usługę” nic nie da, ponieważ wcześniejsza usługa
musi zostać zatrzymana (należy kliknąć w przycisk „Zatrzymaj usługę”. Natomiast w odróżnieniu od
usługi MyService, usługa wykonywana we własnym wątku, może być tworzona niezależnie. Stąd
wielokrotne kliknięcie w przycisk „Uruchom usługę w oddzielnym wątku” powoduje utworzenie wielu
wątków usług i ich systematyczne zakańczanie po 5 sekundach, w kolejności kliknięcia.
II.
AsyncTask
Podczas uruchamiania komponentu aplikacji, w przypadku gdy aplikacja nie posiada innych
działających komponentów, system Android uruchamia dla aplikacji nowy proces linuksowy,
wykonywany jednowątkowo. Domyślnie, wszystkie komponenty tej samej aplikacji funkcjonują w
tym samym procesie i wątku (zwanym wątkiem „głównym”). W przypadku gdy uruchamiany jest
komponent aplikacji i istnieje już proces przydzielony tej aplikacji, komponent uruchomiony zostaje w
tym procesie i wykorzystuje ten sam wątek do wykonywania operacji.
Android może zadecydować w pewnym momencie wykonywania operacji o zakończeniu
procesu, w przypadku gdy ilość dostępnej pamięci jest niska a inne procesu potrzebują pamięci, aby
szybciej dostarczyć odpowiedź do użytkownika. Komponenty aplikacji uruchomione w procesie, który
został zniszczony zostają również unicestwione. Zabity proces uruchamiany jest ponownie, w
przypadku gdy pewne komponenty zażądają jego wykonania.
System Android stara się zarządzać procesem aplikacji tak długo, jak to jest możliwe. Istnieją
jednak sytuacje, w których usuwane są stare procesy w celu odzyskania pamięci dla nowych lub
bardziej istotnych procesów. Aby określić, które procesy powinny zostać zachowane, a które
zniszczone, systemu umieszcza je wszystkie w tzw. hierarchii ważności, bazującej na komponentach
działających w tym procesie oraz stanie tych komponentów. Procesy o najniższej ważności są
6
eliminowane jako pierwsze, następnie w razie konieczności, usuwane są procesy na kolejnym
poziomie ważności, itd.
Hierarchia ważności procesu podzielona została na 5 rodzajów procesów, które mogą być
usunięte przez system (w kolejności od najbardziej istotnego po najmniej istotny):
Proces pierwszego planu – jest wymagany do operacji, wykonywanych na bieżąco
przez użytkownika,
Proces widoczny – proces nie posiadających żadnych komponentów na pierwszym
planie wykonania, jednak mający ciągły wpływ na to, co dzieje się na ekranie,
Proces usługi – proces, który przetwarza usługę wywołaną za pomocą metody
startService, który nie został jeszcze zdegradowany na jeden z dwóch poniższych
poziomów,
Proces tła – proces przechowujący aktywność, która nie jest aktualnie widoczna dla
użytkownika, a więc nie ma bezpośredniego wpływu na to, co wykonuje użytkownik.
Pusty proces – proces, który nie przechowuje żadnych aktywnych komponentów
aplikacji. Jedynym powodem nie niszczenia tego procesu są mechanizmy pamięci
podręcznej, które mogą być z nim związane (skracają np. czas wywołania innych
komponentów).
Kiedy aplikacja jest uruchomiona, system tworzy wątek wykonania dla aplikacji, którego
potocznie nazywa się wątkiem „głównym”. Jest to bardzo ważny wątek odpowiedzialny za kontrolę
zdarzeń generowanych przez odpowiednie elementy interfejsu użytkownika, włączając w to proces
rysowania. Jest to również wątek, w którym aplikacja współpracuje z komponentami pochodzącymi z
zestawy Android UI (User Interface). W związku z tym, „główny” wątek jest często nazywany wątkiem
interfejsu użytkownika (UI).
System nie tworzy oddzielnych wątków dla każdej instancji komponentu –wszystkie
komponenty uruchomione w tym samym procesie są przetwarzane w wątku UI i to właśnie w nim
następuje komunikacja pomiędzy komponentami a systemem. W konsekwencji, metody, które
reagują na sprzężenia zwrotne systemu (np. metody nasłuchiwaczy kliknięcia czy wciśnięcia klawisza)
zawsze uruchomione są w wątku UI procesu aplikacji.
Kiedy aplikacja wymaga intensywnej pracy, której wyniki są potwierdzone w postaci interakcji
z użytkownikiem, model jednowątkowy może przynieść duży spadek wydajności aplikacji. Jeżeli
wszystkie operacja przetwarzane są w głównym wątku UI, przetwarzanie operacji wymagających
czasu (np. zapytanie do bazy danych) może powodować blokadę całego interfejsu aplikacji. Kiedy
wątek UI jest blokowany, nie obsługuje żadnych żądań, włączając w to operacji rysowania. Z punktu
widzenia użytkownika, aplikacja się zawiesza. W przypadku, gdy blokada głównego wątku trwa dłużej
niż ok. 5 sekund, użytkownik może zobaczyć na ekranie okno dialogowe, mówiące o błędzie ANR
(Application Not Responding).
Dobrym rozwiązaniem w przypadku operacji dłużej przetwarzanych, jest wykorzystanie tzw.
zadania asynchronicznego klasy AsyncTask. AsyncTask pozwala wykonywać operacje mogące
blokować wątek główny, w tzw. wątkach roboczych, a następnie publikować wyniki w do wątku UI,
bez konieczności implementacji obsługi wątków oraz ich handlerów do obsługi komunikatów.
7
Aby wykorzystać możliwości AsyncTask, należy stworzyć podklasę dla AsyncTaska i
zaimplementować kilka metod, związanych z przetwarzaniem i wyświetlaniem wyników pracy wątku.
W pierwszej kolejności należy zaimplementować metodę doInBackGround, w której wykonuje się
operacje w przestrzeni wątku w tle. Do aktualizacji interfejsu, powinna zostać zaimplementowana
metoda onPostExecute, która dostarcza wyników z metody doInBackground i działa wątku głównym.
Zadanie może zostać uruchomione poprzez wywołanie metody execute w głównym wątku aplikacji.
Zasady przetwarzania AsyncTask wygląda następująco:
Określa się typy parametrów, wartości postępu oraz wynikowe wartości zadania, przy
wykorzystaniu typów wbudowanych,
Metoda doInBackground jest automatycznie wykonywana w wątku roboczym,
Metody onPreExecute, onPostExecute i onProgressUpdate są wywoływane z wątku
UI,
Wartość zwrócona przez metodę doInBackGround jest wysyłana do metody
onPostExecute,
Metoda publishProgress może być wywołana w metodzie doInBackground w
dowolnym momencie, aby wywołać metodę onProgressUpdate z głównego wątku
aplikacji (wątku UI),
Zadanie AsyncTask może być anulowane w dowolnym momencie przez dowolny
wątek.
Dla zaprezentowania mechanizmu zadania AsyncTask zaimplementowano pokazową
aplikację, które symuluje wykonywanie określonej czynności w czasie podanym jako parametr przez
użytkownika. Dodatkowo postęp wykonania jest ukazany z wykorzystaniem kontrolki paska postępu
(ProgressBar).
W opisywanej dalej aplikacji utworzona została aktywność AsyncMainActivity z wewnętrzną
klasą AsyncTaskRunner. Poniżej zaprezentowano sekcję pól klasy AsyncMainActivity oraz
implementację metody onCreate i onClick. W klasie zdefiniowano obiekt dla kontrolek Button,
EditText, TextView oraz ProgressBar. Przycisk służy do uruchomienia zadania klasy AsyncTaskRunner.
W kontrolce EditText użytkownik wprowadza liczbę milisekund, definiującą czas wykonania zadania.
W kontrolce TextView oraz ProgressBar udostępniane są wyniki uzyskane z zadania
AsyncTaskRunner.
W metodzie onCreate tworzone są obiekty kontrolek interfejsu oraz ustawiany jest
anonimowy nasłuchiwacz kliknięcia dla przycisku. Metoda onClick interfejsu OnClickListener tworzy
obiekt klasy AsyncTaskRunner, pobiera wartość wpisaną przez użytkownika w polu edycyjnym,
wywołuje zadanie (metoda execute) oraz zeruje pasek postępu. W metodzie execute, parametrem
przekazanym do zadania jest obiekt przechowujący liczbę milisekund, podaną przez użytkownika w
polu edycyjnym.
public class AsyncMainActivity extends Activity {
8
private
private
private
private
Button button;
EditText time;
TextView finalResult;
ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_main);
time = (EditText) findViewById(R.id.et_time);
button = (Button) findViewById(R.id.btn_do_it);
finalResult = (TextView) findViewById(R.id.tv_result);
progressBar = (ProgressBar)findViewById(R.id.progressBar1);
progressBar.setProgress(0);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AsyncTaskRunner runner = new AsyncTaskRunner();
String sleepTime = time.getText().toString();
runner.execute(sleepTime);
progressBar.setProgress(0);
}
});
}
Poniżej zaprezentowano implementację wewnętrznej klasy zadania asynchronicznego o
nazwie AsyncTaskRunner, będącej podklasą AsyncTask. Początkowo przedstawiono nagłówek tej
klasy oraz implementacje metody doInBackground. W klasie zdefiniowano pole „resp”
przechowujące wyniki przetwarzania. W metodzie doInBackground wykonywana jest pętla
symulująca wykonywanie operacji, w której na krótką chwilę (time/100) usypiany jest wątek zadania
oraz wywoływana jest metoda publishProgress, przekazująca parametr w postaci zmiennej
iteracyjnej pętli do metody onProgressUpdate. Po wykonaniu metody doInBackground zwracany jest
napis zapisany w polu „resp”. Zmienna przekazana jako parametr do zadania asynchronicznego, jest
wcześniej zamieniana do postaci liczby całkowitej (Integer.parseInt).
private class AsyncTaskRunner extends AsyncTask<String, Integer, String> {
private String resp;
@Override
protected String doInBackground(String... params) {
try {
int time = Integer.parseInt(params[0]);
for (int i=0; i<=100; i++)
{
Thread.sleep(time/100);
publishProgress(i);
}
resp = "Zadanie wykonane";
} catch (InterruptedException e) {
e.printStackTrace();
resp = e.getMessage();
} catch (Exception e) {
e.printStackTrace();
resp = e.getMessage();
}
return resp;
}
9
Następnie zaimplementowane zostały w klasie AsyncTaskRunner pozostałe metody zadania,
przedstawione poniżej.
@Override
protected void onPostExecute(String result) {
finalResult.setText(result);
}
@Override
protected void onPreExecute() {
}
@Override
protected void onProgressUpdate(Integer... progress) {
progressBar.setProgress(progress[0]);
finalResult.setText("Zadanie wykonane w " + progress[0] + " %");
}
}
Metoda onPostExecute wywołana jest po skończeniu przetwarzania metody doInBackground,
zwracającej wartość pola „resp”. Wartość ta jest następnie użyta jako parametr wejściowy metody
onPostExecute, która ustawia wynikowy napis w kontrolce TextView.
Metoda onPrexecute nie została zaimplementowana. W niej należy wykonać pewne operacje
potrzebne przed wykonaniem metody doInBackground (np. wywołanie okna dialogowego z paskiem
postępu).
W metodzie onProgressUpdate aktualizowany jest widok kontrolek ProgressBar oraz
TextView, z wykorzystaniem parametru przekazanego z metody doInBackground przy wywołaniu
metody publishProgress.
Aplikacja obrazuje postęp wykonania poprzez animację paska postępu. Jego definicja została
zaimplementowana w pliku activity_async_main.xml , którego fragment przedstawiono poniżej.
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:progressDrawable="@drawable/progress_bar_states"
android:layout_width="400dip"
android:layout_height="50dip"
android:layout_alignLeft="@+id/tv_time"
android:layout_marginRight="9dip"
android:layout_marginTop="15dip"
android:layout_marginLeft="260dip"
android:minHeight="60dip"
android:layout_centerVertical="true" />
Dla paska postępu określono wbudowany styl poziomego paska postępu (właściwość „style”)
oraz wskazano na zasoby drawable, związane z wyglądem graficznym paska. Elementy te zostały
zdefiniowane w pliku „progress_bar_states.xml”, którego zawartość przedstawiono poniżej.
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<corners android:radius="10px" />
<gradient
android:startColor="#000001"
android:centerColor="#0b131e"
android:centerY="0.25"
10
android:endColor="#0d1522"
android:angle="90"
/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="10px" />
<gradient
android:startColor="#000000"
android:centerColor="#ff0000"
android:centerY="0.5"
android:endColor="#000000"
android:angle="90"
/>
</shape>
</clip>
</item>
</layer-list>
W pliku zdefiniowano listę elementów drawable zwaną layer-list. Elementy na tej liście są
rysowane zgodnie z porządkiem listy, poczynając od góry do dołu (od elementu o najniższym indeksie
do elementu o najwyższym indeksie).
Początkowo będzie zatem rysowany element o id background w kształcie (<shape>)
prostokątnym (<rectangle>) z zaokrąglonymi rogami (android:radius) i tłem w postaci gradientu
(<gradient>). Następnie narysowany na nim zostanie element o id progress, z charakterystyką
podobną do poprzedniego elementu, symbolizujący czerwony prostokąt z gradientem. Dodatkowo
element o id progress otoczony został znacznikiem <clip>, który determinuje zmianę kształtu
prostokąta (przycięcie) zgodnie z postępem wykonywanego zadania AsyncTaskRunner.
Przykładowy ekran zaimplementowanej aplikacji przedstawiono na rys. 2. Po wpisaniu przez
użytkownika liczby milisekund i kliknięciu w przycisk „Uruchom AsyncTask”, rozpoczyna się
przetwarzanie zadania. Szybkość animacji paska postępu i zmian w kontrolce TextView
uwarunkowana jest wartością wpisaną w polu edycyjnym.
Rys. 2 Przykładowy ekran aplikacji wykorzystującej podklasę AsyncTask.
11
III.
Bibliografia
Arsoba, R. (2011). Programowanie urządzeń mobilnych. Zagadnienia podstawowe. Pobrano Czerwiec
12, 2012 z lokalizacji http://grafika.weii.tu.koszalin.pl/android/Programowanie_Android.pdf
Conder S., D. L. (2011). Android. Programowanie aplikacji na urządzenia przenośne. Wydanie II.
Gliwice: Helion.
Geetha, S. (2011, Maj 17). Sai Geetha's Blog - Android. Pobrano Czerwiec 20, 2013 z lokalizacji Sai
Geetha's Blog: http://saigeethamn.blogspot.in/2011/05/contacts-api-20-and-aboveandroid.html
Komatineni S., M. D. (2012). Android 3. Tworzenie aplikacji. Gliwice: Helion.
Lee, W.-M. (2011). Beginning Android Application Development. Indianapolis: Wiley Publishing Inc.
12

Podobne dokumenty