AsyncTask i zakończona Aktywność na androidach

Posted In: Bez kategorii

Na Androidach pre Honeycomb (api 11 czyli android 3.0) bez najmniejszych problemów można było wywołać dowolną procedurę typu pobierz dane (powiedzmy xml) z Internetu, obrób je, a następnie wyświetl. Wszystko w wątku UI.

Od czasu Honeycomb, przy próbie wykonania najprostszego pobrania danych metodą new URL().openStream() aplikacja się wywali zwracając NetworkOnMainThreadException.

Najprostszym sposobem na uniknięcie tego jest stworzenie nowego wątku i uruchomienie go, new Thread(new Runnable(){…}), bardziej zaawansowani programiści stworzą sobie klasę typu extends AsyncTask i użyją jej trzech metod: onPreExecute(), doInBackground(), oraz onPostExecute().

I tak powstanie kolejny problem, mianowicie, co jeśli w trakcie metody doInBackground() użytkownik (lub system) zamknie, czy zapauzuje naszą aplikację (na przykład do usera ktoś zadzwoni) a metoda onPostExecute() wykonuje coś na wątku UI, który w tej sytuacji jest albo w stanie pauzy (po onPause()) lub w ogóle go nie ma (po onDestroy())? Aplikacja także się wywali, w pierwszym wypadku zwracając tajemniczy BadTokenException, w drugim prawdopodobnie NullPointerException, choć to już zależne jest od innych czynników, bo może się zdarzyć, że aplikacja się zablokuje, ponieważ referencja do naszego AsyncTask, który jeszcze się wykonuje, nie mogła zostać wyczyszczona przez Garbage Collectora podczas onStop() ani onDestroy().

57[1]

Rozwiązanie jest dość proste, choć z ilości pytań na stackoverflow wynika, że niewielu je zna. Należy do konstruktora naszej klasy extends AsyncTask przesłać parametr zawierający naszą aktywność i na jego podstawie stworzyć obiekt klasy WeakReference:

public MyAsyncTask(MainActivity mainActivity) {
   super();
   this.mainActivityWeak = new WeakReference(mainActivity);
}

Następnie w onPreExecute() oraz w onPostExecute() we wszystkich operacjach odwołujących się do naszej mainActivity, która może się w międzyczasie zakończyć, odwołujemy się do WeakReference.get(), najlepiej po sprawdzeniu czy WeakReference.get() nie zakończyła się ani nie kończy się w danym momencie:

if (mainActivityWeak.get() != null &&
   !mainActivityWeak.get().isFinishing()) {
   mainActivityWeak.get().doSomethingOnUiThread();
}

Osobiście popieram z całego serca używanie klas extends AsyncTask do jak największej liczby zadań, które mogą być cięższe niż obliczenie SHA1 🙂 Nie blokują wątku UI i powodują, że aplikacja działa płynniej, a Androidowy MultiThreading cieszy się z kolejnej dobrze napisanej aplikacji.