Java - Continuacion de un timer despues de cerrar la aplicacion.

 
Vista:

Continuacion de un timer despues de cerrar la aplicacion.

Publicado por Efrain (2 intervenciones) el 12/07/2020 23:25:15
Hola, Sres. tenia tiempo que no pasaba por aca desde FoxPro, ahora estoy en Java..

quiero hacer una especie de rutina, no se si sera sencilla, pero lo que quiero hacer es crear un timer, que cuando se cumpla ese tiempo, se dispare una accion,

Ejemplo:

contador = 0

timer de 10min <<< este llega a cero

contador =+1

contador = 1

vuelve a comenzar el timer.. (10 minutos despues)

contador =+1

contador = 2


Pero q sucede? quiero q este timer siga funcionando aun despues de cerrar la aplicacion, pero si contador llega a cierto numero, que se detenga.

Ejemplo

contador = 5
(fin del timer) (no se usa mas el timer, se detiene porq contador ya esta en su maximo).

si contador < 5 entonces, que el timer vuelva a comenzar desde sus 10 minutos normales.

pero suponiendo que use todos mis contadores y quede en 0 y el usuario no quiere esperar ese tiempo y cierra el programa, entonces, q siga corriendo el timer hasta q contadores sea igual a 5..

Espero haber sido claro y que me puedan entender, realmente me agradaria y agradeceria si alguien me dice como hacerlo, pasos, metodos, toda la informacion que pueda proveerme, soy un poco nuevo en Java.
Muchisimas Gracias!!

Pd: la programacion es en computador.
Valora esta pregunta
Me gusta: Está pregunta es útil y esta claraNo me gusta: Está pregunta no esta clara o no es útil
0
Responder
Imágen de perfil de Kabuto
Val: 3.428
Oro
Ha mantenido su posición en Java (en relación al último mes)
Gráfica de Java

Continuacion de un timer despues de cerrar la aplicacion.

Publicado por Kabuto (1381 intervenciones) el 13/07/2020 13:04:53
Mmmhh..
Creo que tal y como funciona la JVM, no se puede hacer exactamente tal y como lo planteas.

Podemos crear un hilo/thread en segundo plano que funcione como timer.
Pero si cerramos la aplicación, es decir, ponemos fin al programa, esos hilos también se cierran.

Sin embargo, lo que sí se puede hacer es que cuando el usuario pulse el botón de cerrar aplicación, esta no se cierre de verdad, si no que la interfaz de usuario deja de ser visible y parece que el programa se ha cerrado, pero sigue activo con el hilo timer funcionando.
Dicho timer, cuando termine su tarea (contador == 5 o lo que sea...) se encargará de dar orden de cerrar realmente la aplicación y evitar que se quede como proceso muerto consumiendo memoria.

A modo de ejemplo, esta clase crearía un hilo al que le podemos indicar cada cuantos segundos queremos aumentar el contador y cuántos ciclos ha de repetir (hasta donde ha de llegar el contador)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.time.LocalTime;
 
import javax.swing.JOptionPane;
 
public class Temporizador extends Thread{
 
	private int contador;
	private int segundos; //Cada cuantos minutos queremos incrementar contador
	private int ciclos; //Cuantos ciclos queremos incrementar el contador
	private LocalTime inicio;
 
	public Temporizador(int segundos, int ciclos) {
		contador = 0;
		this.segundos = segundos;
		this.ciclos = ciclos;
	}
 
	@Override
	public void run() {
		inicio = LocalTime.now(); //Momento en que se inicia el ciclo
		while (contador < ciclos) {
			LocalTime ahora = LocalTime.now();//Momento en que consultamos tiempo transcurrido
			if (ahora.toSecondOfDay() - inicio.toSecondOfDay() >= segundos) {
				contador++;
				inicio = LocalTime.now(); //Comienza un nuevo ciclo
				JOptionPane.showMessageDialog(null, "Ciclos completados: " + contador,
						"Temporizador", JOptionPane.INFORMATION_MESSAGE);
			}
		}
		JOptionPane.showMessageDialog(null, "Todos los ciclos completados.\nPrograma terminado",
				"Temporizador", JOptionPane.INFORMATION_MESSAGE);
		System.exit(0); //Pone fin a la aplicación
	}
 
}

Y aquí una pequeña interfaz para que el usuario pueda indicar segundos y ciclos, y un botón para activar el temporizador.
Cada vez que se completa un ciclo, sale una ventana popup de aviso.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
 
public class ActivarTemporizador extends JFrame{
 
	private JTextField campoSegundos;
	private JTextField campoCiclos;
	private JButton botonActivar;
 
