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ê?


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


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

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

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 

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)

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

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.


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

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


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
}

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 !

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.