Android AsyncTask do długotrwałych operacji

91

Cytując dokumentację AsyncTask znalezioną tutaj , mówi:

AsyncTasks powinno być idealnie używane do krótkich operacji (najwyżej kilka sekund). Jeśli chcesz, aby wątki działały przez długi czas, zdecydowanie zaleca się użycie różnych interfejsów API dostarczonych przez pakiet java.util.concurrent, takich jak Executor, ThreadPoolExecutor i FutureTask.

Teraz pojawia się moje pytanie: dlaczego? Ta doInBackground()funkcja działa poza wątkiem interfejsu użytkownika, więc jakie szkody wynikają z długotrwałej operacji w tym miejscu?

user1730789
źródło
2
Problem, który napotkałem podczas wdrażania aplikacji (która używa asyncTask) na rzeczywistym urządzeniu, polegał na tym, że długotrwała doInBackgroundfunkcja zawiesza ekran, jeśli pasek postępu nie jest używany.
venkatKA
2
Ponieważ AsyncTask jest powiązany z działaniem, w którym jest uruchamiany. Jeśli więc działanie zostanie zabite, wystąpienie AsyncTask również może zostać zabite.
IgorGanapolsky,
Co się stanie, jeśli utworzę AsyncTask w ramach usługi? Czy to nie rozwiązałoby problemu?
eddy
1
Użyj IntentService, idealnego rozwiązania do uruchamiania długich operacji w tle.
Keyur Thumar

Odpowiedzi:

121

To bardzo dobre pytanie, jako programista Androida potrzeba czasu, aby w pełni zrozumieć problem. Rzeczywiście, AsyncTask ma dwa główne problemy, które są ze sobą powiązane:

  • Są słabo przywiązani do cyklu życia działalności
  • Bardzo łatwo tworzą wycieki pamięci.

W aplikacji RoboSpice Motivations ( dostępnej w Google Play ) szczegółowo odpowiadamy na to pytanie. Zapewni dogłębny wgląd w AsyncTasks, Loaders, ich funkcje i wady, a także przedstawi alternatywne rozwiązanie dla żądań sieciowych: RoboSpice. Żądania sieciowe są typowym wymaganiem w systemie Android i są z natury długotrwałymi operacjami. Oto fragment aplikacji:

Cykl życia AsyncTask i Activity

AsyncTasks nie podążają za cyklem życia instancji Activity. Jeśli uruchomisz AsyncTask wewnątrz działania i obrócisz urządzenie, działanie zostanie zniszczone i zostanie utworzone nowe wystąpienie. Ale AsyncTask nie umrze. Będzie żyć, dopóki się nie skończy.

Po zakończeniu AsyncTask nie zaktualizuje interfejsu użytkownika nowego działania. Rzeczywiście aktualizuje poprzednie wystąpienie działania, które nie jest już wyświetlane. Może to prowadzić do wyjątku typu java.lang.IllegalArgumentException: Widok niepołączony z menedżerem okien, jeśli na przykład używasz findViewById do pobierania widoku wewnątrz działania.

Problem z wyciekiem pamięci

Tworzenie AsyncTasks jako klas wewnętrznych działań jest bardzo wygodne. Ponieważ AsyncTask będzie musiał manipulować widokami działania, gdy zadanie jest zakończone lub w toku, użycie wewnętrznej klasy działania wydaje się wygodne: klasy wewnętrzne mogą uzyskać bezpośredni dostęp do dowolnego pola klasy zewnętrznej.

Niemniej jednak oznacza to, że klasa wewnętrzna będzie zawierała niewidoczne odniesienie do swojej instancji klasy zewnętrznej: Activity.

W dłuższej perspektywie powoduje to wyciek pamięci: jeśli AsyncTask trwa długo, utrzymuje aktywność „przy życiu”, podczas gdy Android chciałby się go pozbyć, ponieważ nie można go już wyświetlić. Działania nie można zbierać jako śmieci i jest to centralny mechanizm systemu Android służący do ochrony zasobów na urządzeniu.


To naprawdę bardzo zły pomysł, aby używać AsyncTasks do długotrwałych operacji. Niemniej jednak są one dobre dla krótkotrwałych, takich jak aktualizacja widoku po 1 lub 2 sekundach.

Zachęcam do pobrania aplikacji RoboSpice Motivations , która naprawdę wyjaśnia to szczegółowo i zawiera przykłady i demonstracje różnych sposobów wykonywania niektórych operacji w tle.

Snicolas
źródło
@Snicolas Hi. Mam aplikację, w której skanuje dane z tagu NFC i wysyła na serwer. Działa dobrze w obszarach o dobrym sygnale, ale gdzie nie ma sygnału, AsyncTask, który powoduje, że połączenie internetowe działa. np. okno dialogowe postępu trwa kilka minut, a gdy zniknie, ekran staje się czarny i nie odpowiada. Moja AsyncTask jest klasą wewnętrzną. Piszę do Handlera, aby anulował zadanie po X sekundach. Wygląda na to, że aplikacja wysyła stare dane na serwer kilka godzin po skanowaniu. Czy może to być spowodowane tym, że AsyncTask nie kończy się, a potem może kończyć się kilka godzin później? Byłbym wdzięczny za każdy wgląd. dzięki
turtleboy
Śledź, aby zobaczyć, co się stanie. Ale tak, to całkiem możliwe! Jeśli dobrze zaplanujesz swoje asynchroniczne zadanie, możesz je anulować raczej poprawnie, to byłby dobry punkt wyjścia, jeśli nie chcesz migrować do RS lub usługi ...
Snicolas
@Snicolas Dzięki za odpowiedź. Wczoraj opublikowałem post na SO, w którym opisałem mój problem i pokazałem kod modułu obsługi, który napisałem, aby spróbować zatrzymać AsyncTask po 8 sekundach. Czy mógłbyś rzucić okiem, jeśli masz czas? Czy wywołanie AsyncTask.cancel (true) z programu obsługi anuluje zadanie poprawnie? Wiem, że powinienem okresowo sprawdzać wartość iscancelled () w moim doInBackgroud, ale nie sądzę, że ma to zastosowanie w moich okolicznościach, ponieważ wykonuję tylko jedno wierszowe połączenie internetowe HttpPost i nie publikuję aktualizacji w interfejsie użytkownika. Czy są alternatywy dla AsyncTask np. czy możliwe jest wysłanie HttPost z IntentService
turtleboy
Powinieneś spróbować w RoboSpice (na github). ;)
Snicolas
38