	public ActivarTemporizador() {
		campoSegundos = new JTextField(4);
		campoCiclos = new JTextField(4);
		botonActivar = new JButton("Activar Temporizador");
		botonActivar.addActionListener(new AccionActivar());
 
		JPanel pnSegundos = new JPanel();
		pnSegundos.add(new JLabel("Segundos: "));
		pnSegundos.add(campoSegundos);
		JPanel pnCiclos = new JPanel();
		pnCiclos.add(new JLabel("Ciclos: "));
		pnCiclos.add(campoCiclos);
 
		JPanel pnDatos = new JPanel();
		pnDatos.setLayout(new BoxLayout(pnDatos, BoxLayout.X_AXIS));
		pnDatos.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
		pnDatos.add(pnSegundos);
		pnDatos.add(pnCiclos);
 
		JPanel pnBoton = new JPanel();
		pnBoton.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
		pnBoton.add(botonActivar);
 
		setLayout(new BorderLayout());
		add(pnDatos, BorderLayout.CENTER);
		add(pnBoton, BorderLayout.SOUTH);
 
		setTitle("Temporizador");
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
	}
 
	class AccionActivar implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				int ciclos = Integer.parseInt(campoCiclos.getText());
				int segundos = Integer.parseInt(campoSegundos.getText());
				//Iniciamos nuevo temporizador con los datos obtenidos
				new Temporizador(segundos, ciclos).start();
			}catch(NumberFormatException nfe) {
				JOptionPane.showMessageDialog(null, "Los campos no tienen datos válidos",
						"Activar Temporizador", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
 
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				new ActivarTemporizador();
			}
		});
	}
}

Una vez iniciado el temporizador, el usuario puede cerrar la interfaz y el temporizador seguirá funcionando aunque aparentemente se ha "cerrado" la aplicación.

Ojo, es un tema delicado, si se cierra la ventana sin haber puesto en marcha el temporizador, la aplicación quedará funcionando en memoria sin poder ser cerrada a no ser que pidamos al sistema operativo que mate este proceso.

Este programa es un ejemplo, si quisiéramos hacer un programa más en serio, habría que controlar si el temporizador está activo o no, para decidir si se cierra el programa por completo, o solo se ha de cerrar la interfaz.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar

Continuacion de un timer despues de cerrar la aplicacion.

Publicado por Efrain (2 intervenciones) el 13/07/2020 14:38:32
Creo que me has resuelto todo el problema, no pense que fuese tan rapido..
Bueno, la idea no es que el contador al llegar a su maximo, ejemplo, hasta 5, el timer se paralice y cierre la aplicacion, no.

El truco es el siguiente.

Imaginemos que comenzamos un juego, y al iniciar te regalan 5 lanzamientos de moneda (no importa que fin cumpla el lanzar la moneda), solo sabes que es importante lanzar la moneda, al ser 5 lanzamientos de moneda, quiere decir que esos son tus contadores, es decir, en estos momentos contador = 5, pero timer tiene que estar en 0'00. ahora, supongamos que lanzaste la moneda 3 veces, quiere decir que contador = 2, pero ya timer tiene que estar en 9'30 (9 minutos, 30 segundos y descontando...) para que cuando llegue timer a 0'00, contador suba 1, es decir, contador = 3, por lo que tendre que esperar unos 20 minutos mas para volver a tener contadores = 5 (esto si no gasto mis contadores (lanzamientos de moneda)).

En definitiva, mientras contadores < 5, el timer nunca puede dejar de funcionar (excepto si la pc esta apagada).

Otra cosa, el timer tiene que funcionar independientemente de la hora de la pc (asi, al modificar la hora del computador, esto no afectaria el funcionamiento del timer ya sea que hagas o no un viaje en el tiempo).

Realmente le agradezco a "KABUTO" por su resolucion.

PD. Oroshimaru agradecera tu sabiduria.
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar
Imágen de perfil de Kabuto
Val: 3.428
Oro
Ha mantenido su posición en Java (en relación al último mes)
Gráfica de Java

Continuacion de un timer despues de cerrar la aplicacion.

Publicado por Kabuto (1381 intervenciones) el 19/07/2020 14:45:32
Hola.
Seguí pensando en este asunto. Como dije, no es posible, o yo no se como, hacer que Java deje un proceso funcionando al cerrar la aplicación.

Pero si he pensado que puede "simularse" ese proceso haciendo que al cerrar aplicación, se guarde en disco los datos actuales de la aplicación.
Así la próxima vez que se abra la aplicación, se pueden recuperar esos datos, y analizando el tiempo que ha estado la aplicación inactiva, se puede determinar si se han recuperado monedas o no durante ese lapso de tiempo y actualizar los valores guardados.
Así parece que el temporizador ha seguido funcionando con la aplicación cerrada, aunque no es cierto.

He modificado la clase Temporizador que escribí y creado un proyecto un poquito más elaborado que el anterior, para poder poner a prueba si esto era posible.

