poniedziałek, 17 lutego 2014

Wielowątkowość w Androidzie

Jak bardzo potrafi irytować zawieszanie się aplikacji wie każdy z nas. Teraz wyobraź sobie, że Twoja aplikacja na Androida ma do wykonania jakieś czasochłonne czynności, wystarczy że będzie musiała pobrać jakieś dane czy obraz z internetu. Jeśli zrobisz to w ramach głównej wątku programu, UI przestanie odpowiadać, a użytkownik Twojego programu będzie życzył Ci właśnie tego, czego zapewne Ty życzysz czasem programistom Internet Explorera :) Aby się o tym przekonać, wystarczy przykleić na ekranie przycisk, a jako jego reakcję oprogramować zdarzenie oczekiwania. Przyklej guzik:


a jako obsługę jego naciśnięcia wpisz taki kod:



W zasadzie interesują nas tylko linie 24-32. Reszta to otoczka. Jak widzimy, w chwili naciśnięcia przycisku wywoływana jest instrukcja Thread.sleep(10000). To sprawi że aplikacja zaczeka 10 sekund. Spróbuj to uruchomić na dowolnym urządzeniu i np. wywołać menu pogramu (tym dedykowanym guzikiem do menu). Zobaczysz że nic się nie stanie, aplikacja po prostu się zawiesiła.
Trzeba więc wydzielić osobny wątek i puścić go w tle do realizowania czasochłonnych zadań. Można zrobić to „tradycyjnie” po javowemu:


Możesz uruchomić tak przerobiony program, a przekonasz się że wątek działający w tle nie blokuje innych elementów interfejsu. Jest oczywiście pewne „ale”. Jeśli spróbujesz sięgnąć z poziomu kodu do jakichkolwiek komponentów wizualnych z poziomu takiego wątku, dostaniesz taki wyjątek:


Myślę że komunikat tego wyjątku jest zrozumiały. Na potrzeby Androida powstał zupełnie osobny mechanizm wielowątkowości. Wykorzystamy tutaj klasę AsyncTask która jak sama nazwa wskazuje, służy do wykonywania zadań asynchronicznie. Wykorzystamy też jeden ciekawy gadżet – ProgressBar. Jest dostępny na palecie Form Widgets.



Chcemy żeby użytownik widział, że program coś wykonuje, a nie po prostu zwisł. Taki ProgressBar domyślnie jest widoczny i wykonuje się animacja. My zrobimy tak, żeby na począŧku nie był widoczny, pokażemy go gdy puścimy wątek i schowamy gdy go zakończymy.
Wykorzystanie wcześniej wspomnianej klast AsyncTask polega na stworzeniu klasy która po niej dziedziczy i zaimplementowaniu paru metod. Do klasy naszej aktywności dodaję wewnętrzną klasę OsobnyWątek dziedziczącą po AsyncTask. Nic nie stoi na przeszkodzie by była to w ogóle osobna klasa. Pojawiło się tutaj też coś nowego – element <Void,Void,Void> oraz dziwna parametryzacja metod doInBackground i onPostExecute. Związane jest to z pojęciem klas generycznych, ogólnych. Tym zagadnieniem nie będziemy się tutaj zajmować, nie jest związane bezpośrednio z tematem wielowątkowości w Androidzie. Obiekty klasy OsobnyWatek będą stanowiły osobne wątki w ramach aplikacji. Zauważ że mamy tutaj trzy metody przesłaniające takie metody z klasy po której dziedziczymy. Metoda onPreExecute jest automatycznie wywoływana przy starcie wątku. Wyświetlam tutaj nasz ProgressBar. Kółko staje się widoczne i zaczyna „pracować”. Daję też komunikat na konsolę. W tej metodzie, oraz w metodzie onPostExecute możemy bez obaw odwoływać się do komponentów graficznych w naszej aktywności. Nie robimy tego za to w metodzie doInBackground. To co ma się dziać w ramach wątku – te długotrwałe czynności piszemy właśnie w metodzie doInBackground. Tutaj wyrzucam na konsolę informację, oraz wstawiam 10 sekundowe oczekiwanie. Metoda onPostExecute jest wywoływana automatycznie w chwili zakończenia wątku. Wyświetlam tutaj informację o zakończeniu i chowam ProgressBar.



Zobaczmy teraz co się dzieje w metodzie onCreate:



W linii 55 na początek chowam ProgressBar. Będzie on wyświetlany tylko w trakcie działania wątku w tle. Jako reakcję na naciśnięcie przycisku uruchamiam nasz wątek (linia 60).
Program po uruchomieniu i kliknięciu przycisku wygląda tak:



Zawartość konsoli LogCata po zakończeniu całego procesu:



6 komentarzy:

  1. A czy ta prywatna klasa OsobnyWatek nie przetrzymuje referencji do Activity, z której została wywołana? Jeśli tak, to może to powodować wycieki pamięci - Activity będzie trzymane w pamięci nawet po zamknięciu (w przypadku aplikacji z wieloma aktywnościami).

    OdpowiedzUsuń
    Odpowiedzi
    1. Dokładnie tak. Mamy tu do czynienia z dużym wyciekiem pamięci. AsyncTask należy używać ostrożnie. Rozwiązaniem jest zrobienie wewnętrznej klasy static lub przeniesienie jej do własnego pliku i użycie WeekReference

      Usuń
  2. a nie lepiej zamiast OnClickListenerów wpisywać nazwę fukncji do wywołania w onclick buttona w pliku xml?
    Nie trzeba wtedy pisać tyle kodu żeby wywołać to kliknięcie.

    OdpowiedzUsuń
    Odpowiedzi
    1. Próbowałem ale nie działa. możesz za to zrobić funkcję w której wywołasz new OsobnyWatek().execute(); a jej nazwę w onlick w pliku xml.

      Usuń
  3. Anonimowy13 września 2014 04:17
    Autor tłumaczył to wcześniej. Pisanie metody w XML do onClick powoduje, że część logiki znajduje się w warstwie prezentacji, a to znów zmniejsza czytelność.

    OdpowiedzUsuń
  4. Witam.
    Szkoda, że nie ma kodu do pobrania.
    ps.
    Strona zawiera bardzo cenne informacje i pisana jest w sposób bardzo zrozumiały. Brawo dal autora.

    OdpowiedzUsuń