5

W najnowszej wersji mojej aplikacji niektórzy użytkownicy mają awarię, której nie mogę odtworzyć. Obecnie tylko urządzenia o numerze Samsung pracujące pod numerem Lollipop mają problem, ale może to być tylko zbieg okoliczności. Po przeanalizowaniu śladu stosu i odpowiedniego kodu, myślę, że mogłem znaleźć winnego. Aby przetestować moje założenie, że uproszczony kod na poniższym fragmencie:Czy możliwe jest wywołanie metody oddzwaniania po onDestroy?

public class TestActivity extends AppCompatActivity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     Button b = new Button(this); 
     b.setText("Click me!"); 
     b.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View view) { 
       new Handler().post(new Runnable() { 
        @Override 
        public void run() { 
         // This is the callback method 
         Log.d("TAG", "listenerNotified"); 
        } 
       }); 
      } 
     }); 

     setContentView(b); 
    } 

    @Override 
    protected void onDestroy() { 
     super.onDestroy(); 
     Log.d("TAG", "onDestroy"); 
    } 

} 

Za każdym razem testować powyższym wniosku przez pierwsze dotknięcie Kliknij mnie przycisk, a następnie przycisk Wstecz, listenerNotified jest drukowany na konsolę przed onDestroy().

Nie jestem jednak pewien, czy mogę polegać na tym zachowaniu. Czy Android daje jakiekolwiek gwarancje dotyczące powyższego scenariusza? Czy mogę bezpiecznie założyć, że mój Runnable będzie zawsze wykonywany przed onDestroy(), czy istnieje scenariusz, w którym tak nie jest? W mojej prawdziwej aplikacji jest oczywiście dużo więcej rzeczy (jak inne wątki wysyłane do głównego wątku i więcej operacji występujących w oddzwanianiu). Ale ten prosty fragment wydawał się wystarczający do wykazania mojej troski.

Czy jest to możliwe (możliwe, że z powodu wpływu innych wątków lub wywołań zwrotnych wysłanych do głównego wątku), że otrzymam dane wyjściowe debugowania poniżej?

D/TAG: onDestroy 
D/TAG: listenerNotified 

Chciałbym to wiedzieć, ponieważ ten wynik byłby możliwy do wyjaśnienia katastrofy.

+0

Dlaczego publikujesz uruchamianie za pomocą programu obsługi? Tymczasem możesz rzucić okiem na http://stackoverflow.com/questions/31432014/onclicklistener-fired-after-onpause –

+1

Kiedy masz asynchroniczne callbacki w ten sposób, zawsze powinieneś sprawdzić callback, jeśli 'Activity' jest jeszcze żywa przed przetwarzanie wywołania zwrotnego. Najprościej to zrobić, wywołując 'isFinishing()', która zwraca 'true', jeśli' Activity' nie jest już "aktywny". –

Odpowiedz

3

Czy jest możliwe wywołanie metody oddzwonienia po onDestroy()?

Tak.

Zmodyfikujmy nieco twój przykładowy kod dotyczący umieszczenia Runnable na Handler. Zakładam również (w zależności od opisu), które mogą mieć wiele Runnable s wysłana do głównego wątku, więc w pewnym momencie może istnieć kolejka Runnable S, która doprowadza mnie do opóźnienia w eksperymencie poniżej:

public void onClick(View view) { 
    new Handler().postDelayed(new Runnable() { 
     @Override 
     public void run() { 
      // This is the callback method 
      Log.d("TAG", "listenerNotified"); 
     } 
    }, 3000); 
} 

Teraz naciśnij przycisk b, a następnie naciśnij przycisk Wstecz i powinieneś zobaczyć dane wyjście.

Might it be the reason of your app crash? Trudno powiedzieć, nie widząc, co masz. Chciałbym tylko zauważyć, że gdy instancja new Handler() jest tworzona w wątku (główny wątek w twoim przypadku), Handler jest powiązany z kolejką komunikatów wątku Looper, wysyłając do i przetwarzając Runnable s oraz wiadomości z kolejki . Te Runnable s i komunikaty mają odniesienie do celu Handler. Mimo że metoda Activity 'onDestroy() nie jest "destruktorem", tj. Gdy metoda zwróci instancję Activity nie zostanie natychmiastowo zabita (see), pamięć nie może być GC-ed z powodu niejawnego odnośnika * do Activity. Będziesz przeciekać, dopóki Runnable nie zostanie usunięty w kolejce z kolejki komunikatów Looper i nie zostanie przetworzony.

Bardziej szczegółowe wyjaśnienie można znaleźć na How to Leak a Context: Handlers & Inner Classes


* Instancji anonimowej klasy wewnętrznej Runnable ma odnosi się do instancji anonimowej klasy wewnętrznej View.OnClickListener który z kolei, ma odniesienie do Instancja Activity.

+0

'Handler' nie ma odniesienia do' Activity'. Co sprawia, że ​​tak myślisz? –

+0

@ David Wasser Wielkie dzięki za wskazanie popełnionego błędu. – Onik

+0

Przeczytałem ten artykuł o wycieku pamięci za pomocą 'Handler' i' Context'. W praktyce nie jest to zwykle prawdziwy problem, chyba że masz 'static' zmienną, która odwołuje się do martwego' Kontekstu'. Byłby to prawdziwy wyciek pamięci (np. Pamięć, która nigdy nie jest odzyskiwana). Przez większość czasu te "wycieki" są krótkotrwałe, więc nie są prawdziwymi wyciekami. Jak już powiedziałeś: "Będziesz przeciekać, dopóki' Runnable' nie zostanie zapisany ... "co zwykle trwa bardzo krótko. Nie ma się czym martwić. –

1

Być może trzeba będzie rozważyć nie tylko publikowanie opóźnionego wywoływania do programu obsługi. Podczas uruchamiania zadania w osobnym wątku mogą wystąpić problemy, a działanie jest już zniszczone. Możesz to zrobić i wdrożyć w ten sposób.

W ten sposób każde oczekujące zadanie w kolejce komunikatów programu obsługi zostanie zniszczone po zniszczeniu działania.

Tylko po to, aby wziąć pod uwagę fakt, że użytkownik nie musi wykonywać żadnych zadań po zniszczeniu działania.

0

Odpowiedź brzmi "tak". A tak przy okazji: Memory Leak.

Powiązane problemy