He diseñado una interfaz para poder elegir el tiempo, en segundos, que han de transcurrir para recuperar una moneda. Esta selección se confirma con un botón.
Se muestran las monedas que hay disponibles y otro botón para lanzar la moneda:

lanzaMoneda1

Cuando se pulsa el botón, se descuenta una moneda y se muestra el resultado del lanzamiento en pantalla.

lanzaMoneda2

En fin, lo importante es que al lanzar la primera de las cinco monedas, se activa el Temporizador y va recuperando monedas cada vez que transcurren los segundos indicados.

Si cerramos la aplicación, todo se detiene incluido el Temporizador, pero se guardan en disco los datos internos de la aplicación (el modelo).
Al reabrir aplicación, se recuperan esos datos y se analiza si se han recuperado monedas durante el tiempo que ha estado cerrado e incluso si es necesario volver a activar el Temporizador para que siga contando.

Si se han recuperado monedas durante ese tiempo de inactividad, automáticamente veremos el contador de monedas actualizado con las monedas recuperadas. Así que se produce el efecto de que el programa ha seguido contando el tiempo mientras estaba cerrado.
Pero no es cierto, je, je..

Solo presenta dos inconvenientes.
Si los datos guardados en disco se borran, corrompen, se cambian de ubicación...pues ya no sirve el invento.

El otro inconveniente, es que mencionabas que el timer ha de ser independiente de la hora del PC. Esto tampoco se hacerlo, ni siquiera se si es posible. Todos los programa se rigen por el tiempo que indica el sistema operativo y/o el tiempo que indica el BIOS del motherboard.

Si intencionalmente se modifican el reloj del sistema ( S.O. o motherboard),se podría engañar a la aplicación para recuperar las monedas antes de tiempo.

Pero bueno, no soy ningún experto, quizás alguien más sabio pueda proponer como solucionar esto.

Dejo a continuación mi código para quien pueda servirle. Lo he separado en varias clases: Vista, Modelo, Controlador,...
Está documentado y comentado, así que creo que se entiende bastante bien lo que hace el código en cada momento, si no, basta con preguntar.

Junto con el mensaje adjunto también un zip con todo el proyecto. Un saludo.

Modelo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * Representa el Modelo de datos del programa:<br>
 * <ul>
 * <li><b>Monedas</b> de las que dispone el usuario.</li>
 * <li><b>Tiempo</b> en segundos que ha de transcurrir para poder recuperar una moneda.</li>
 * <li><b>Momento</b> en el que se activa el Temporizador que recupera monedas. Para esto usamos
 * la clase LocalTime.</li>
 * <li><b>Fecha</b> en el que se activa el Temporizador. Usamos la clase LocalDate.</li>
 * </ul>
 * Esta clase implemente la interfaz <i>Serializable</i> para poder guardar una copia del objeto
 * instanciado en disco. De este modo los datos del programa son recuperables cada vez que se abre
 * la aplicación.
 * @author Kabuto
 * @see <a href="https://www.lawebdelprogramador.com/foros/Java/1751736-Continuacion-de-un-timer-despues-de-cerrar-la-aplicacion.html">
 * Web del Programador</a>
 */
@SuppressWarnings("serial")
public class Modelo implements Serializable{
 
	private int monedas;
	private int tiempoNuevaMoneda; //Cada cuantos segundos recuperamos 1 moneda
	private LocalTime inicioTimer; //Momento en que el temporizar se activa
	private LocalDate fechaInicio; //Fecha en que se inicia
 
	public Modelo() {
		monedas = 5;
		tiempoNuevaMoneda = 20;  //20 segundos valor por defecto
	}
 
	/**
	 * Retorna la cantidad de monedas actual.
	 * @return Monedas actuales.
	 */
	public int getMonedas() {
		return monedas;
	}
 
	/**
	 * Retorna la cantidad de tiempo en segundos establecidos
	 * actualmente necesarios para recupera una moneda.
	 * @return Segundos que han de transcurror para recuperar monedas.
	 */
	public int getTiempoNuevaMoneda() {
		return tiempoNuevaMoneda;
	}
 
	/**
	 * Actualiza el valor actual de monedas. El valor máximo
	 * está limitado a solo 5 monedas.<br>Si se alcanza este límite
	 * se considera que el Temporizador ya cumplió su función y se
	 * ha desactivado. Por lo tanto se eliminan los datos guardados
	 * de hora y fecha de su última activación.<br>
	 * Este método lo invoca el Controlador cuando al abrir el programa
	 * se analiza si ha habido recuperación de monedas durante el tiempo
	 * que el programa ha estado inactivo.
	 * @param monedas Cantidad de monedas a establecer.
	 */
	public void setMonedas(int monedas) {
		if (monedas > 5) {
			this.monedas = 5;
			inicioTimer = null;
			fechaInicio = null;
		}
		else
			this.monedas = monedas;
	}
 
