Jak uruchomić wątek Runnable na Androidzie w określonych odstępach czasu?

351

Opracowałem aplikację do wyświetlania tekstu w określonych odstępach czasu na ekranie emulatora Androida. Korzystam z Handlerklasy. Oto fragment mojego kodu:

handler = new Handler();
Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");               
    }
};
handler.postDelayed(r, 1000);

Po uruchomieniu tej aplikacji tekst jest wyświetlany tylko raz. Dlaczego?

Rajapandian
źródło
109
Nigdy nie pamiętam, jak zrobić runnable, więc zawsze odwiedzam twój post, jak to zrobić :))
Adrian Sicaru
2
haha to samo kolego, tak prawdziwe
NoXSaeeD
1
lambdas to teraz najlepsza droga;)
Xerus
@AdrianSicaru: same same
Sovandara LENG

Odpowiedzi:

534

Prosta poprawka do twojego przykładu to:

handler = new Handler();

final Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");
        handler.postDelayed(this, 1000);
    }
};

handler.postDelayed(r, 1000);

Lub możemy na przykład użyć normalnego wątku (z oryginalnym Runnerem):

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                sleep(1000);
                handler.post(this);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

thread.start();

Możesz uznać swój uruchamialny obiekt za polecenie, które można wysłać do kolejki komunikatów w celu wykonania, a moduł obsługi jako za obiekt pomocniczy używany do wysłania tego polecenia.

Więcej informacji znajduje się tutaj http://developer.android.com/reference/android/os/Handler.html

alex2k8
źródło
Alex, mam jedną małą wątpliwość: teraz wątek działa idealnie i wyświetla tekst w sposób ciągły, jeśli chcę to zatrzymać, oznacza to, co muszę zrobić? Proszę, pomóż mi.
Rajapandian,
11
Możesz zdefiniować zmienną boolowską _stop i ustawić ją na „true”, kiedy chcesz się zatrzymać. I zmień „while (true)” na „while (! _ Stop)” lub jeśli użyto pierwszej próbki, po prostu zmień na „if (! _ Stop) handler.postDelayed (this, 1000)”.
alex2k8,
Co jeśli chcę ponownie uruchomić wiadomość?
Sonhja
a jeśli potrzebuję programu uruchamiającego, aby ustawić 8 różnych ImageViews widocznych jeden po drugim, to ustaw je wszystkie niewidoczne w ten sam sposób i tak dalej (aby utworzyć animację „migającą”), jak mogę to zrobić?
Droidman
1
Jeśli chcesz mieć pewność, że moduł obsługi zostanie dołączony do głównego wątku, powinieneś go zainicjować w następujący sposób: handler = new moduł obsługi (Looper.getMainLooper ());
Yair Kukielka,
47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);
użytkownik2212515
źródło
2
Jeśli chcesz mieć pewność, że moduł obsługi zostanie dołączony do głównego wątku, powinieneś go zainicjować w następujący sposób: new moduł obsługi (Looper.getMainLooper ());
Yair Kukielka,
1
Czy to rozwiązanie nie jest równoważne z oryginalnym postem? Uruchomi Runnable tylko raz po 100 milisekundach.
tronman
Odpowiedź @YairKukielka jest rozwiązaniem! musisz dołączyć MainLooper. taki wybawca życia!
Houssem Chlegou
40

Myślę, że mogę poprawić pierwsze rozwiązanie Alex2k8 dla aktualizacji poprawnych co sekundę

1. oryginalny kod:

public void run() {
    tv.append("Hello World");
    handler.postDelayed(this, 1000);
}

2. Analiza

  • Przy koszcie powyżej załóżmy tv.append("Hello Word")koszt T milisekund, po wyświetleniu 500 razy opóźniony czas to 500 * T milisekund
  • Zwiększy się ono z opóźnieniem, gdy będzie działać długo

3. Rozwiązanie

