Co dokładnie robi metoda postu?

106

Napotkałem bardzo dziwną funkcję.

Kiedy próbuję uruchomić animację w głównym wątku, to się nie uruchamia. Kiedy uruchomiłem wspomnianą animację za pomocą

getView().post(new Runnable() {
            @Override
            public void run() {
                getView().startAnimation(a);
            }
        });

Zaczyna się.

Wydrukowałem CurrentThreadprzed rozpoczęciem animacji i oba wydruki main.

Oczywiście czegoś mi tu brakuje, bo obaj powinni zacząć animację w głównym wątku ... Domyślam się, że gdy post dodaje zadanie do kolejki, zaczyna się w bardziej "odpowiednim czasie", ale chciałbym wiedzieć co dzieje się tutaj na większej głębokości.

EDYCJA: Pozwólcie, że wyjaśnię - moje pytanie brzmi, dlaczego uruchomienie animacji na poście powoduje jej uruchomienie, a uruchomienie animacji na głównym wątku nie.

Gal
źródło
Czy to zachowanie jest specyficzne dla wersji Androida? Nie mogłem go odtworzyć na Androidzie 4.1.2!
Akdeniz
Odtworzyłem to zachowanie na Androidzie 2.3.3. Ale dla AnimationDrawable! Zwykła Animationinstancja zaczęła pomyślnie animować się przy każdej konfiguracji. W AnimationDrawableprzypadku; kiedy próbujesz rozpocząć onCreate, nie zaczyna się, ponieważ w tym momencie nie jesteś przywiązany do widoku. Nie jest to więc problem z wątkami AnimationDrawable. Może to samo dotyczy Animation? developer.android.com/guide/topics/graphics/…
Akdeniz,

Odpowiedzi:

161

post : post powoduje dodanie Runnable do kolejki wiadomości,

Runnable: Reprezentuje polecenie, które można wykonać. Często używany do uruchamiania kodu w innym wątku.

run () : Rozpoczyna wykonywanie aktywnej części kodu klasy. Ta metoda jest wywoływana, gdy uruchamiany jest wątek, który został utworzony za pomocą klasy implementującej Runnable.

getView().post(new Runnable() {

         @Override
         public void run() {
             getView().startAnimation(a);
         }
     });

kod :getView().startAnimation(a);

w swoim kodzie,

post powoduje, że Runnable ( kod zostanie uruchomiony w innym wątku), aby dodać kolejkę komunikatów.

Więc startAnimation zostanie uruchomiona w nowym wątku, gdy zostanie pobrana z messageQueue

[EDYCJA 1]

Dlaczego używamy nowego wątku zamiast wątku interfejsu użytkownika (wątek główny)?

Wątek interfejsu użytkownika:

  • Po uruchomieniu aplikacji, wątek Ui jest tworzony automatycznie

  • odpowiada za wysyłanie wydarzeń do odpowiednich widżetów, w tym za rysowanie.

  • Jest to również wątek, z którym korzystasz z widżetów Androida

Na przykład, jeśli dotkniesz przycisku a na ekranie, wątek interfejsu użytkownika wysyła zdarzenie dotyku do widżetu, który z kolei ustawia swój stan naciśnięcia i wysyła żądanie unieważnienia do kolejki zdarzeń. Wątek interfejsu użytkownika usuwa z kolejki żądanie i powiadamia widżet o konieczności przerysowania.

Co się stanie, jeśli użytkownik naciśnie przycisk, który będzie działał długo?

((Button)findViewById(R.id.Button1)).setOnClickListener(           
             new OnClickListener() {        
        @Override
    public void onClick(View v) {
            final Bitmap b = loadImageFromNetwork();
            mImageView.setImageBitmap(b);
}
});

Interfejs użytkownika zawiesza się. Program może się nawet zawiesić.

public void onClick(View v) {
  new Thread(new Runnable() {
    public void run() {
        final Bitmap b = loadImageFromNetwork();
        mImageView.setImageBitmap(b);
    }
  }).start();
}

Łamie regułę Androida, która nigdy nie aktualizuje interfejsu użytkownika bezpośrednio z wątku roboczego

Android oferuje kilka sposobów uzyskiwania dostępu do wątku interfejsu użytkownika z innych wątków.

  • Activity.runOnUiThread (do uruchomienia)
  • View.post (Runnable)
  • View.postDelayed (Runnable, long)
  • Treser

Jak poniżej,

View.post (Runnable)

public void onClick(View v) {
  new Thread(new Runnable() {
    public void run() {
      final Bitmap b = loadImageFromNetwork();
      mImageView.post(new Runnable() {
        public void run() {
          mImageView.setImageBitmap(b);
        }
      });
    }
  }).start();
}

Treser

final Handler myHandler = new Handler(Looper.getMainLooper());

(new Thread(new Runnable() {

    @Override
    public void run() {
       final Bitmap b = loadImageFromNetwork();
      myHandler.post(new Runnable() {                           

        @Override
        public void run() {
           mImageView.setImageBitmap(b);
          }
        });
      }
    })).start();                
}

wprowadź opis obrazu tutaj

Po więcej informacji

http://android-developers.blogspot.com/2009/05/peling-threading.html

http://www.aviyehuda.com/blog/2010/12/20/android-multithreading-in-a-ui-environment/

