Badałem ten problem od miesięcy, wymyśliłem różne rozwiązania, z których nie jestem zadowolony, ponieważ wszystkie są ogromnymi hackami. Nadal nie mogę uwierzyć, że klasa, która ma wady w projektowaniu, weszła w ramy i nikt o tym nie mówi, więc chyba coś mi brakuje.
Problem tkwi w tym AsyncTask
. Zgodnie z dokumentacją to
„umożliwia wykonywanie operacji w tle i publikowanie wyników w wątku interfejsu użytkownika bez konieczności manipulowania wątkami i / lub programami obsługi”.
Przykład dalej pokazuje, jak showDialog()
wywoływana jest przykładowa metoda onPostExecute()
. Wydaje mi się to jednak całkowicie wymyślone , ponieważ wyświetlenie okna dialogowego zawsze wymaga odwołania do poprawnego Context
, a AsyncTask nigdy nie może zawierać silnego odwołania do obiektu kontekstu .
Powód jest oczywisty: co się stanie, jeśli działanie zostanie zniszczone, co uruchomiło zadanie? Może się to zdarzać przez cały czas, np. Dlatego, że odwróciłeś ekran. Jeśli zadanie zawiera odniesienie do kontekstu, który go utworzył, nie tylko trzymasz się bezużytecznego obiektu kontekstu (okno zostanie zniszczone, a każda interakcja interfejsu użytkownika zakończy się niepowodzeniem z wyjątkiem!), Możesz nawet ryzykować utworzenie wyciek pamięci.
O ile moja logika nie jest tutaj wadliwa, przekłada się to na: onPostExecute()
jest całkowicie bezużyteczne, ponieważ po co ta metoda działa w wątku interfejsu użytkownika, jeśli nie masz dostępu do żadnego kontekstu? Nie możesz tutaj zrobić nic znaczącego.
Jednym obejściem byłoby nie przekazywanie instancji kontekstu do zadania AsyncTask, ale Handler
instancja. To działa: ponieważ moduł obsługi luźno wiąże kontekst i zadanie, możesz wymieniać wiadomości między nimi bez ryzyka wycieku (prawda?). Oznaczałoby to jednak, że założenie AsyncTask, a mianowicie, że nie trzeba zawracać sobie głowy obsługą, jest błędne. Wygląda to również na nadużywanie modułu obsługi, ponieważ wysyłasz i odbierasz wiadomości w tym samym wątku (tworzysz go w wątku interfejsu użytkownika i wysyłasz za pośrednictwem onPostExecute (), który jest również wykonywany w wątku interfejsu użytkownika).
Podsumowując, nawet przy takim obejściu nadal masz problem polegający na tym, że gdy kontekst zostanie zniszczony, nie masz zapisanego zadania, które wykonał. Oznacza to, że musisz ponownie uruchomić wszelkie zadania podczas ponownego tworzenia kontekstu, np. Po zmianie orientacji ekranu. Jest to powolne i marnotrawstwo.
Moim rozwiązaniem tego ( zaimplementowanym w bibliotece Droid-Fu ) jest utrzymanie odwzorowania WeakReference
s z nazw komponentów na ich bieżące instancje w unikalnym obiekcie aplikacji. Za każdym razem, gdy AsyncTask jest uruchamiany, zapisuje kontekst wywołania na tej mapie i przy każdym wywołaniu zwrotnym pobiera bieżącą instancję kontekstu z tego odwzorowania. Zapewnia to, że nigdy nie będziesz odwoływał się do przestarzałej instancji kontekstu i zawsze będziesz mieć dostęp do poprawnego kontekstu w wywołaniach zwrotnych, dzięki czemu możesz wykonywać znaczącą pracę interfejsu użytkownika. Nie przecieka również, ponieważ odniesienia są słabe i są kasowane, gdy nie istnieje już żadna instancja danego komponentu.
Mimo to jest to skomplikowane obejście i wymaga podklasowania niektórych klas bibliotek Droid-Fu, co czyni to dość nachalnym podejściem.
Teraz chcę po prostu wiedzieć: czy po prostu coś masowo mi brakuje, czy też AsyncTask jest naprawdę całkowicie wadliwy? Jakie są twoje doświadczenia z tym związane? Jak rozwiązałeś ten problem?
Dzięki za wkład.
źródło
Odpowiedzi:
Co powiesz na coś takiego:
źródło
Ręcznie oddzielić działalność od
AsyncTask
inonDestroy()
. Ręcznie ponownie powiąż nowe działanie zAsyncTask
wejściemonCreate()
. Wymaga to albo statycznej klasy wewnętrznej, albo standardowej klasy Java, a także 10 linii kodu.źródło
Wygląda na to, że
AsyncTask
jest nieco więcej niż tylko wadą koncepcyjną . Nie można go również używać z powodu problemów ze zgodnością. Dokumenty Androida brzmią:Po raz pierwszy AsyncTasks były wykonywane szeregowo w jednym wątku w tle. Począwszy od DONUT, zmieniono to na pulę wątków, umożliwiając równoległe wykonywanie wielu zadań. Po uruchomieniu HONEYCOMB zadania wracają do wykonywania w jednym wątku, aby uniknąć typowych błędów aplikacji spowodowanych równoległym wykonywaniem. Jeśli naprawdę chcesz wykonywać równolegle, możesz użyć
executeOnExecutor(Executor, Params...)
wersji tej metody zTHREAD_POOL_EXECUTOR
; jednak zobacz komentarz tam ostrzeżenia o jego użyciu.Zarówno
executeOnExecutor()
iTHREAD_POOL_EXECUTOR
są dodane w poziomie API 11 (Android 3.0.x, plaster miodu).Oznacza to, że jeśli utworzysz dwa
AsyncTask
s, aby pobrać dwa pliki, drugie pobieranie nie rozpocznie się, dopóki pierwszy się nie skończy. Jeśli czatujesz przez dwa serwery, a pierwszy serwer jest wyłączony, nie nawiążesz połączenia z drugim przed upływem limitu czasu połączenia z pierwszym. (Oczywiście, chyba że użyjesz nowych funkcji API11, ale spowoduje to, że Twój kod będzie niezgodny z wersją 2.x).A jeśli chcesz kierować reklamy zarówno do wersji 2.x, jak i 3.0+, rzeczy stają się naprawdę trudne.
Ponadto doktorzy mówią:
Przestroga: Innym problemem, który może wystąpić podczas korzystania z wątku roboczego, jest nieoczekiwany restart działania w związku ze zmianą konfiguracji środowiska wykonawczego (na przykład gdy użytkownik zmienia orientację ekranu), co może zniszczyć wątek roboczy . Aby zobaczyć, jak możesz zachować swoje zadanie podczas jednego z tych restartów i jak poprawnie anulować zadanie po zniszczeniu działania, zobacz kod źródłowy przykładowej aplikacji Półki.
źródło
Prawdopodobnie wszyscy, w tym Google, niewłaściwie wykorzystujemy
AsyncTask
z punktu widzenia MVC .Aktywność jest kontrolerem i kontroler nie powinien rozpoczynać operacji, które mogą przeżyć widok . Oznacza to, że AsyncTasks należy stosować z modelu , z klasy, która nie jest związana z cyklem życia działania - pamiętaj, że działania są niszczone podczas rotacji. (Jeśli chodzi o widok , zwykle nie programujesz klas pochodzących z np. Android.widget.Button, ale możesz. Zazwyczaj jedyne, co robisz z widokiem, to xml.)
Innymi słowy, niewłaściwe jest umieszczanie pochodnych AsyncTask w metodach Działania. OTOH, jeśli nie wolno nam używać AsyncTasks w działaniach, AsyncTask traci swoją atrakcyjność: kiedyś reklamowano go jako szybką i łatwą naprawę.
źródło
Nie jestem pewien, czy to prawda, że ryzykujesz przeciek pamięci w związku z kontekstem z AsyncTask.
Typowym sposobem ich implementacji jest utworzenie nowej instancji AsyncTask w ramach jednej z metod działania. Więc jeśli aktywność zostanie zniszczona, to po zakończeniu AsyncTask nie będzie ona nieosiągalna, a następnie kwalifikuje się do odśmiecania? Odniesienie do działania nie będzie miało znaczenia, ponieważ samo AsyncTask nie będzie się zawieszać.
źródło
Bardziej solidne byłoby zachowanie WeekReference na temat swojej aktywności:
źródło
Dlaczego nie zastąpić
onPause()
metody w działaniu będącym właścicielem i anulowaćAsyncTask
stamtąd?źródło
onPause
, ponieważ jest tak samo zły, jak trzymanie go gdziekolwiek indziej? Czyli możesz dostać ANR?onPause
czy innego), ponieważ ryzykujemy uzyskanie ANR.Masz całkowitą rację - dlatego odejście od używania zadań asynchronicznych / modułów ładujących w działaniach do pobierania danych nabiera tempa. Jednym z nowych sposobów jest użycie struktury Volley , która zasadniczo zapewnia oddzwonienie, gdy dane są gotowe - znacznie bardziej spójne z modelem MVC. Siatkówka została zaludniona w Google I / O 2013. Nie jestem pewien, dlaczego więcej osób nie jest tego świadomych.
źródło
Osobiście po prostu rozszerzam wątek i używam interfejsu zwrotnego do aktualizacji interfejsu użytkownika. Nigdy nie mogłem zmusić AsyncTask do prawidłowej pracy bez problemów z FC. Korzystam również z nieblokującej kolejki do zarządzania pulą wykonawczą.
źródło
Myślałem, że anulowanie działa, ale nie działa.
tutaj RTFM o tym:
„” Jeśli zadanie już się rozpoczęło, parametr mayInterruptIfRunning określa, czy wątek wykonujący to zadanie powinien zostać przerwany w celu zatrzymania zadania. ”
Nie oznacza to jednak, że wątek jest przerywany. To jest Java, a nie AsyncTask. ”
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
źródło
Lepiej byłoby myśleć o AsyncTask jako o czymś ściślej powiązanym z Aktywnością, Kontekstem, ContextWrapper itp. Jest to wygodniejsze, gdy jego zakres jest w pełni zrozumiały.
Upewnij się, że masz zasady anulowania w swoim cyklu życia, aby w końcu zostały one wyrzucone i nie będą już zawierały odniesienia do Twojej aktywności, a także mogą być wyrzucane.
Bez anulowania AsyncTask podczas przechodzenia od kontekstu napotkasz wycieki pamięci i wyjątki NullPointerException, jeśli po prostu potrzebujesz przekazać opinię, np. Toast, proste okno dialogowe, wówczas singleton kontekstu aplikacji pomógłby uniknąć problemu NPE.
AsyncTask nie jest wcale taki zły, ale na pewno dzieje się dużo magii, która może prowadzić do nieprzewidzianych pułapek.
źródło
Jeśli chodzi o „doświadczenia w pracy z nim”: możliwe jest zabicie procesu wraz ze wszystkimi AsyncTasks, Android odtworzy stos aktywności, aby użytkownik nic nie wspominał.
źródło