Como executar um thread Runnable no Android em intervalos definidos?

351

Desenvolvi um aplicativo para exibir algum texto em intervalos definidos na tela do emulador do Android. Eu estou usando a Handlerclasse. Aqui está um trecho do meu código:

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

Quando executo esse aplicativo, o texto é exibido apenas uma vez. Por quê?

Rajapandian
fonte
109
Eu nunca consigo lembrar como fazer um executável, então eu sempre visitar o seu post sobre como fazê-lo :))
Adrian Sicaru
2
haha mesmo aqui acasalar tão verdadeiro
NoXSaeeD
11
lambdas são o caminho a percorrer agora a maior parte do tempo;)
Xerus
@AdrianSicaru: same same
Sovandara LENG 17/02

Respostas:

534

A correção simples para o seu exemplo é:

handler = new Handler();

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

handler.postDelayed(r, 1000);

Ou podemos usar thread normal, por exemplo (com o Runner original):

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

thread.start();

Você pode considerar seu objeto executável apenas como um comando que pode ser enviado para a fila de mensagens para execução e o manipulador como apenas um objeto auxiliar usado para enviar esse comando.

Mais detalhes estão aqui http://developer.android.com/reference/android/os/Handler.html

alex2k8
fonte
Agora, o thread está funcionando perfeitamente e exibindo o texto continuamente, se eu quiser parar isso significa o que eu tenho que fazer? Por favor me ajude.
21410 Rajapandian
11
Você pode definir a variável booleana _stop e configurá-la como 'true' quando quiser parar. E mude 'while (true)' para 'while (! _ Stop)' ou, se a primeira amostra usada, altere para 'if (! _ Stop) handler.postDelayed (this, 1000)'.
21139 alex2k8
E se eu quiser reiniciar a mensagem?
Sonhja
e se eu precisar de um executável para definir 8 ImageViews diferentes visíveis um após o outro, configure-os todos invisíveis da mesma maneira e assim por diante (para criar uma animação "piscante"), como posso fazer isso?
Droidman
11
Se você quiser ter certeza de que Handler será anexado ao thread principal, você deve inicializá-lo assim: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);
user2212515
fonte
2
Se você quiser ter certeza de que Handler será anexado ao thread principal, você deve inicializá-lo assim: new Handler (Looper.getMainLooper ());
Yair Kukielka
11
Esta solução não é equivalente à postagem original? Ele executaria o Runnable apenas uma vez após 100 milissegundos.
tronman
A resposta @YairKukielka é a solução! você precisa anexar o MainLooper. um salvador de vida!
Houssem Chlegou
40

Eu acho que pode melhorar a primeira solução do Alex2k8 para atualização correta a cada segundo

1. código original:

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

2.Análise

  • No custo acima, assuma o tv.append("Hello Word")custo T milissegundos, depois que o tempo de exibição 500 vezes atrasado é 500 * T milissegundos
  • Aumentará atrasado quando executado muito tempo

3. Solução

Para evitar isso, basta alterar a ordem de postDelayed (), para evitar atrasos:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}
NguyenDat
fonte
6
-1, você está assumindo que a tarefa que você executa na execução () é uma quantia constante de custos a cada execução; se isso fosse operação em dados dinâmicos (o que normalmente é), você terminaria com mais de uma execução () ocorrendo em uma vez. É por isso que o postDelayed normalmente é colocado no final.
Jay
11
@ Jay Infelizmente você está errado. Um manipulador está associado a um único thread (e a um looper que é o método de execução desse thread) + a um MessageQueue. Cada vez que você publica uma mensagem, coloca-a na fila e na próxima vez que o looper verifica a fila, ele executa o método de execução do Runnable que você postou. Como tudo isso acontece em apenas um Thread, você não pode ter mais de 1 executado ao mesmo tempo. Também fazer o postDelayed primeiro o aproximará de 1000ms por execução, porque internamente usa o tempo atual + 1000 como tempo de execução. Se você colocar o código antes da postagem, adicione um atraso adicional.
Zapl
11
@zapl obrigado pela dica sobre o manipulador, eu assumi que ele estaria executando vários executáveis ​​e, portanto, vários threads. Internamente, porém, uma condição como se ((tempo atual - tempo de execução)> 1000) funcionará bem quando as durações de execução forem menores ou iguais a 1000ms; no entanto, quando isso for excedido, certamente o temporizador ocorrerá em intervalos não lineares dependente inteiramente em tempo de execução do método run (daí o meu ponto sobre a despesa computacional imprevisível)
Jay
Se você deseja um período fixo, sem conflito, meça a hora de início antes de fazer o trabalho e ajuste seu atraso de acordo. Você ainda verá uma pequena latência se a CPU estiver ocupada, mas pode permitir um período mais rigoroso e detectar se o sistema está sobrecarregado (talvez para sinalizar coisas de baixa prioridade para recuar).
Ajax
27