Talha
źródło
15
Dlaczego więc uruchamianie animacji w poście różni się od uruchamiania jej w głównym wątku, skoro oba ostatecznie działają w tym samym wątku?
Gal
Ponieważ ten model pojedynczego wątku może dawać słabą wydajność w aplikacjach dla systemu Android.
Talha
1
Co ma wspólnego słaba wydajność z brakiem animacji?
Gal
17
Nie sądzę, aby to odpowiadało na pytanie, jest to bardziej ogólna odpowiedź dla początkujących, którzy nie wiedzą nic o interfejsie użytkownika i wielowątkowości. To nie wyjaśnia, dlaczego wrzucenie animacji do przodu w kolejce powoduje, że animacja działa; animacja powinna być czymś do wykonania bezpośrednio w wątku interfejsu użytkownika bez użycia jakichkolwiek sztuczek post () lub runOnUiThread ().
carrizo
3
Cała praca interfejsu użytkownika powinna znajdować się w wątku głównym (wątku interfejsu użytkownika). Sztuczka, która sprawia, że ​​animacja działa przez użycie post () zamiast wywołania w głównym wątku od razu, to Time: Jeśli zadzwonisz od razu w głównym wątku, co oznacza, że ​​powiedziałeś „rozpocznij animację teraz”, ale w tej chwili widok nie jest gotowy do animacji (pomiar, rysowanie ...). Ale jeśli umieścisz to w post (), to będzie czekało na startAnimation w kolejce, aby przygotować widok gotowy do anim.
NguyenDat
35

Czy dzieje się to w onCreate czy onCreateView? Jeśli tak, aplikacja może nie znajdować się w stanie, w którym widok jest dołączony do okna. Wiele algorytmów opartych na metrykach widoku może nie działać, ponieważ takie elementy, jak pomiary i położenie widoku, mogły nie zostać obliczone. Animacje systemu Android zazwyczaj wymagają, aby były obsługiwane przez obliczenia w interfejsie użytkownika

View.post faktycznie kolejkuje animację w pętli komunikatów widoku, więc gdy widok zostanie dołączony do okna, wykonuje animację, zamiast wykonywać ją ręcznie.

W rzeczywistości uruchamiasz rzeczy w wątku interfejsu użytkownika, ale w innym czasie

Joe Plante
źródło
Zaakceptowana odpowiedź jest myląca, gdy plakat stwierdza: „post powoduje Runnable (kod zostanie uruchomiony w innym wątku)”. To jest poprawna odpowiedź „Rzeczywiście uruchamiasz rzeczy w wątku interfejsu użytkownika, ale w innym czasie” - plus 1
smitty1
18

Zajrzyj tutaj, aby uzyskać dobrą odpowiedź. view.post () działa prawie tak samo jak handler.post (). Przechodzi do głównej kolejki wątków i jest wykonywany po zakończeniu innych oczekujących zadań. Jeśli wywołasz activity.runOnUiThread () zostanie wywołany natychmiast w wątku interfejsu użytkownika.

Tas Morf
źródło
31
Jedną z ogromnych (i niezwykle pomocnych) różnic, które znalazłem, jest to, że funkcja runnable in view.post () zostanie wywołana po pierwszym wyświetleniu widoku. IE, możesz ustawić, aby uruchamiał animację po rozwinięciu widoku, a następnie w pewnym momencie w przyszłości, ostatecznie dodając go do hierarchii widoków. W tym momencie rozpocznie się animacja i nie będziesz musiał się tym martwić.
DeeV
W rzeczywistości funkcja handler.post () nie zawsze wysyła wiadomość / runnable w głównym wątku. Zależy to od tego, jak został utworzony program obsługi (może być powiązany z pętlą w innym wątku). Z drugiej strony funkcja view.post () zawsze będzie działać w głównym wątku
Yair Kukielka
4

Myślę, że problemem może być metoda cyklu życia, w której wywołujesz metodę post (). Czy robisz to w onCreate ()? jeśli tak, spójrz na to, co znalazłem w dokumentacji onResume () działania:

onResume ()

Dodano na poziomie API 1 void onResume () Wywoływany po onRestoreInstanceState (Bundle), onRestart () lub onPause (), aby Twoja aktywność zaczęła wchodzić w interakcję z użytkownikiem. To dobre miejsce do rozpoczęcia animacji , otwierania urządzeń z wyłącznym dostępem (takich jak kamera) itp.

https://developer.android.com/reference/android/app/Activity.html#onResume ()

Tak więc, jak powiedział Joe Plante, być może widok nie jest gotowy do rozpoczęcia animacji w momencie wywołania post (), więc spróbuj przenieść go do onResume ().

PD: Właściwie, jeśli przeniesiesz kod do onResume (), myślę, że możesz usunąć wywołanie post (), ponieważ jesteś już w wątku interfejsu użytkownika, a widok powinien być gotowy do rozpoczęcia animacji.

carrizo
źródło
3
onResumemoże być wywoływane wiele razy (ekrany przechodzą w stan uśpienia, aktywność przenoszona do tyłu itp.) po tym, jak początkowo „widok jest gotowy”. Jeśli zostanie wywołana z onResume, może być potrzebna flaga do śledzenia pogody, której animacja została już uruchomiona, aby uniknąć (ponownego) uruchomienia się wiele razy.
samis