Jak wstrzymać / uśpić wątek lub przetworzyć w Androidzie?


294

Chcę zrobić przerwę między dwoma wierszami kodu, Pozwól, że wyjaśnię trochę:

-> użytkownik klika przycisk (w rzeczywistości karta) i pokazuję go, zmieniając tło tego przycisku:

thisbutton.setBackgroundResource(R.drawable.icon);

-> po powiedzmy 1 sekundę, muszę wrócić do poprzedniego stanu przycisku, zmieniając tło:

thisbutton.setBackgroundResource(R.drawable.defaultcard);

-> Próbowałem wstrzymać wątek między tymi dwoma wierszami kodu za pomocą:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

To jednak nie działa. Może to proces, a nie Wątek, który muszę zatrzymać?

Próbowałem też (ale to nie działa):

new Reminder(5);

Z tym:

public class Reminder {

Timer timer;

        public Reminder(int seconds) {
            timer = new Timer();
            timer.schedule(new RemindTask(), seconds*1000);
        }

        class RemindTask extends TimerTask {
            public void run() {
                System.out.format("Time's up!%n");
                timer.cancel(); //Terminate the timer thread
            }
        }  
    }

Jak mogę zatrzymać / uśpić wątek lub proces?


4
Och, po prostu użyj klasycznego bloku pauzy wątków: while (true) {}
KristoferA

6
@ KristoferA-Huagati.com Nie jestem pewien, czy jesteś sarkastyczny, czy rzeczywiście istnieje magia Dalvik / Android, więc jest to dopuszczalne na Androidzie. Czy możesz wyjaśnić? Przepraszam, że wątpię, ale pytam, ponieważ (!conditionCheck()) {}zwykle jest to odradzane.
Nędzna Zmienna

„Jednak to nie działa”. „Też próbowałem (ale to nie działa)” Jest to klasyczny przykład stwierdzenia, że ​​istnieje problem bez podania objawów. W jaki sposób te próby nie spełniły Twoich wymagań? Czy wątek się nie zatrzymał? Otrzymałeś komunikat o błędzie?
LarsH

Odpowiedzi:


454

Jednym z rozwiązań tego problemu jest użycie metody Handler.postDelayed () . Niektóre materiały szkoleniowe Google sugerują to samo rozwiązanie.

@Override
public void onClick(View v) {
    my_button.setBackgroundResource(R.drawable.icon);

    Handler handler = new Handler(); 
    handler.postDelayed(new Runnable() {
         @Override 
         public void run() { 
              my_button.setBackgroundResource(R.drawable.defaultcard); 
         } 
    }, 2000); 
}

Jednak niektórzy zauważyli, że powyższe rozwiązanie powoduje wyciek pamięci, ponieważ wykorzystuje niestatyczną wewnętrzną i anonimową klasę, która domyślnie zawiera odniesienie do swojej klasy zewnętrznej, działania. Jest to problem, gdy kontekst działania jest odśmiecany.

Bardziej złożone rozwiązanie pozwalające uniknąć wycieku pamięci podklas Handleri Runnableze statycznymi klasami wewnętrznymi wewnątrz działania, ponieważ statyczne klasy wewnętrzne nie zawierają niejawnego odniesienia do ich klasy zewnętrznej:

private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();

public static class MyRunnable implements Runnable {
    private final WeakReference<Activity> mActivity;

    public MyRunnable(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void run() {
        Activity activity = mActivity.get();
        if (activity != null) {
            Button btn = (Button) activity.findViewById(R.id.button);
            btn.setBackgroundResource(R.drawable.defaultcard);
        }
    }
}

private MyRunnable mRunnable = new MyRunnable(this);

public void onClick(View view) {
    my_button.setBackgroundResource(R.drawable.icon);

    // Execute the Runnable in 2 seconds
    mHandler.postDelayed(mRunnable, 2000);
}

Zauważ, że Runnableużywa WeakReference do działania, co jest konieczne w klasie statycznej, która potrzebuje dostępu do interfejsu użytkownika.


3
Działa, ale jest to dość niewygodny sposób wprowadzania opóźnień w całym kodzie, prawda?
Ehtesh Choudhury,

4
Nie jestem pewien, co rozumiesz przez „niewygodny”. Metoda postDelayed programu Handler ma na celu poinformowanie Androida, że ​​po upływie określonego czasu chcesz wykonać trochę kodu.
tronman

27
po 2 latach ten kod mi pomógł! dzięki @tronman !! :)
Melvin Lai

2
Możesz po prostu skopiować go do innej (końcowej) zmiennej, final Button mynewbutton = mybutton;a następnie użyć mynewbuttonw module obsługi i Runnable.
Dzhuneyt

2
@MelvinLai po 5 latach i ten kod mi tylko pomógł! :)
gabrieloliveira

191

Możesz spróbować tego, który jest krótki