Para repetir tarefas, você pode usar

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

chame assim

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

            }
        },500,1000);

O código acima será executado pela primeira vez após meio segundo (500) e se repetirá após cada segundo (1000)

Onde

tarefa sendo o método a ser executado

após o tempo de execução inicial

( intervalo do tempo para repetir a execução)

Em segundo lugar

E você também pode usar CountDownTimer se desejar executar um número de tarefas várias vezes.

    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 você também pode fazê-lo com executável. crie um método executável como

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

        }
    };

E chamá-lo de ambas as maneiras

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

OU

new Thread(runnable).start();//to work in Background 
Zar E Ahmer
fonte
Para a opção 3, como posso pausar / retomar e também parar permanentemente?
precisa saber é
crie uma instância de Handler como Handler handler = new Handler () e remova-a como handler.removeCallbacksAndMessages (null);
Zar E Ahmer
24

Eu acredito que neste caso típico, ou seja, executar algo com um intervalo fixo, Timeré mais apropriado. Aqui está um exemplo simples:

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

O uso Timertem poucas vantagens:

  • O atraso inicial e o intervalo podem ser facilmente especificados nos scheduleargumentos da função
  • O temporizador pode ser parado simplesmente chamando myTimer.cancel()
  • Se você deseja ter apenas um thread em execução, lembre-se de ligar myTimer.cancel() antes de agendar um novo (se myTimer não for nulo)
iTech
fonte
7
Não acredito que um cronômetro seja mais apropriado, pois não considera o ciclo de vida do andróide. Quando você pausa e continua, não há garantia de que o cronômetro funcione corretamente. Eu argumentaria que um executável é a melhor escolha.
Janpan
11
Isso significa que quando um aplicativo é colocado em segundo plano, um manipulador será pausado? e quando recuperar o foco, continuará (mais ou menos) como se nada tivesse acontecido?
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
fonte
5
Isso deve dar um erro. Na sua segunda linha, você está chamando variável rque ainda está definida.
Seul
Se você quiser ter certeza de que Handler será anexado ao thread principal, você deve inicializá-lo assim: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
resposta apenas repetitiva!
Hamid
Como pausar / retomar o executável com um clique na visualização de imagem?
precisa saber é
4

Se eu entendi corretamente a documentação do método Handler.post ():

Faz com que o Runnable r seja adicionado à fila de mensagens. O executável será executado no encadeamento ao qual esse manipulador está anexado.

Portanto, os exemplos fornecidos pelo @ alex2k8, mesmo que estejam funcionando corretamente, não são os mesmos. Caso Handler.post()seja usado, nenhum novo encadeamento será criado . Você acabou de postar Runnableno thread Handlerpara ser executado pelo EDT . Depois disso, o EDT apenas executa Runnable.run(), nada mais.

Lembre-se: Runnable != Thread.

Damian Walczak
fonte
11
Verdade isso. Não crie um novo thread sempre, sempre. O objetivo principal do manipulador e de outros conjuntos de execução é ter um ou dois encadeamentos extrair tarefas de uma fila, para evitar a criação de encadeamentos e o GC. Se você tiver um aplicativo realmente com vazamento, o GC extra pode ajudar a encobrir as situações OutOfMemory, mas a melhor solução em ambos os casos é evitar criar mais trabalho do que o necessário.
Ajax
Então, a melhor maneira de fazer isso é usando o thread normal baseado na resposta do alex2k8?
Compaq LE2202x 04/04
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)
}

Java

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
fonte
1

Um exemplo interessante é que você pode ver continuamente um contador / cronômetro em execução em thread separado. Também mostrando a localização do GPS. Enquanto a atividade principal, o thread da interface do usuário já estiver lá.

Excerto:

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

Para ver o código, veja aqui:

Exemplo de encadeamento que exibe a localização do GPS e a hora atual executáveis ​​junto com o encadeamento da interface do usuário da atividade principal

Animesh Shrivastav
fonte
11
Dica: se você quiser tornar sua resposta útil - aprenda a formatar a entrada aqui. Essa janela de visualização existe por um motivo.
GhostCat
0

agora no Kotlin você pode executar threads desta maneira:

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
fonte
0

Kotlin com corotinas

No Kotlin, usando corotinas, você pode fazer o seguinte:

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

Experimente aqui !

Willi Mentzel
fonte