czemu ?

Ponieważ AsyncTaskdomyślnie używa puli wątków , której nie utworzyłeś . Nigdy nie wiąż zasobów z puli, której nie utworzyłeś, ponieważ nie wiesz, jakie są wymagania tej puli. I nigdy nie wiąż zasobów z puli, której nie utworzyłeś, jeśli dokumentacja dla tej puli mówi ci, żeby tego nie robić, jak ma to miejsce w tym przypadku.

W szczególności, począwszy od Androida 3.2, AsyncTaskdomyślnie używana pula wątków (dla aplikacji z android:targetSdkVersionustawieniem na 13 lub wyższą) ma tylko jeden wątek - jeśli związasz ten wątek na czas nieokreślony, żadne inne zadania nie zostaną uruchomione.

CommonsWare
źródło
Dzięki za to wyjaśnienie ... Nie mogłem znaleźć nic naprawdę błędnego w używaniu AsyncTasks do długotrwałych operacji (chociaż zazwyczaj sam je deleguję do Services), ale twój argument dotyczący wiązania tej puli wątków (dla którego rzeczywiście możesz nie zakładaj z góry jego rozmiaru) wydaje się trafiony. Jako uzupełnienie posta Anupa na temat korzystania z usługi: ta usługa powinna również sama uruchamiać zadanie w wątku w tle, a nie blokować własnego wątku głównego. Opcją może być IntentService lub, w przypadku bardziej złożonych wymagań dotyczących współbieżności, zastosuj własną strategię wielowątkowości.
baske
Czy to jest to samo, nawet jeśli uruchomię AsyncTask w usłudze? Mam na myśli, czy długo działająca operacja nadal byłaby problemem?
eddy
1
@eddy: Tak, ponieważ nie zmienia to charakteru puli wątków. W przypadku a Service, po prostu użyj a Threadlub a ThreadPoolExecutor.
CommonsWare
Dziękuję @CommonsW, to tylko ostatnie pytanie, czy TimerTasks ma te same wady co AsyncTasks? czy to zupełnie inna sprawa?
eddy
1
@eddy: TimerTaskpochodzi ze standardowej Javy, a nie Androida. TimerTaskzostał w dużej mierze porzucony na rzecz ScheduledExecutorService(który pomimo swojej nazwy jest częścią standardowej Javy). Żadne z nich nie jest powiązane z Androidem, więc nadal potrzebujesz usługi, jeśli oczekujesz, że te rzeczy będą działać w tle. I naprawdę powinieneś rozważyć AlarmManager, z Androida, więc nie potrzebujesz usługi kręcącej się po prostu obserwując tykanie zegara.
CommonsWare
4

Zadanie Aysnc to wyspecjalizowane wątki, które nadal mają być używane z graficznym interfejsem użytkownika aplikacji, ale jednocześnie zachowują duże zadania wątku interfejsu użytkownika. Więc kiedy takie rzeczy jak aktualizacja list, zmiana widoków itp. Wymagają wykonania pewnych operacji pobierania lub aktualizacji, powinieneś używać zadań asynchronicznych, abyś mógł zachować te operacje poza wątkiem interfejsu użytkownika, ale pamiętaj, że te operacje są nadal w jakiś sposób połączone z interfejsem użytkownika .

W przypadku dłuższych zadań, które nie wymagają aktualizacji interfejsu użytkownika, można zamiast tego korzystać z usług, ponieważ mogą one funkcjonować nawet bez interfejsu użytkownika.

Dlatego w przypadku krótkich zadań używaj zadań asynchronicznych, ponieważ mogą one zostać zabite przez system operacyjny po zaniku aktywności odradzania (zwykle nie umierają w trakcie operacji, ale wykonają swoje zadanie). W przypadku długich i powtarzalnych zadań korzystaj z usług.

aby uzyskać więcej informacji, zobacz wątki:

AsyncTask dłużej niż kilka sekund?

i

AsyncTask nie zatrzyma się nawet po zniszczeniu działania

Anup Cowkur
źródło
1

Problem z AsyncTask polega na tym, że jeśli jest zdefiniowana jako niestatyczna klasa wewnętrzna działania, będzie miała odniesienie do działania. W scenariuszu, w którym działanie kontenera zadania asynchronicznego kończy się, ale praca w tle w AsyncTask jest kontynuowana, obiekt działania nie będzie zbierany jako śmieci, ponieważ istnieje do niego odwołanie, co powoduje wyciek pamięci.

Rozwiązaniem tego problemu jest zdefiniowanie zadania asynchronicznego jako statycznej wewnętrznej klasy działania i użycie słabego odniesienia do kontekstu.

Mimo to dobrze jest używać go do prostych i szybkich zadań w tle. Aby opracować aplikację z czystym kodem, lepiej jest używać RxJava do uruchamiania złożonych zadań w tle i aktualizowania interfejsu użytkownika za pomocą wyników.

Arnav Rao
źródło