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.