Come eseguire un thread eseguibile in Android a intervalli definiti?


351

Ho sviluppato un'applicazione per visualizzare del testo a intervalli definiti nella schermata dell'emulatore Android. Sto usando la Handlerclasse. Ecco uno snippet dal mio codice:

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

Quando eseguo questa applicazione, il testo viene visualizzato solo una volta. Perché?


109
Non riesco mai a ricordare come fare un runnable, quindi visito sempre il tuo post su come farlo :))
Adrian Sicaru

2
haha stesso qui
amico

1
lambda sono la strada da percorrere ora per la maggior parte del tempo;)
Serse

@AdrianSicaru: stesso uguale
Sovandara LENG

Risposte:


534

La semplice soluzione per il tuo esempio è:

handler = new Handler();

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

handler.postDelayed(r, 1000);

Oppure possiamo usare il thread normale per esempio (con Runner originale):

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

thread.start();

È possibile considerare l'oggetto eseguibile proprio come un comando che può essere inviato alla coda dei messaggi per l'esecuzione e il gestore come solo un oggetto helper utilizzato per inviare quel comando.

Maggiori dettagli qui http://developer.android.com/reference/android/os/Handler.html


Alex, ho un piccolo dubbio: ora il thread funziona perfettamente e visualizza continuamente il testo, se voglio smettere questo significa che cosa devo fare? Per favore, aiutatemi.
Rajapandian

11
È possibile definire la variabile booleana _stop e impostarla su "true" quando si desidera interrompere. E cambia 'while (true)' in 'while (! _ Stop)', o se viene usato il primo campione, cambia semplicemente in 'if (! _ Stop) handler.postDelayed (this, 1000)'.
alex2k8,

E se volessi riavviare il messaggio?
Sonhja,

e se ho bisogno di un file eseguibile per impostare 8 diverse visualizzazioni di immagini visibili una dopo l'altra, quindi impostarle tutte invisibili nello stesso modo e così via (per creare un'animazione "lampeggiante"), come posso farlo?
Droidman,

1
Se vuoi essere sicuro che Handler sarà collegato al thread principale, dovresti inizializzarlo in questo modo: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka il

47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);

2
Se vuoi essere sicuro che Handler sarà collegato al thread principale, dovresti inizializzarlo in questo modo: new Handler (Looper.getMainLooper ());
Yair Kukielka il

1
Questa soluzione non è equivalente al post originale? Avrebbe eseguito il Runnable solo una volta dopo 100 millisecondi.
tronman,

@YairKukielka la risposta è la soluzione! devi collegare MainLooper. che salvatore di vita!
Houssem Chlegou,

40

Penso che possa migliorare la prima soluzione di Alex2k8 per l'aggiornamento corretto ogni secondo

1. codice originale:

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

2.Analysis

  • Nel suddetto costo, si supponga che il tv.append("Hello Word")costo sia T millisecondi, dopo che il tempo di visualizzazione 500 volte ritardato è di 500 * T millisecondi
  • Aumenterà in ritardo se eseguito a lungo

3. Soluzione

Per evitare ciò, basta cambiare l'ordine di postDelayed (), per evitare ritardi:

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

6
-1 stai assumendo che l'attività che esegui nella corsa () sia un costo un importo costante per ogni corsa, se questa fosse un'operazione su dati dinamici (che in genere è) allora finiresti con più di una corsa () che si verificano a una volta. Questo è il motivo per cui postDelayed viene in genere posizionato alla fine.
Jay,

1
@Jay Purtroppo ti sbagli. Un gestore è associato a un singolo thread (e un Looper che è il metodo di esecuzione di quel thread) + un MessageQueue. Ogni volta che pubblichi un messaggio lo accoda e la volta successiva il looper verifica la coda esegue il metodo di esecuzione della Runnable che hai postato. Dal momento che tutto ciò accade in un solo thread, non è possibile eseguirne più di 1 contemporaneamente. Anche eseguendo prima PostDelayed ti avvicinerai a 1000ms per esecuzione perché internamente utilizza il tempo corrente + 1000 come tempo di esecuzione. Se metti il ​​codice prima del post aggiungi ulteriore ritardo.
zapl,

1
@zapl grazie per il suggerimento sul gestore, ho pensato che avrebbe eseguito più runnable e quindi più thread. Internamente, una condizione come se ((ora corrente - durata)> 1000) funzionerà bene quando la durata della corsa è inferiore o uguale a 1000 ms, tuttavia, quando questo viene superato, sicuramente il timer si verificherà a intervalli non lineari dipende interamente dal tempo di esecuzione del metodo di esecuzione (da qui il mio punto su spese computazionali imprevedibili)
Jay