	/**
	 * Comunica al Modelo que se ha recuperado una moneda para que
	 * actulice el dato. Si se alcanza el límite de 5 monedas, se
	 * eliminan los datos de hora y fecha de la última activación del
	 * Temporizador porque ya no son necesarios.<br>
	 * Este método lo invoca el Controlador cada vez que el Temporizador
	 * ha contado los segundos necesarios para recuperar una moneda.
	 */
	public void recuperaMoneda() {
		monedas++;
		if (monedas == 5) {
			inicioTimer = null;
			fechaInicio = null;
		}
	}
 
	/**
	 * Retorna el momento del tiempo en el que
	 * se activó el Temporizador la última vez.
	 * @return Objeto LocalTime con la hora de activación.
	 */
	public LocalTime getInicioTimer() {
		return inicioTimer;
	}
 
	/**
	 * Actualiza el momento del tiempo en el que se activó el
	 * Temporizador.<br>Este método lo invoca el Controlador
	 * cuando al abrir el programa, se ha configurado un nuevo Temporizador
	 * en función del tiempo que el programa ha permanecido cerrado.
	 * @param nuevoTimer Objeto LocalTime con la nueva hora de activación.
	 */
	public void setInicioTimer(LocalTime nuevoTimer) {
		inicioTimer = nuevoTimer;
	}
 
	/**
	 * Retorna la fecha en que se activó el temporizador.<br>
	 * La fecha es útil para saber si el programa se cerró por
	 * última vez el dia anterior o incluso más atrás en el tiempo,
	 * en cuyo caso automáticamente se recuperan las 5 monedas.<br>
	 * Este método lo requiere el Controlador cuando está analizando
	 * cuánto tiempo ha estado la aplicación cerrada.
	 * @return Objeto LocalDate con la fecha de activación del Temporizador.
	 */
	public LocalDate getFechaInicio() {
		return fechaInicio;
	}
 
	/**
	 * Actualiza el tiempo necesario para poder recuperar una moneda.
	 * @param tiempoNuevaMoneda Segundos que han de transcurrir para recuperar monedas.
	 */
	public void setTiempoNuevaMoneda(int tiempoNuevaMoneda) {
		this.tiempoNuevaMoneda = tiempoNuevaMoneda;
	}
 
	/**
	 * Comunica al Modelo que se ha lanzado una moneda para que decremente su cantidad.<br>
	 * Si antes de este lanzamiento teníamos las 5 monedas completas
	 * significa que el Temporizador ha de ser activado así que guardamos
	 * hora y fecha de este lanzamiento.<br>
	 */
	public void lanzarMoneda() {
		if (monedas == 5) {//Vamos a gastar la primera moneda, así que guardamos momento para el timer
			inicioTimer = LocalTime.now();
			fechaInicio = LocalDate.now();
		}
 
		monedas--;
	}
 
 
}

Vista
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * Construye un Contenedor con la interfaz de usuario que se mostrará en el marco principal.<br>
 * La interfaz consiste básicamente en dos paneles:<br>
 * <ul>
 * <li>El panel superior ofrece un selector de tiempo en segundos para decidir cada cuántos segundos
 * se ha de recuperar una moneda y un botón para establecer esta selección y que quede guardada.</li>
 * <li>El panel inferior ofrece un botón para "lanzar una moneda". Se muestra el resultado del
 * lanzamiento (cara o cruz) y la cantidad de monedas actuales.<br>Estas monedas disminuyen con cada
 * lanzamiento y aumentan cuando ha transcurrido el tiempo necesario tras el primer lanzamiento.</li>
 * </ul>
 * @author Kabuto
 * @see <a href="https://www.lawebdelprogramador.com/foros/Java/1751736-Continuacion-de-un-timer-despues-de-cerrar-la-aplicacion.html">
 * Web del Programador</a>
 */
@SuppressWarnings("serial")
public class Vista extends Container{
 
	private JSpinner spinSegundos;
	private MiLabel labelMonedas; //Monedas restantes
	private MiLabel labelResultado; //Resultado lanzar moneda(cara o cruz)
	private JButton botonSetTiempo;
	private JButton botonLanzar;
	private Controlador controlador;
 
	public Vista(Controlador c) {
		controlador = c;
		setLayout(new BorderLayout());
		add(new PanelNorte(), BorderLayout.NORTH);
		add(new PanelCentro(), BorderLayout.CENTER);
	}
 
	/**
	 * Clase interna para modelar el panel superior.<br>Consta de un JSpinner
	 * para seleccionar el tiempo en segundos. La selección está limitada
	 * entre 0 segundos y 1200 (20 minutos), siendo modificable en intervalos
	 * de 10 segundos.<br>El tiempo seleccionado solo será efectivo cuando se
	 * pulse el botón "Establecer Tiempo", quedando además guardado en el Modelo.
	 */
	private class PanelNorte extends JPanel {
 
