Repita uma tarefa com um atraso de tempo?

216

Eu tenho uma variável no meu código dizer que é "status".

Quero exibir algum texto no aplicativo, dependendo desse valor da variável. Isso deve ser feito com um atraso de tempo específico.

É como,

  • Verificar valor da variável de status

  • Exibir algum texto

  • Aguarde 10 segundos

  • Verificar valor da variável de status

  • Exibir algum texto

  • Aguarde 15 segundos

e assim por diante. O atraso de tempo pode variar e é definido assim que o texto é exibido.

Eu tentei Thread.sleep(time delay)e falhou. Existe uma maneira melhor de fazer isso?

Kalpa Pathum Welivitigoda
fonte

Respostas:

448

Você deve usar Handlera postDelayedfunção para esse fim. Ele executará seu código com atraso especificado no thread principal da interface do usuário, para que você possa atualizar os controles da interface do usuário.

private int mInterval = 5000; // 5 seconds by default, can be changed later
private Handler mHandler;

@Override
protected void onCreate(Bundle bundle) {

    // your code here

    mHandler = new Handler();
    startRepeatingTask();
}

@Override
public void onDestroy() {
    super.onDestroy();
    stopRepeatingTask();
}

Runnable mStatusChecker = new Runnable() {
    @Override 
    public void run() {
          try {
               updateStatus(); //this function can change value of mInterval.
          } finally {
               // 100% guarantee that this always happens, even if
               // your update method throws an exception
               mHandler.postDelayed(mStatusChecker, mInterval);
          }
    }
};

void startRepeatingTask() {
    mStatusChecker.run(); 
}

void stopRepeatingTask() {
    mHandler.removeCallbacks(mStatusChecker);
}
inazaruk
fonte
1
Obrigado inazaruk, conseguiu fazê-lo funcionar. Foram encontrados 2 erros de digitação pequenos (na parte superior, "Manipulador", não "Manipular" e, na parte inferior, "removeCallbacks", não remova "removeecallback". .. fazer para retribuir o favor no mínimo que você ganhou o meu respeito Atenciosamente Aubrey Bourke.
aubreybourke
20
Bom programa, funciona absolutamente bem. Mas o startRepeatingTask () teve que ser chamado a partir do método onCreate / thread da interface do usuário (demorei algum tempo para perceber isso!), Talvez esse ponto possa ter sido mencionado em algum lugar. Saudações
gkris
1
sua resposta continua dando. Isso me ajudou a sair de um buraco hoje. obrigado.
Dean Blakely
Existe alguma maneira de executar um Runnable repetido dentro do método getView () de um adaptador?
toobsco42
1
Aqui, quando importamos classes, o que devemos importar? android.os.Handler ou java.util.logging.Handler?
EJ Chathuranga
34

Para qualquer pessoa interessada, aqui está uma classe que criei usando o código do inazaruk que cria tudo o necessário (chamei-o de UIUpdater porque o uso para atualizar periodicamente a interface do usuário, mas você pode chamá-lo como quiser):

import android.os.Handler;
/**
 * A class used to perform periodical updates,
 * specified inside a runnable object. An update interval
 * may be specified (otherwise, the class will perform the 
 * update every 2 seconds).
 * 
 * @author Carlos Simões
 */
public class UIUpdater {
        // Create a Handler that uses the Main Looper to run in
        private Handler mHandler = new Handler(Looper.getMainLooper());

        private Runnable mStatusChecker;
        private int UPDATE_INTERVAL = 2000;

        /**
         * Creates an UIUpdater object, that can be used to
         * perform UIUpdates on a specified time interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         */
        public UIUpdater(final Runnable uiUpdater) {
            mStatusChecker = new Runnable() {
                @Override
                public void run() {
                    // Run the passed runnable
                    uiUpdater.run();
                    // Re-run it after the update interval
                    mHandler.postDelayed(this, UPDATE_INTERVAL);
                }
            };
        }

        /**
         * The same as the default constructor, but specifying the
         * intended update interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         * @param interval  The interval over which the routine
         *                  should run (milliseconds).
         */
        public UIUpdater(Runnable uiUpdater, int interval){
            UPDATE_INTERVAL = interval;
            this(uiUpdater);
        }

        /**
         * Starts the periodical update routine (mStatusChecker 
         * adds the callback to the handler).
         */
        public synchronized void startUpdates(){
            mStatusChecker.run();
        }

        /**
         * Stops the periodical update routine from running,
         * by removing the callback.
         */
        public synchronized void stopUpdates(){
            mHandler.removeCallbacks(mStatusChecker);
        }
}

Você pode criar um objeto UIUpdater dentro da sua classe e usá-lo da seguinte maneira:

...
mUIUpdater = new UIUpdater(new Runnable() {
         @Override 
         public void run() {
            // do stuff ...
         }
    });

// Start updates
mUIUpdater.startUpdates();

// Stop updates
mUIUpdater.stopUpdates();
...

Se você quiser usar isso como um atualizador de atividades, coloque a chamada inicial dentro do método onResume () e a chamada final dentro de onPause (), para que as atualizações sejam iniciadas e paradas de acordo com a visibilidade da atividade.

ravemir
fonte
1
Editado: UPDATE_INTERVAL = interval;deve estar antes this(uiUpdater); de UIUpdater(Runnable uiUpdater, int interval)(como o valor de UPDATE_INTERVALé usado e deve ser o passado como parâmetro interval;). Além disso, evite largura de mais de 80 caracteres no código, quando possível (quase sempre;)
Mr_and_Mrs_D
5
Esta classe tem vários problemas. Em primeiro lugar, ele deve ser instanciado no encadeamento principal para poder atualizar a GUI. Você poderia ter resolvido isso passando o looper principal para o construtor manipulador: new Handler(Looper.getMainLooper()). Em segundo lugar, ele não valida argumentos e engole Runnables nulos e intervalos negativos. Por fim, ele não leva em consideração o tempo gasto na uiUpdater.run()linha, nem lida com possíveis exceções geradas por esse método. Também não é thread-safe, você deve fazer starte stopmétodos sincronizados.
Mister Smith
2
Editado até a parte de validação do argumento, pois não tenho o Eclipse aqui para testar o código. Obrigado pelo feedback! É isso que você queria dizer? Sincronizada startUpdates e stopUpdates e colocar uma chamada Looper.getMainLooper () dentro do contructor Handler (eu espero que você pode chamá-lo diretamente da declaração de campo)
ravemir
2
Eu entendi: error: call to this must be first statement in constructortalvez haja uma solução fácil.
msysmilu
4
Upvoting por ter de importação - leva tempo para descobrir onde Handler vem ao programar em Java casualmente
Roman Susi
23

Eu acho que o novo hotness é usar um ScheduledThreadPoolExecutor . Igual a:

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);
Nels Beckman
fonte
Executors.newSingleThreadScheduledExecutor()pode ser outra opção aqui.
Gulshan
13