Aby tego uniknąć Po prostu zmień kolejność postDelayed (), aby uniknąć opóźnień:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}
NguyenDat
źródło
6
-1 zakładasz, że zadanie, które wykonujesz w run (), kosztuje stałą kwotę każdego uruchomienia, gdyby to była operacja na danych dynamicznych (co zwykle jest), to skończyłbyś się więcej niż jednym uruchomieniem () występującym w pewnego razu. Dlatego postDelayed zazwyczaj umieszcza się na końcu.
Jay
1
@Jay Niestety nie masz racji. Program obsługi jest powiązany z pojedynczym wątkiem (i Looper, który jest metodą uruchamiania tego wątku) + MessageQueue. Za każdym razem, gdy publikujesz wiadomość, umieszczasz ją w kolejce i następnym razem, gdy looper sprawdza kolejkę, wykonuje metodę uruchomienia opublikowanej przez ciebie Runnable. Ponieważ wszystko to dzieje się w jednym wątku, nie można wykonać więcej niż jednego jednocześnie. Wykonanie postDelayed w pierwszej kolejności zbliży Cię do 1000 ms na wykonanie, ponieważ wewnętrznie wykorzystuje bieżący czas + 1000 jako czas wykonania. Jeśli umieścisz kod przed postem, dodasz dodatkowe opóźnienie.
zapl
1
@zapl dziękuję za poradę na temat modułu obsługi, założyłem, że będzie on wykonywać wiele programów runnables, a tym samym wiele wątków. Jednak wewnętrznie warunek taki, jak ((bieżący czas - ostatni czas)> 1000) będzie działał dobrze, gdy czasy trwania są mniejsze lub równe 1000 ms, jednak po przekroczeniu tego z pewnością zegar będzie występował w nieliniowych odstępach zależy całkowicie od czasu wykonania metody uruchamiania (stąd mój punkt widzenia na nieprzewidywalny koszt obliczeniowy)
Jay
Jeśli chcesz na określony czas, konflikt w / out, zmierz czas rozpoczęcia przed rozpoczęciem pracy i odpowiednio dostosuj opóźnienie. Nadal zobaczysz małe opóźnienie, jeśli procesor jest zajęty, ale może to pozwolić ci na surowszy okres i wykrycie, czy system jest przeciążony (być może w celu zasygnalizowania rzeczy o niskim priorytecie do wycofania się).
Ajax
27

Do powtarzania zadania możesz użyć

new Timer().scheduleAtFixedRate(task, runAfterADelayForFirstTime, repeaingTimeInterval);

nazywaj to jak

new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        },500,1000);

Powyższy kod zostanie uruchomiony po raz pierwszy po pół sekundy (500) i powtórzy się po każdej sekundzie (1000)

Gdzie

zadanie będące metodą do wykonania

po czasie do pierwszego wykonania

( przedział czasu na powtórzenie wykonania)

Po drugie

Możesz także użyć CountDownTimer, jeśli chcesz wykonać zadanie kilka razy.

    new CountDownTimer(40000, 1000) { //40000 milli seconds is total time, 1000 milli seconds is time interval

     public void onTick(long millisUntilFinished) {
      }
      public void onFinish() {
     }
    }.start();

//Above codes run 40 times after each second

Możesz to również zrobić przy pomocy Runnable. utwórz metodę uruchamialną, taką jak

Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {

        }
    };

I nazwij to na dwa sposoby

new Handler().postDelayed(runnable, 500 );//where 500 is delayMillis  // to work on mainThread

LUB

new Thread(runnable).start();//to work in Background 
Zar E Ahmer
źródło
W przypadku opcji nr 3 jak mogę wstrzymać / wznowić, a także zatrzymać na stałe?
Si8
utwórz instancję klasy Handler, taką jak Handler handler = new Handler () i usuń ją jak handler.removeCallbacksAndMessages (null);
Zar E Ahmer,
24

Uważam, że w tym typowym przypadku, tj. Aby uruchomić coś ze stałym interwałem, Timerbardziej odpowiednie jest. Oto prosty przykład:

myTimer = new Timer();
myTimer.schedule(new TimerTask() {          
@Override
public void run() {
    // If you want to modify a view in your Activity
    MyActivity.this.runOnUiThread(new Runnable()
        public void run(){
            tv.append("Hello World");
        });
    }
}, 1000, 1000); // initial delay 1 second, interval 1 second

Korzystanie Timerma kilka zalet:

  • Początkowe opóźnienie i interwał można łatwo określić w scheduleargumentach funkcji
  • Timer można zatrzymać, po prostu dzwoniąc myTimer.cancel()
  • Jeśli chcesz mieć tylko jeden wątek, pamiętaj, aby zadzwonić myTimer.cancel() przed zaplanowaniem nowego wątku (jeśli myTimer nie ma wartości null)