SystemClock.sleep(7000);

OSTRZEŻENIE : Nigdy nie rób tego w wątku interfejsu użytkownika.

Użyj tego do spania np. wątek tła.


Pełne rozwiązanie Twojego problemu będzie: To jest dostępne API 1

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View button) {
                button.setBackgroundResource(R.drawable.avatar_dead);
                final long changeTime = 1000L;
                button.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setBackgroundResource(R.drawable.avatar_small);
                    }
                }, changeTime);
            }
        });

Bez tworzenia modułu obsługi tmp. Również to rozwiązanie jest lepsze niż @tronman, ponieważ nie zachowujemy widoku Handlera. Nie mamy też problemu z Handlerem stworzonym przy złym wątku;)

Dokumentacja

publiczny statyczny brak snu (długie ms)

Dodano w interfejsie API poziomu 1

Czeka określoną liczbę milisekund (uptimeMillis) przed powrotem. Podobny do uśpienia (długi), ale nie zgłasza wyjątku InterruptedException ; zdarzenia interrupt () są odraczane do następnej operacji przerywanej. Nie zwraca wartości, dopóki nie upłynie przynajmniej określona liczba milisekund.

Parametry

ms do spania przed powrotem, w milisekundach bezawaryjności.

Kod dla postDelayed from View class:

/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 * @param delayMillis The delay (in milliseconds) until the Runnable
 *        will be executed.
 *
 * @return true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the Runnable will be processed --
 *         if the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 *
 * @see #post
 * @see #removeCallbacks
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}

22
I pozwól, aby interfejs Android się zawiesił? Nie rób tego
shkschneider,

3
Ctrl + Shift + O (automatyczny import Eclipse)
Dawid Drozd

13
Gelldur, nie rozumiesz komentarza @ RichieHH. W porównaniu z odpowiedzią, która została zaakceptowana 3 lata przed opublikowaniem , sugestia nie pomaga OP rozwiązać jego problemu. Powód: Kod, zarówno pokazany przez OP, jak i pokazany w zaakceptowanej odpowiedzi, działa w module obsługi interfejsu użytkownika . Mówienie nie OMG of course do this in background threadma znaczenia, chyba że pokażesz JAK umieścić go w wątku w tle. W tym momencie odkryjesz, że masz odpowiedź bardziej skomplikowaną niż już zaakceptowana. Czy wspomniałem, że trzy lata temu zaakceptowano lepsze rozwiązanie? : P
ToolmakerSteve

2
... zapomniałem wspomnieć, że tym, co czyni tę sugestię szczególnie niecelową w tym pytaniu, jest to, że OP wyraźnie chce wykonać działanie interfejsu użytkownika po tym opóźnieniu. Miałbyś więc rozwiązanie, w którym utworzyłeś wątek w tle (już cięższy niż jest to potrzebne do rozwiązania problemu), spałeś, a następnie (jakoś) musisz wrócić do wątku interfejsu użytkownika. Handler + postRunnabledokonuje tego wszystkiego w jednym kroku. Bez obciążenia systemowego tworzenia drugiego wątku.
ToolmakerSteve

2
@ToolmakerSteve szczęśliwy teraz: D? Główne pytanie brzmi: „Jak wstrzymać / uśpić wątek lub przetworzyć w Androidzie?” więc moja odpowiedź była prosta :). Jeśli ktoś google dla „jak spać wątku” to pytanie pojawi się. Prawdopodobnie szuka mojej odpowiedzi: P. Ale ok, dodałem pełną odpowiedź;)
Dawid Drozd

28

Używam tego:

Thread closeActivity = new Thread(new Runnable() {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
      // Do some stuff
    } catch (Exception e) {
      e.getLocalizedMessage();
    }
  }
});

4
Co e.getLocalizedMessage()ma robić?
Philipp Reichart

Korzystam z e.getLocalizedMessage (), gdy potrzebuję prostego, ogólnego i szybkiego komunikatu o wyjątku
Byt3

1
Ale założę się, że nie robisz tego w sytuacji, o którą prosi OP, w metodzie kliknięcia interfejsu użytkownika, która dokona dalszych aktualizacji interfejsu użytkownika. Przyjęte Handler/postDelayedrozwiązanie ma dwie zalety: (1) pozwala uniknąć narzutu systemowego na drugi wątek, (1) działa na wątku interfejsu użytkownika, dzięki czemu można wprowadzać zmiany interfejsu użytkownika bez powodowania wyjątku.
ToolmakerSteve

1
Nie ma bezpośredniego związku z głównym celem pytania, ale nie powinieneś wychwytywać wyjątku. Raczej złap InterruptedException. Łapiąc wyjątek, złapiesz wszystko, co może pójść nie tak, ukrywając w ten sposób problemy, które w innym przypadku złapałbyś.
Herve Czw