O temporizador funciona bem. Aqui, eu uso o Timer para pesquisar texto após 1.5s e atualizar a interface do usuário. Espero que ajude.

private Timer _timer = new Timer();

_timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // use runOnUiThread(Runnable action)
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                search();
            }
        });
    }
}, timeInterval);
Kai Wang
fonte
onde você colocou o intervalo de tempo?
Nathiel Barros
1
Oi Nathiel, Acabei de atualizar meu post, espero que ajude! Tempo de intervalo é o segundo parâmetro de Timer.schedule ().
Kai Wang
7

Existem três maneiras de fazer isso:

Use ScheduledThreadPoolExecutor

Um pouco de exagero, já que você não precisa de um pool de Threads.

   //----------------------SCHEDULER-------------------------
    private final ScheduledThreadPoolExecutor executor_ =
            new ScheduledThreadPoolExecutor(1);
     ScheduledFuture<?> schedulerFuture;
   public void  startScheduler() {
       schedulerFuture=  executor_.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(View.GONE);
            }
        }, 0L, 5*MILLI_SEC,  TimeUnit.MILLISECONDS);
    }


    public void  stopScheduler() {
        pageIndexSwitcher.setVisibility(View.VISIBLE);
        schedulerFuture.cancel(false);
        startScheduler();
    }

Usar tarefa de timer

Estilo Android antigo

    //----------------------TIMER  TASK-------------------------

    private Timer carousalTimer;
    private void startTimer() {
        carousalTimer = new Timer(); // At this line a new Thread will be created
        carousalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(INVISIBLE);
            }
        }, 0, 5 * MILLI_SEC); // delay
    }

    void stopTimer() {
        carousalTimer.cancel();
    }