		public PanelNorte() {
			spinSegundos = new JSpinner();
			spinSegundos.setModel(new SpinnerNumberModel(10, 0, 1200, 10));
			spinSegundos.setPreferredSize(new Dimension(70, 25));
			spinSegundos.setFont(new Font("Verdana", Font.PLAIN, 18));
			botonSetTiempo = new JButton("Establecer tiempo");
			botonSetTiempo.addActionListener(new AccionSetTiempo());
 
			JPanel pnTiempo = new JPanel();
			pnTiempo.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
			pnTiempo.add(new MiLabel("Segundos para nueva Moneda: ", Font.PLAIN, 20, Color.BLACK));
			pnTiempo.add(spinSegundos);
 
			JPanel pnSetTiempo = new JPanel();
			pnSetTiempo.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
			pnSetTiempo.add(botonSetTiempo);
 
			setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
			setBorder(BorderFactory.createCompoundBorder(
					BorderFactory.createEmptyBorder(20, 20, 20, 20),
					BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));
			add(pnTiempo);
			add(pnSetTiempo);
		}
	}
 
	/**
	 * Clase interna para modelar el panel inferior.<br>Consta de un JLabel que muestra
	 * la cantidad de monedas disponibles actualmente. Esta cantidad disminuye cuando se
	 * pulsa el botón lanzar moneda, inciandose además el Temporizador en un hilo paralelo,
	 * y aumentará cuando hayan transcurridos los segundos necesarios.<br>
	 * Otro JLabel muestra el resultado del lanzamiento de la moneda: cara o cruz.
	 */
	private class PanelCentro extends JPanel{
 
		public PanelCentro() {
			labelMonedas = new MiLabel("5", Font.ITALIC, 20, Color.RED);
			setMonedas();
			labelResultado = new MiLabel("    ", Font.BOLD, 30, Color.BLUE);
			botonLanzar = new JButton("Lanzar Moneda");
			botonLanzar.addActionListener(new AccionLanzarMoneda());
 
			JPanel pnMonedas = new JPanel();
			pnMonedas.add(new MiLabel("Monedas restantes: ", Font.PLAIN, 20, Color.BLACK));
			pnMonedas.add(labelMonedas);
			JPanel pnBoton = new JPanel();
			pnBoton.add(botonLanzar);
			JPanel pnResultado = new JPanel();
			pnResultado.add(labelResultado);
 
			setLayout(new GridLayout(3,1));
			setBorder(BorderFactory.createCompoundBorder(
					BorderFactory.createEmptyBorder(20, 20, 20, 20),
					BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));
			add(pnMonedas);
			add(pnBoton);
			add(pnResultado);
 
		}
	}
 
	/**
	 * Clase interna para crear un JLabel con un estilo aplicado.<br>
	 * Usa la fuente Verdana y mediante su constructor podemos decidir
	 * texto, estilo(plano, cursiva, negrita), tamaño y color de texto.
	 */
	private class MiLabel extends JLabel{
 
		public MiLabel(String texto, int estilo, int size, Color color) {
			super(texto);
			setFont(new Font("Verdana", estilo, size));
			setForeground(color);
		}
	}
 
	/**
	 * Clase interna para modelar la acción que se produce al pulsar el
	 * botón "Establecer Tiempo" del panel superior.<br>Simplemente se
	 * recoge el valor que el usuario ha establecido como tiempo para
	 * recuperar monedas, y se transmite al Modelo.
	 */
	private class AccionSetTiempo implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			controlador.setTiempoModelo(spinSegundos.getValue());
		}
	}
 
	/**
	 * Clase interna para modelar la acción que se produce al pulsar el
	 * botón "Lanzar Moneda".<br>El Controlador comprueba si quedan monedas
	 * disponibles, si las hay, descontará una moneda e iniciará el Temporizador
	 * con el momento actual para determinar cuando se recupera.<br>
	 * Tras esto, se simula el lanzamiento de moneda con un objeto Random. Este objeto
	 * nos da un entero al azar entre 0 y 99, si el entero es par se considera Cara y
	 * si es impar será Cruz. Este resultado se muestra en el JLabel correspondiente.<br>
	 * Si no hay monedas disponibles, una ventana emergente advertirá al usuario.
	 */
	private class AccionLanzarMoneda implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			if (controlador.lanzarMoneda()) {
				Random moneda = new Random();
				if (moneda.nextInt(100) % 2 == 0) //Si sale un par es CARA, impar es CRUZ
					labelResultado.setText("CARA");
				else
					labelResultado.setText("CRUZ");
 
				setMonedas();
			}
			else {
				JOptionPane.showMessageDialog(null, "No te quedan monedas en este momento.",
						"Lanzar Moneda", JOptionPane.WARNING_MESSAGE);
			}
		}
	}
 
	/**
	 * Actualiza la cantidad de monedas actuales según los valores del Modelo.
	 */
	public void setMonedas() {
		labelMonedas.setText(Integer.toString(controlador.getMonedasModelo()));
	}
 
	/**
	 * Actualiza el valor del selector de segundos de tiempo según los valores del Modelo.
	 */
	public void setSegundos() {
		spinSegundos.setValue(controlador.getSegundosModelo());
	}
 
}

