Alarmmanager "not made" for this type of processing.
The very documentation, anticipating possible misuse, states:
The Alarm Manager is intended for cases Where you want to have your application code run at a specific time, Even if your application is not Currently running. For normal timing Operations (ticks, timeouts, etc) it is easier and Much more Efficient to use Handler.
Alarmemanager is intended for cases where you want your code to be executed at any given time, even if the application is not running. For normal timing operations (ticking, inaction intervals, etc.) it is easier and much more efficient to use a Handler.
Android provides the class Countdowntimer which allows scheduling a countdown, with notifications at regular intervals, during counting.
However, it does not fully fit the behavior you want, namely add more time to the remaining time.
The solution will be to create a class, like the Countdowntimer, but adapted to our needs:
Countdown.java (Githubgist)
public class CountDown {
//Interface a ser implementada por um listener
public interface CountDownListener {
//Chamado quando o valor de secondsLeft é alterado,
//quando for decrementado ou incrementado.
void onChange(long timeLeft);
//Chamado quando o contador chegar ao fim.
void onEnd();
}
private long fromSeconds;
private long secondsLeft;
private CountDownListener listener;
private boolean isCounting = false;
//Valor em milissegundos de um segundo.
private static final long ONE_SECOND = 1000;
private static final int MSG = 1;
//Constrói o contador com o valor inicial de segundos.
public CountDown(long fromSeconds){
this.fromSeconds = fromSeconds;
handler = new CountDownHandler(this);
}
//Inicia a contagem, a partir do valor inícial.
public synchronized void start(){
if(isCounting){
return;//ou talvez lançar uma excepção
}
isCounting = true;
secondsLeft = fromSeconds;
handler.sendMessage(handler.obtainMessage(MSG));
}
//Pára a contagem.
public synchronized void stop(){
if(!isCounting){
return;//ou talvez lançar uma excepção
}
isCounting = false;
handler.removeMessages(MSG);
}
//Retoma a contagem.
public synchronized void resume(){
if(isCounting || secondsLeft == 0){
return;//ou talvez lançar uma excepção
}
isCounting = true;
handler.sendMessageDelayed(handler.obtainMessage(MSG), ONE_SECOND);
}
//Incrementa o valor do contador.
public synchronized void increaseBy(long value){
secondsLeft += value;
}
//true se o contador estiver contando.
public boolean isCounting(){
return isCounting;
}
//Guarda um listener.
public void setCountDownListener(CountDownListener listener){
this.listener = listener;
}
//Método para formatar um valor em segundos em algo tipo "mm:ss" ou "HH:mm:ss".
public static String secondsToString(long seconds, String format){
return DateFormat.format(format, seconds * ONE_SECOND).toString();
}
private final Handler handler;
//Handler para controlar o contador
private static class CountDownHandler extends Handler
{
private final WeakReference<CountDown> countDownWeakReference;
private CountDownHandler(CountDown countDownInstance) {
countDownWeakReference = new WeakReference<>(countDownInstance);
}
@Override
public void handleMessage(Message msg) {
CountDown countDown = countDownWeakReference.get();
if(countDown == null){
return;
}
synchronized (countDown) {
//Guarda o instante em que inicia o processamento.
long tickStart = SystemClock.elapsedRealtime();
//Se tiver sido parado sai.
if(!countDown.isCounting){
return;
}
//Notifica o listener com o segundos que faltam para terminar.
if (countDown.listener != null) {
countDown.listener.onChange(countDown.secondsLeft);
}
//O contador chegou ao fim, notifica o listener.
if (countDown.secondsLeft == 0) {
countDown.isCounting = false;
if (countDown.listener != null) {
countDown.listener.onEnd();
}
} else {
//decrementa o contador.
countDown.secondsLeft--;
//Obtém o tempo para o próximo decremento.
//Leva em conta o tempo gasto no processamento,
//principalmente o eventualmente gasto pela implementação
// do método onChange() no listener.
long delay = ONE_SECOND - (SystemClock.elapsedRealtime() - tickStart);
//Se o tempo gasto for superior a um segundo, ajusta-o para o próximo.
//Se o tempo gasto no método onChange() for próximo ou
// superior a um segundo ele só será chamado no próximo.
while(delay < 0){
countDown.secondsLeft--;
delay += ONE_SECOND;
}
//Garante o término se o tempo for excedido
if(countDown.secondsLeft < 0){
countDown.listener.onEnd();
}else {
//Agenda o próximo decremento.
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
}
};
}
The class makes it possible to create a counter with a set number of seconds which, when started by the method start()
, shall count down to zero, notifying a Listener, method-associated setCountDownListener()
, when the value of the remaining time is decreased or incremented by the method increaseBy()
and when the counter comes to an end.
It is possible to stop counting with the method stop()
and take it up again with the method resume()
.
Our counter can now be used to control a class that encapsulates the behavior we wish to have during counting.
From what I understand, the behavior you want is:
- Display the value of the remaining time on the screen.
- Trigger an alarm when the remaining time is 15 seconds from the end.
- Can add more time to the counter (already implemented in Countdown)
Countdownbehavior.java
public abstract class CountDownBehavior implements CountDown.CountDownListener {
private final long alarmTime;
private final String displayFormat;
public CountDownBehavior(long alarmTime, String displayFormat){
//Valor em segundos no qual deve ser chamado onAlarm().
this.alarmTime = alarmTime;
//Formato da string passada ao displayTimeLeft().
this.displayFormat = displayFormat;
}
@Override
public void onChange(long timeLeft) {
//Aqui é implementado o comportamento que queremos ter enquanto
//o CountDown "conta".
//Deve informar quando chegar a altura de accionar o alarma.
if(timeLeft == alarmTime)
{
onAlarm();
}
//Informa o valor actual do contador, com o formato indicado por displayFormat.
displayTimeLeft(CountDown.secondsToString(timeLeft, displayFormat));
}
//Metodos a implementar em resposta ao comportamento.
protected abstract void onAlarm();
protected abstract void displayTimeLeft(String timeLeft);
}
When associated with the meter, by the method setCountDownListener()
, the counter will call the methods onChange()
and onEnd()
, every time the count is counted and when it comes to an end.
Behavior response should be implemented in the methods onAlarm()
and displayTimeLeft()
, in a derivative or "online" class, in an anonymous class.
Example of use:
Mainactivity.java
public class MainActivity extends AppCompatActivity {
private TextView textView;
private CountDown countDown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.textView);
//Cria o contador com 10 minuto
countDown = new CountDown(10*60);
//Cria e atribui um CountDownBehavior ao contador
countDown.setCountDownListener(new CountDownBehavior(15, "mm:ss") {
@Override
public void onEnd() {
Toast.makeText(MainActivity.this, "terminou", Toast.LENGTH_SHORT).show();
}
@Override
protected void onAlarm() {
Toast.makeText(MainActivity.this, "alarme", Toast.LENGTH_SHORT).show();
}
@Override
protected void displayTimeLeft(String timeLeft) {
textView.setText(timeLeft);
}
});
}
protected void startClick(View v){
countDown.start();
}
protected void addClick(View v){
countDown.increaseBy(60);
}
protected void stopClick(View v){
countDown.stop();
}
protected void resumeClick(View v){
countDown.resume();
}
@Override
protected void onDestroy() {
//Antes de sair deve parar o contador caso este esteja a contar
if(countDown.isCounting()){
countDown.stop();
}
super.onDestroy();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CountDown"
android:id="@+id/textView" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Iniciar"
android:id="@+id/button1"
android:onClick="startClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mais 1 minuto"
android:id="@+id/button2"
android:onClick="addClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Parar"
android:id="@+id/button"
android:onClick="stopClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="retomar"
android:id="@+id/button3"
android:onClick="resumeClick"/>
</LinearLayout>
I tried to use this Cod posted by vc but could not :(
– Jasar Orion
Why didn’t I get?
– ramaral
ta danod some strange mistakes can be some class that is not importing , I will check here how I am beginner in android still m grip by thing beast
– Jasar Orion
@ramaral Thanks so much for your help! Code worked 100% here.
– LMaker
@ramaral But I still have a question, for that time continue counting when the user closes the application (and remove from the recent app list) only using a service?
– LMaker
Yes, in that case you’ll have to use a service
– ramaral
@ramaral the implementation of this is easy? I never got to create a service yet. It would be easier for me to somehow decrease from an hour x to a y every time the guy opens Activity, instead of the service?
– LMaker
It depends on what you want to do. You may not even need to use a service, can for example save the time when the application was closed and compare with the time when it is opened again and act according to the time that has passed.
– ramaral
@ramaral The idea is to have the contdown, and let the user manage this contdown and the contdown not get lost when they close the app and open again. I thought about doing it that way, saving the time it was closed, but would I do it onstop? So, every time the guy put the app in the background, he’d save it, right?
– LMaker
It depends, in the example code I put in the answer the counter is only stopped in the
onDestroy()
, even if the application goes into the background it continues counting, in this case the time should be recorded in theonDestroy()
.– ramaral