Usar manipulador e executável

Estilo Android moderno

    //----------------------HANDLER-------------------------

    private Handler taskHandler = new android.os.Handler();

    private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            //DO YOUR THINGS
        }
    };

   void startHandler() {
        taskHandler.postDelayed(repeatativeTaskRunnable, 5 * MILLI_SEC);
    }

    void stopHandler() {
        taskHandler.removeCallbacks(repeatativeTaskRunnable);
    }

Manipulador não vazado com atividade / contexto

Declarar uma classe Handler interna que não vaza Memória na sua classe Activity / Fragment

/**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class NonLeakyHandler extends Handler {
        private final WeakReference<FlashActivity> mActivity;

        public NonLeakyHandler(FlashActivity activity) {
            mActivity = new WeakReference<FlashActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            FlashActivity activity = mActivity.get();
            if (activity != null) {
                // ...
            }
        }
    }

Declarar um executável que executará sua tarefa repetitiva na sua classe Activity / Fragment

   private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            new Handler(getMainLooper()).post(new Runnable() {
                @Override
                public void run() {

         //DO YOUR THINGS
        }
    };

Inicialize o objeto Handler em sua Activity / Fragment (aqui FlashActivity é minha classe de atividade)

//Task Handler
private Handler taskHandler = new NonLeakyHandler(FlashActivity.this);

Para repetir uma tarefa após o intervalo de tempo fixo

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

Para parar a repetição de tarefas

taskHandler .removeCallbacks (repeatativeTaskRunnable);

ATUALIZAÇÃO: No Kotlin:

    //update interval for widget
    override val UPDATE_INTERVAL = 1000L

    //Handler to repeat update
    private val updateWidgetHandler = Handler()

    //runnable to update widget
    private var updateWidgetRunnable: Runnable = Runnable {
        run {
            //Update UI
            updateWidget()
            // Re-run it after the update interval
            updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
        }

    }

 // SATART updating in foreground
 override fun onResume() {
        super.onResume()
        updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
    }


    // REMOVE callback if app in background
    override fun onPause() {
        super.onPause()
        updateWidgetHandler.removeCallbacks(updateWidgetRunnable);
    }
Hitesh Sahu
fonte
6

O temporizador é outra maneira de fazer o seu trabalho, mas fique quieto se runOnUiThreadquiser adicionar se estiver trabalhando com a interface do usuário.

    import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

 CheckBox optSingleShot;
 Button btnStart, btnCancel;
 TextView textCounter;

 Timer timer;
 MyTimerTask myTimerTask;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  optSingleShot = (CheckBox)findViewById(R.id.singleshot);
  btnStart = (Button)findViewById(R.id.start);
  btnCancel = (Button)findViewById(R.id.cancel);
  textCounter = (TextView)findViewById(R.id.counter);

  btnStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {

    if(timer != null){
     timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
     //singleshot delay 1000 ms
     timer.schedule(myTimerTask, 1000);
    }else{
     //delay 1000ms, repeat in 5000ms
     timer.schedule(myTimerTask, 1000, 5000);
    }
   }});

  btnCancel.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    if (timer!=null){
     timer.cancel();
     timer = null;
    }
   }
  });

 }

 class MyTimerTask extends TimerTask {

  @Override
  public void run() {
   Calendar calendar = Calendar.getInstance();
   SimpleDateFormat simpleDateFormat = 
     new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
   final String strDate = simpleDateFormat.format(calendar.getTime());

   runOnUiThread(new Runnable(){

    @Override
    public void run() {
     textCounter.setText(strDate);
    }});
  }

 }

}

e xml é ...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:autoLink="web"
    android:text="http://android-er.blogspot.com/"
    android:textStyle="bold" />
<CheckBox 
    android:id="@+id/singleshot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Single Shot"/>

Outra maneira de usar CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Programe uma contagem regressiva até uma hora no futuro, com notificações regulares em intervalos ao longo do caminho. Exemplo de exibição de uma contagem regressiva de 30 segundos em um campo de texto:

Para detalhes

Zar E Ahmer
fonte
1
O manipulador é preferível ao Timer. Veja Timer vs Handler
Suragch,
4

Tente seguir o exemplo que funciona !!!

Use [Handler] no método onCreate (), que utiliza o método postDelayed () que faz com que o Runnable seja adicionado à fila de mensagens, para ser executado após o tempo especificado decorrido ser 0 no exemplo. 1

Consulte este código:

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
    //------------------
    //------------------
    android.os.Handler customHandler = new android.os.Handler();
            customHandler.postDelayed(updateTimerThread, 0);
}

private Runnable updateTimerThread = new Runnable()
{
        public void run()
        {
            //write here whaterver you want to repeat
            customHandler.postDelayed(this, 1000);
        }
};

Gazal Patel
fonte
4

Com base na postagem acima sobre o ScheduledThreadPoolExecutor , criei um utilitário que atendia às minhas necessidades (queria disparar um método a cada 3 segundos):

class MyActivity {
    private ScheduledThreadPoolExecutor mDialogDaemon;

    private void initDebugButtons() {
        Button btnSpawnDialogs = (Button)findViewById(R.id.btn_spawn_dialogs);
        btnSpawnDialogs.setVisibility(View.VISIBLE);
        btnSpawnDialogs.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                spawnDialogs();
            }
        });
    }

    private void spawnDialogs() {
        if (mDialogDaemon != null) {
            mDialogDaemon.shutdown();
            mDialogDaemon = null;
        }
        mDialogDaemon = new ScheduledThreadPoolExecutor(1);
        // This process will execute immediately, then execute every 3 seconds.
        mDialogDaemon.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Do something worthwhile
                    }
                });
            }
        }, 0L, 3000L, TimeUnit.MILLISECONDS);
    }
}
HelloImKevo
fonte
4

No meu caso, tive que executar um processo se uma dessas condições fosse verdadeira: se um processo anterior foi concluído ou se já haviam passado 5 segundos. Então, fiz o seguinte e funcionou muito bem:

private Runnable mStatusChecker;
private Handler mHandler;

class {
method() {
  mStatusChecker = new Runnable() {
            int times = 0;
            @Override
            public void run() {
                if (times < 5) {
                    if (process1.isRead()) {
                        executeProcess2();
                    } else {
                        times++;
                        mHandler.postDelayed(mStatusChecker, 1000);
                    }
                } else {
                    executeProcess2();
                }
            }
        };

        mHandler = new Handler();
        startRepeatingTask();
}

    void startRepeatingTask() {
       mStatusChecker.run();
    }

    void stopRepeatingTask() {
        mHandler.removeCallbacks(mStatusChecker);
    }


}

Se process1 for lido, ele executará process2. Caso contrário, ele incrementa os tempos variáveis ​​e faz com que o manipulador seja executado após um segundo. Ele mantém um loop até o processo1 ser lido ou o tempo ser 5. Quando o tempo é 5, isso significa que 5 segundos se passaram e, a cada segundo, a cláusula if de process1.isRead () é executada.

jvdecamargo
fonte
1

Usando o kotlin e sua Coroutine é bastante fácil, primeiro declare um trabalho em sua classe (melhor no seu viewModel) como este:

private var repeatableJob: Job? = null

quando você quiser criar e iniciar, faça o seguinte:

repeatableJob = viewModelScope.launch {
    while (isActive) {
         delay(5_000)
         loadAlbums(iImageAPI, titleHeader, true)
    }
}
repeatableJob?.start()

e se você quiser terminar:

repeatableJob?.cancel()

PS: viewModelScopeestá disponível apenas em modelos de exibição, você pode usar outros escopos da Coroutine, comowithContext(Dispatchers.IO)

Mais informações: Aqui

Amin Keshavarzian
fonte
0

Para pessoas que usam o Kotlin, a resposta do inazaruk não funcionará, o IDE exigirá que a variável seja inicializada; portanto, em vez de usar o postDelayedinterior do Runnable, usaremos em um método separado.

  • Inicialize Runnableassim:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Inicialize seu runDelayedHandlermétodo assim:

     private fun runDelayedHandler(timeToWait : Long) {
        if (!keepRunning) {
            //Stop your handler
            handler.removeCallbacksAndMessages(null)
            //Do something here, this acts like onHandlerStop
        }
        else {
            //Keep it running
            handler.postDelayed(myRunnable, timeToWait)
        }
    }
  • Como você pode ver, essa abordagem permitirá controlar o tempo de vida da tarefa, acompanhar keepRunninge alterá-la durante o tempo de vida do aplicativo fará o trabalho por você.

Tamim Attafi
fonte