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