Se si desidera un periodo fisso, senza conflitti, misurare l'ora di inizio prima di svolgere il lavoro e regolare di conseguenza il ritardo. Vedrai comunque un po 'di latenza se la cpu è occupata, ma può permetterti un periodo più rigido e rilevare se il sistema è sovraccarico (forse per segnalare roba a bassa priorità per arretrare).
Ajax

27

Per attività ripetitive è possibile utilizzare

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

chiamalo come

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

            }
        },500,1000);

Il codice sopra verrà eseguito la prima volta dopo mezzo secondo (500) e si ripeterà dopo ogni secondo (1000)

Dove

l'attività è il metodo da eseguire

dopo il tempo di esecuzione iniziale

( intervallo il tempo per ripetere l'esecuzione)

in secondo luogo

E puoi anche usare CountDownTimer se vuoi eseguire un'attività diverse volte.

    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

E puoi anche farlo con runnable. creare un metodo eseguibile come

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

        }
    };

E chiamalo in entrambi questi modi

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

O

new Thread(runnable).start();//to work in Background 

Per l'opzione n. 3 come posso mettere in pausa / riprendere e anche interrompere definitivamente?
Si8,

creare un'istanza di Handler come Handler handler = new Handler () e rimuoverla come handler.removeCallbacksAndMessages (null);
Zar E Ahmer,

24

Credo che per questo caso tipico, vale a dire eseguire qualcosa con un intervallo fisso, Timersia più appropriato. Qui c'è un semplice esempio:

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

L'uso Timerpresenta alcuni vantaggi:

  • Il ritardo iniziale e l'intervallo possono essere facilmente specificati negli scheduleargomenti della funzione
  • Il timer può essere fermato semplicemente chiamando myTimer.cancel()
  • Se vuoi avere solo un thread in esecuzione, ricorda di chiamare myTimer.cancel() prima di programmarne uno nuovo (se myTimer non è null)

7
Non credo che un timer sia più appropriato in quanto non considera il ciclo di vita di Android. Quando si mette in pausa e si riprende, non vi è alcuna garanzia che il timer funzioni correttamente. Direi che un runnable è la scelta migliore.
Janpan,

1
Ciò significa che quando un'app viene messa in background, un gestore verrà messo in pausa? e quando riacquisterà la concentrazione continuerà (più o meno) come se nulla fosse successo?
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);

5
Questo dovrebbe dare un errore. Nella tua seconda linea stai chiamando la variabile rche è ancora definita.
Seoul,

Se vuoi essere sicuro che Handler sarà collegato al thread principale, dovresti inizializzarlo in questo modo: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka il

solo una risposta ripetitiva!
Hamid,

Come posso mettere in pausa / riprendere il runnable con un clic di imageview?
Si8,

4

Se capisco correttamente la documentazione del metodo Handler.post ():

Fa sì che Runnable r venga aggiunto alla coda dei messaggi. Il runnable verrà eseguito sul thread a cui è collegato questo gestore.

Quindi gli esempi forniti da @ alex2k8, anche se funzionano correttamente, non sono gli stessi. Nel caso in cui Handler.post()venga utilizzato, non vengono creati nuovi thread . È sufficiente pubblicare Runnablesul thread che Handlerdeve essere eseguito da EDT . Successivamente, EDT esegue solo Runnable.run(), nient'altro.

Ricorda: Runnable != Thread.


1
Vero che. Non creare un nuovo thread ogni volta, mai. Il punto centrale di Handler e di altri pool di esecuzione è che uno o due thread estraggano le attività da una coda, per evitare la creazione di thread e GC. Se disponi di un'app che perde davvero, il GC aggiuntivo potrebbe aiutarti a coprire le situazioni di OutOfMemory, ma la soluzione migliore in entrambi i casi è quella di evitare di creare più lavoro del necessario.
Ajax

Quindi il modo migliore per farlo è usare il thread normale basato sulla risposta di 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)
}

Giava

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);
}

1

Un esempio interessante è che puoi vedere continuamente un contatore / cronometro in esecuzione in thread separato. Mostra anche la posizione GPS. Mentre l'attività principale Discussione interfaccia utente è già lì.

Estratto:

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();
}

Per vedere il codice vedi qui:

Esempio di thread che mostra la posizione GPS e l'ora corrente eseguibili insieme al thread dell'interfaccia utente dell'attività principale


1
Suggerimento: se vuoi rendere utile la tua risposta, impara come formattare l'input qui. Quella finestra di anteprima esiste per un motivo.
GhostCat,

0

ora in Kotlin puoi eseguire discussioni in questo modo:

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
}

0

Kotlin con Coroutine

In Kotlin, usando le coroutine puoi fare quanto segue:

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

Provalo qui !

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.