Controlador
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/**
 * Clase Controlador para coordinar la Vista con el Modelo (Patrón MVC).<br>
 * Además de esa coordinación, se encarga de guardar y recuperar la copia del Modelo
 * que se hace en disco.<br>También se encarga de analizar el tiempo que la aplicación
 * ha estado cerrada tras la última sesión para calcular cuántas monedas han podido
 * recuperarse en ese lapso de tiempo y decidir si hay que reactivar el Temporizador o no.
 * @author Kabuto
 * @see <a href="https://www.lawebdelprogramador.com/foros/Java/1751736-Continuacion-de-un-timer-despues-de-cerrar-la-aplicacion.html">
 * Web del Programador</a>
 */
public class Controlador {
 
	private Vista vista;
	private Modelo modelo;
	private Temporizador timer;
 
	public Controlador() {
		if (!leerModelo()) //No se pudieron leer datos de disco
			modelo = new Modelo();
	}
 
	/**
	 * Establece la referencia a la Vista y ordena actualizar
	 * los valores que se muestran para las monedas y los segundos
	 * de recuperación, según lo que indique el Modelo.
	 * @param vista Objeto Vista que muestra la GUI al usuario.
	 */
	public void setVista(Vista vista) {
		this.vista = vista;
		this.vista.setMonedas();
		this.vista.setSegundos();
	}
 
	/**
	 * Pregunta al Modelo cuántas monedas le constan
	 * y retorna dicho valor.<br>Este método lo invoca
	 * la Vista para mostrar el dato en pantalla.
	 * @return Cantidad de monedas que constan en el Modelo.
	 */
	public int getMonedasModelo() {
		return modelo.getMonedas();
	}
 
	/**
	 * Informa al Modelo que ha de recuperar una moneda,
	 * pide a la Vista que se actualice y se hace un guardado
	 * en disco.<br>Este método lo invoca la clase Temporizador
	 * cuando han transcurrido los segundos necesarios para recuperar
	 * una moneda.
	 */
	public void modeloRecuperaMoneda() {
		modelo.recuperaMoneda();
		vista.setMonedas();
		guardarModelo();
	}
 
	/**
	 * Pregunta al Modelo cuantos segundos necesarios
	 * para recuperar monedas le constan y retorna dicho valor.<br>
	 * Este método lo invoca la Vista para mostrar el dato
	 * en pantalla.
	 * @return Segundos necesarios para recuperar monedas.
	 */
	public int getSegundosModelo() {
		return modelo.getTiempoNuevaMoneda();
	}
 
	/**
	 * Pregunta al Modelo cuál es el momento del día a partir
	 * del cuál el Temporizador tiene que iniciarse. Este momento
	 * se decide cuando se lanza la primera de las 5 monedas.<br>
	 * El Temporizador invoca este método cuando se inicia.
	 * @return Objeto LocalTime que representa el momento del día
	 * a partir del cuál el Temporizador ha de empezar a contar segundos.
	 */
	public LocalTime getTimerModelo() {
		return modelo.getInicioTimer();
	}
 
	/**
	 * Transmite al Modelo un nuevo momento de inicio para el Temporizador.<br>
	 * Este método lo invoca el Temporizador, cuando se reactiva tras haber estado
	 * la aplicación cerrada y el propio Controlador le informa de cuántos segundos
	 * ha de incrementar su momento de inicio para seguir contando.
	 * @param nuevoTimer Objeto LocalTime con el momento de empezar a contar actualizado.
	 */
	public void setTimerModelo(LocalTime nuevoTimer) {
		modelo.setInicioTimer(nuevoTimer);
	}
 
	/**
	 * Comunica al Modelo la nueva selección de segundos necesarios para
	 * recuperar monedas y se hace un guardado en disco.<br>Este método
	 * se invoca desde la Vista, cuando el usuario pulsa el botón
	 * "Establecer Tiempo".
	 * @param tiempo Valor seleccionado por el usuario en el JSpinner
	 * de la Vista.
	 */
	public void setTiempoModelo(Object tiempo) {
		modelo.setTiempoNuevaMoneda((int) tiempo);
		guardarModelo();
	}
 