iTech
źródło
7
Nie wierzę, że czasomierz jest bardziej odpowiedni, ponieważ nie uwzględnia cyklu życia Androida. Po wstrzymaniu i wznowieniu nie ma gwarancji, że stoper będzie działał poprawnie. Twierdziłbym, że lepszym wyborem jest możliwość uruchomienia.
Janpan
1
Czy to oznacza, że ​​gdy aplikacja zostanie umieszczona w tle, program obsługi zostanie wstrzymany? a kiedy odzyska koncentrację, będzie kontynuował (mniej więcej), jakby nic się nie wydarzyło?
Andrew Gallasch,
17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);
singh arjun
źródło
5
To powinno dać błąd. W drugim wierszu wywołujesz zmienną, rktóra jest jeszcze zdefiniowana.
seul
Jeśli chcesz mieć pewność, że moduł obsługi zostanie dołączony do głównego wątku, powinieneś go zainicjować w następujący sposób: handler = new moduł obsługi (Looper.getMainLooper ());
Yair Kukielka,
tylko powtarzalna odpowiedź!
Hamid
Jak mogę wstrzymać / wznowić uruchamianie za pomocą kliknięcia podglądu obrazu?
Si8,
4

Jeśli dobrze rozumiem dokumentację metody Handler.post ():

Powoduje dodanie Runnable r do kolejki komunikatów. Plik wykonywalny zostanie uruchomiony w wątku, do którego jest dołączony ten moduł obsługi.

Przykłady podane przez @ alex2k8, mimo że działają poprawnie, nie są takie same. W przypadku, gdy Handler.post()jest używany, nie są tworzone nowe wątki . Po prostu publikujesz Runnablew wątku, Handlerktóry ma zostać wykonany przez EDT . Potem EDT wykonuje tylko Runnable.run()nic więcej.

Pamiętaj: Runnable != Thread.

Damian Walczak
źródło
1
Prawda, że. Nie twórz nowego wątku za każdym razem, nigdy. Cały sens modułu obsługi i innych pul wykonawczych polega na tym, że jeden lub dwa wątki ściągają zadania z kolejki, aby uniknąć tworzenia wątków i GC. Jeśli masz naprawdę nieszczelną aplikację, dodatkowy GC może pomóc ukryć sytuacje OutOfMemory, ale lepszym rozwiązaniem w obu przypadkach jest uniknięcie tworzenia większej ilości pracy niż potrzebujesz.
Ajax
Więc lepszym sposobem na to jest użycie normalnego wątku opartego na odpowiedzi alex2k8?
Compaq LE2202x
4

Kotlin

private lateinit var runnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
    val handler = Handler()
    runnable = Runnable {
        // do your work
        handler.postDelayed(runnable, 2000)
    }
    handler.postDelayed(runnable, 2000)
}

Jawa

Runnable runnable;
Handler handler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    handler = new Handler();
    runnable = new Runnable() {
        @Override
        public void run() {
            // do your work
            handler.postDelayed(this, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}
Khemraj
źródło
1

Ciekawym przykładem jest ciągłe obserwowanie licznika / stopera w osobnym wątku. Pokazuje również lokalizację GPS. Podczas gdy główna aktywność Wątek interfejsu użytkownika już tam jest.

Fragment:

try {    
    cnt++; scnt++;
    now=System.currentTimeMillis();
    r=rand.nextInt(6); r++;    
    loc=lm.getLastKnownLocation(best);    

    if(loc!=null) { 
        lat=loc.getLatitude();
        lng=loc.getLongitude(); 
    }    

    Thread.sleep(100); 
    handler.sendMessage(handler.obtainMessage());
} catch (InterruptedException e) {   
    Toast.makeText(this, "Error="+e.toString(), Toast.LENGTH_LONG).show();
}

Aby zobaczyć kod zobacz tutaj:

Przykład wątku przedstawiający lokalizację GPS i bieżący czas, które można uruchomić obok wątku interfejsu użytkownika głównej aktywności

Animesh Shrivastav
źródło
1
Wskazówka: jeśli chcesz, aby twoja odpowiedź była użyteczna - dowiedz się, jak sformatować dane wejściowe tutaj. To okno podglądu istnieje z jakiegoś powodu.
GhostCat,
0

teraz w Kotlin możesz uruchamiać wątki w ten sposób:

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}
fun main(args: Array<String>) {
    val thread = SimpleThread()
    thread.start() // Will output: Thread[Thread-0,5,main] has run.
    val runnable = SimpleRunnable()
    val thread1 = Thread(runnable)
    thread1.start() // Will output: Thread[Thread-1,5,main] has run
}
André Abboud
źródło
0

Kotlin z Coroutines

W Kotlin, używając coroutines, możesz wykonać następujące czynności:

CoroutineScope(Dispatchers.Main).launch { // Main, because UI is changed
    ticker(delayMillis = 1000, initialDelayMillis = 1000).consumeEach {
        tv.append("Hello World")
    }
}

Wypróbuj tutaj !

Willi Mentzel
źródło