16

Prawdopodobnie nie chcesz tego robić w ten sposób. Umieszczając wyraźny element sleep()w module obsługi zdarzeń klikniętym przyciskiem, faktycznie zablokowałbyś cały interfejs użytkownika na sekundę. Jedną z możliwości jest użycie timera z jednym strzałem . Utwórz TimerTask, aby zmienić kolor tła z powrotem na domyślny, i zaplanuj go w Timerze.

Inną możliwością jest użycie modułu obsługi . Jest tutorial o kimś, kto przeszedł z używania Timera na Handler.

Nawiasem mówiąc, nie można wstrzymać procesu. Proces Java (lub Android) ma co najmniej 1 wątek i możesz tylko uśpić wątki.


14

Używam CountDownTime

new CountDownTimer(5000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        // do something after 1s
    }

    @Override
    public void onFinish() {
        // do something end times 5s
    }

}.start(); 

to jest przydatne.
UzaySan

9

To właśnie zrobiłem pod koniec dnia - teraz działa dobrze:

@Override
    public void onClick(View v) {
        my_button.setBackgroundResource(R.drawable.icon);
        // SLEEP 2 SECONDS HERE ...
        final Handler handler = new Handler(); 
        Timer t = new Timer(); 
        t.schedule(new TimerTask() { 
                public void run() { 
                        handler.post(new Runnable() { 
                                public void run() { 
                                 my_button.setBackgroundResource(R.drawable.defaultcard); 
                                } 
                        }); 
                } 
        }, 2000); 
    }

2
Dlaczego nie postDelayed? Nie jest wymagany zegar.
RichieHH

8

Oprócz odpowiedzi pana Jankowskiego można również użyć postDelayed(). Jest to dostępne na dowolnej Viewkarcie (np. Na karcie) i trwa pewien Runnableokres. Wykonuje Runnablepo tym opóźnieniu.


5

To jest mój przykład

Utwórz narzędzia Java

    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.Intent;

    public class Utils {

        public static void showDummyWaitingDialog(final Context context, final Intent startingIntent) {
            // ...
            final ProgressDialog progressDialog = ProgressDialog.show(context, "Please wait...", "Loading data ...", true);

            new Thread() {
                public void run() {
                    try{
                        // Do some work here
                        sleep(5000);
                    } catch (Exception e) {
                    }
                    // start next intent
                    new Thread() {
                        public void run() {
                        // Dismiss the Dialog 
                        progressDialog.dismiss();
                        // start selected activity
                        if ( startingIntent != null) context.startActivity(startingIntent);
                        }
                    }.start();
                }
            }.start();  

        }

    }    

3
Kolejna odpowiedź dodana wiele lat po tym, jak pytanie zostało już dobrze rozwiązane, nie ma znaczenia dla pytania, ponieważ nie dotyczy zadeklarowanej chęci wykonywania pracy w interfejsie użytkownika. Nie możesz tutaj pracować z interfejsem użytkownika, ponieważ pracujesz w nowym wątku. Nie w wątku interfejsu użytkownika.
ToolmakerSteve

1
Po stronie UX pokazanie użytkownikowi okna dialogowego blokującego ładowanie danych nie jest ... dobre.
2Dee

4

Lub możesz użyć:

android.os.SystemClock.sleep(checkEvery)

co ma tę zaletę, że nie wymaga pakowania try ... catch.


0

Jeśli używasz Kotlina i coroutines , możesz po prostu to zrobić

GlobalScope.launch {
   delay(3000) // In ms
   //Code after sleep
}

A jeśli musisz zaktualizować interfejs użytkownika

GlobalScope.launch {
  delay(3000)
  GlobalScope.launch(Dispatchers.Main) {
    //Action on UI thread
  }
}

0

Wiem, że to stary wątek, ale w dokumentacji Androida znalazłem rozwiązanie, które działało dla mnie bardzo dobrze ...

new CountDownTimer(30000, 1000) {

    public void onTick(long millisUntilFinished) {
        mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
    }

    public void onFinish() {
        mTextField.setText("done!");
    }
}.start();

https://developer.android.com/reference/android/os/CountDownTimer.html

Mam nadzieję, że to pomoże komuś ...


0
  class MyActivity{
    private final Handler handler = new Handler();
    private Runnable yourRunnable;
    protected void onCreate(@Nullable Bundle savedInstanceState) {
       // ....
       this.yourRunnable = new Runnable() {
               @Override
               public void run() {
                   //code
               }
            };

        this.handler.postDelayed(this.yourRunnable, 2000);
       }


     @Override
  protected void onDestroy() {
      // to avoid memory leaks
      this.handler.removeCallbacks(this.yourRunnable);
      }
    }

Aby mieć pewność, że można go połączyć z metodą „klasy statycznej”, jak opisano w odpowiedzi tronmana

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.