	/**
	 * Solicita lanzar una moneda.<br>Si el Modelo indica que tiene monedas disponibles
	 * se hace el lanzamiento. A continuación se consulta si esta ha sido la primera de las
	 * cinco monedas disponibles, en cuyo caso, se activa una nueva instancia del Temporizador.<br>
	 * @return <i>True</i> si se lanzó una moneda,<i>False</i> en caso contrario, debido a que no
	 * hay monedas disponibles.
	 */
	public boolean lanzarMoneda() {
		if (modelo.getMonedas() > 0) {
			modelo.lanzarMoneda();
			if (modelo.getMonedas() == 4) {//En este caso es que hemos lanzado la primera moneda
				//Hay que activar el temporizador
				timer = new Temporizador(this);
				timer.start();
			}
			return true;
		}
		else
			return false;
	}
 
	/**
	 * Hace una copia en disco de la instancia del Modelo que hay en memoria, de este modo
	 * se pueden recuperar los datos que alberga cada vez que se inicia la aplicación.<br>
	 * La copia se hace en el directorio <i>home</i> del usuario, según indique el sistema
	 * operativo.
	 */
	public void guardarModelo() {
		File fichero = new File(System.getProperty("user.home") + "/LanzaMonedas/modelo.dat");
		try {
			if (!fichero.exists()) {
				File carpeta = new File(System.getProperty("user.home") + "/LanzaMonedas");
				carpeta.mkdir();
				fichero.createNewFile();
			}
			ObjectOutputStream obs = new ObjectOutputStream(new FileOutputStream(fichero));
			obs.writeObject(modelo);
			obs.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	/**
	 * Trata de recuperar la copia guardada del Modelo en disco, que debería estar
	 * en el directorio <i>home</i> del usuario indicado por el sistema operativo.<br>
	 * Si logra recuperar con éxito esta copia, el Controlador analizará si es necesario
	 * reactivar el Temporizador, actualizando sus valores según el tiempo que haya estado
	 * inactiva la aplicación.
	 * @return<i>True</i> si se pudo recuperar el Modelo guardado, <i>False</i> si no fue posible
	 * lo que implica que se creará un nuevo Modelo con los valores por defecto.
	 */
	public boolean leerModelo() {
		File fichero = new File(System.getProperty("user.home") + "/LanzaMonedas/modelo.dat");
		if (fichero.exists()) {
			ObjectInputStream ois;
			try {
				ois = new ObjectInputStream(new FileInputStream(fichero));
				Object aux = ois.readObject();
				if (aux instanceof Modelo) {
					modelo = (Modelo)aux;
					analizarTiempoGuardado();
				}
				ois.close();
				return true;
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	}
 
	/**
	 * Al recuperar el Modelo guardado en disco, hay que analizar si es
	 * necesario reactivar el Temporizador, para simular que ha permanecido
	 * contando segundos incluso con la aplicación cerrada.<br>
	 * Si el Modelo recuperado indica que se tenían las cinco monedas intactas
	 * no será necesario reactivar Temporizador, porque ya estaba inactivo cuando
	 * se cerró aplicación la última vez.<br>
	 * Si no se tienen las cinco monedas, signfica que el Temporizador sí estaba
	 * funcionando cuando se cerró la aplicación. En este caso se compara el tiempo
	 * que según el Modelo se activó el Temporizador anterior con el tiempo actual.<br>
	 * De esta comparación se determina cuántas monedas han podido ser recuperadas durante
	 * el tiempo que la aplicación ha estado inactiva y si aún no se han recuperado las
	 * cinco monedas, se activará un nuevo Temporizador con los valores actualizados como si
	 * hubiera seguido funcionando todo el tiempo de inactividad.
	 */
	private void analizarTiempoGuardado() {
		if (modelo.getMonedas() != 5) {
			/*
			 * El Modelo recuperado de disco indica que cuando se guardó
			 * no se tenían las 5 monedas. Esto implica que había un timer
			 * activo cuando se cerró el programa.
			 * Ahora debemos decidir si hay que reactivar el timer, pero primero
			 * hay que analizar cuánto tiempo ha transcurrido desde que se cerró el
			 * programa para determinar si durante este lapso de tiempo ya se ha
			 * recuperado alguna moneda, o todas.
			 */
			int monedasRecuperadas = 0;
			int segundosAhora = 0;
			int segundosTimer = 0;
			LocalTime inicioTimer = modelo.getInicioTimer();
			LocalTime ahora = LocalTime.now();
			/*
			 * Primero comprobamos si los "momentos" de tiempo
			 * son del mismo dia o no. Si el "ahora" es de un dia posterior,
			 * las monedas se consideran recuperadas
			 */
			if (LocalDate.now().getDayOfYear() > modelo.getFechaInicio().getDayOfYear())
				monedasRecuperadas = 5;
			else {
				segundosAhora = ahora.toSecondOfDay();
				segundosTimer = inicioTimer.toSecondOfDay();
				monedasRecuperadas = (segundosAhora - segundosTimer) / modelo.getTiempoNuevaMoneda();
			}
			modelo.setMonedas(modelo.getMonedas() + monedasRecuperadas);
			if (modelo.getMonedas() !=  5) {
				/*
				 * Seguimos sin tener las 5 monedas. Hay que activar timer.
				 * A su momento de inicio, le añadiremos el tiempo que no ha estado contando mientras
				 * el programa estuvo cerrado.
				 */
				timer = new Temporizador(this);
				timer.agregarTiempo(segundosAhora - segundosTimer);
				timer.start();
			}
		}
	}
 
}

Temporizador
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * Esta clase genera un hilo de ejecución donde simula un temporizador para decidir cuándo
 * ha transcurrido el suficiente tiempo para recuperar una moneda.<br>
 * El Modelo le indica cada cuántos segundos ha de recuperar una moneda y a partir de que
 * momento del tiempo ha de empezar a contar.<br>
 * El hilo se ejecutará hasta haber recuperado las 5 monedas o el programa se cierre.
 * Si el programa se cierra sin haberse recuperado las 5 monedas, la próxima vez que se active
 * la aplicación el Controlador analizará el tiempo que la aplicación ha estado cerrada y decidirá
 * si hay que reactivar el Temporizador en el caso de que no haya transcurrido suficiente tiempo
 * para que las monedas se hayan recuperado en su totalidad.<br>En este caso, el temporizador se reactiva
 * pero se le actualiza el momento desde que tiene que contar en función del tiempo que la aplicación estuvo cerrada.
 * <br>De este modo, parece que el temporizador hubiera permanecido activo incluso con la aplicación cerrada.
 * @author Kabuto
 * @see <a href="https://www.lawebdelprogramador.com/foros/Java/1751736-Continuacion-de-un-timer-despues-de-cerrar-la-aplicacion.html">
 * Web del Programador</a>
 */
public class Temporizador extends Thread{
 
	private int segundos;
	private LocalTime inicio;
	private Controlador controlador;
 
	public Temporizador(Controlador c) {
		controlador = c;
		segundos = controlador.getSegundosModelo();
		inicio = controlador.getTimerModelo();
	}
 
	/**
	 * Actualiza el momento del tiempo desde donde el Temporizador
	 * tiene que contar. Esto ocurre cuando se reabre la aplicación
	 * y se ha de seguir contando para recuperar monedas.<br>
	 * También se encarga de actualizar los datos del Modelo con el nuevo
	 * valor del LocalTime y guardarlo en disco.
	 * @param segundos Cantidad de segundos que se ha de aumentar el contador.<br>
	 * Estos segundos son el tiempo que la aplicación ha estado cerrada desde la
	 * última ejecución.
	 */
	public void agregarTiempo(int segundos) {
		inicio = inicio.plusSeconds(segundos);
		controlador.setTimerModelo(inicio);
		controlador.guardarModelo();
	}
 
	/**
	 * Esta es la tarea que se ejecuta en el hilo paralelo. Básicamente lo que hace
	 * es consultar la hora actual y comprobar si ha transcurrido el tiempo necesario
	 * para recuperar una moneda.
	 */
	@Override
	public void run() {
		while (controlador.getMonedasModelo() < 5) {
			LocalTime ahora = LocalTime.now();//Momento en que consultamos tiempo transcurrido
			if (ahora.toSecondOfDay() - inicio.toSecondOfDay() >= segundos) {
				controlador.modeloRecuperaMoneda();
				inicio = LocalTime.now();
			}
		}
	}
 
}

LanzaMonedas (main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * Construye el marco principal JFrame para mostrar el Contenedor que proporciona la
 * clase Vista y pone en marcha todo el programa.<br>
 * En su constructor se incializan y relacionan el Controlador y la Vista.<br>
 * Se modifica el comportamiento del "aspa" de cierre de ventana para que antes de cerrar
 * la aplicación, se haga un guardado del Modelo en disco. De este modo al reabrir aplicación
 * podremos recuperar el Modelo tal cuál estaba en la última sesión.
 * @author Kabuto
 * @see <a href="https://www.lawebdelprogramador.com/foros/Java/1751736-Continuacion-de-un-timer-despues-de-cerrar-la-aplicacion.html">
 * Web del Programador</a>
 */
@SuppressWarnings("serial")
public class LanzaMonedas extends JFrame{
 
	private Vista vista;
	private Controlador controlador;
 
	public LanzaMonedas() {
		controlador = new Controlador();
		vista = new Vista(controlador);
		controlador.setVista(vista);
 
		addWindowListener(new WindowAdapter() {
			@Override
	        public void windowClosing(WindowEvent e) {
	            controlador.guardarModelo();
	            System.exit(0);
	        }
		});
 
		setContentPane(vista);
		setTitle("Lanza Monedas");
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
	}
 
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				new LanzaMonedas();
			}
		});
	}
 
}
Valora esta respuesta
Me gusta: Está respuesta es útil y esta claraNo me gusta: Está respuesta no esta clara o no es útil
0
Comentar