Nota: Probé varias soluciones sobre las que se escribe aquí en StackOverflow (ejemplo aquí ). No cierre esto sin verificar si su solución de lo que ha encontrado funciona con la prueba que he escrito a continuación.
Antecedentes
Hay un requisito en la aplicación, que el usuario establezca un recordatorio para que se programe en un momento específico, por lo que cuando la aplicación se activa en este momento, hace algo pequeño en segundo plano (solo una operación de consulta DB) y muestra un Notificación simple, para informar sobre el recordatorio.
En el pasado, utilicé un código simple para configurar algo que se programaría en un momento relativamente específico:
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
//do something in the real app
}
}
Uso:
val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
setAlarm(this, timeToTrigger, 1)
El problema
Ahora he probado este código en emuladores en nuevas versiones de Android y en Pixel 4 con Android 10, y parece que no se dispara, o tal vez se dispara después de mucho tiempo desde que lo proporcioné. Soy consciente del terrible comportamiento que algunos fabricantes de equipos originales agregaron para eliminar aplicaciones de las tareas recientes, pero este se encuentra tanto en emuladores como en dispositivos Pixel 4 (stock).
Leí en los documentos sobre la configuración de una alarma, que se restringió para las aplicaciones, por lo que no ocurrirá con demasiada frecuencia, pero esto no explica cómo configurar una alarma en un momento específico, y no explica ¿Cómo es que la aplicación Reloj de Google tiene éxito al hacerlo?
No solo eso, sino que según lo que entiendo, dice que las restricciones deberían aplicarse especialmente para el estado de baja potencia del dispositivo, pero en mi caso, no tenía este estado, tanto en el dispositivo como en los emuladores. He configurado las alarmas para que se activen en aproximadamente un minuto a partir de ahora.
Al ver que muchas aplicaciones de despertador ya no funcionan como solían hacerlo, creo que hay algo que falta en los documentos. Ejemplo de tales aplicaciones es la popular aplicación Timely que fue comprada por Google pero nunca recibió nuevas actualizaciones para manejar las nuevas restricciones, y ahora los usuarios quieren recuperarla. . Sin embargo, algunas aplicaciones populares funcionan bien, como esta .
Lo que he intentado
Para probar que la alarma funciona, realizo estas pruebas cuando intento activar la alarma en un minuto a partir de ahora, después de instalar la aplicación por primera vez, todo mientras el dispositivo está conectado a la PC (para ver los registros):
- Pruebe cuando la aplicación esté en primer plano, visible para el usuario. - tomó 1-2 minutos.
- Pruebe cuándo se envió la aplicación a un segundo plano (usando el botón de inicio, por ejemplo): tomó aproximadamente 1 minuto
- Pruebe cuándo se eliminó la tarea de la aplicación de las tareas recientes. - Esperé más de 20 minutos y no vi que se activara la alarma, escribiendo en los registros.
- Como # 3, pero también apaga la pantalla. Probablemente sería peor ...
Traté de usar las siguientes cosas, todas no funcionan:
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
combinación de cualquiera de los anteriores, con:
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)
Intenté usar un servicio en lugar de BroadcastReceiver. También probé en un proceso diferente.
Intenté ignorar la aplicación desde la optimización de la batería (no ayudó), pero como otras aplicaciones no la necesitan, tampoco debería usarla.
Intenté usar esto:
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
- Intenté tener un servicio que activará onTaskRemoved , para reprogramar la alarma allí, pero esto tampoco ayudó (aunque el servicio funcionó bien).
En cuanto a la aplicación Reloj de Google, no vi nada especial al respecto, excepto que muestra una notificación antes de activarse, y tampoco la veo en la sección "no optimizada" de la pantalla de configuración de optimización de la batería.
Al ver que esto parece un error, informé sobre esto aquí , incluido un proyecto de muestra y un video para mostrar el problema.
He comprobado varias versiones del emulador, y parece que este comportamiento comenzó desde la API 27 (Android 8.1 - Oreo). Mirando los documentos , no veo que se mencione AlarmManager, sino que se escribió sobre varios trabajos de fondo.
Las preguntas
¿Cómo establecemos que algo se active en un momento relativamente exacto hoy en día?
¿Cómo es que las soluciones anteriores ya no funcionan? ¿Me estoy perdiendo algo? ¿Permiso? ¿Quizás se supone que debo usar un trabajador en su lugar? Pero entonces, ¿no significaría que podría no activarse a tiempo?
¿Cómo supera la aplicación "Reloj" de Google todo esto y se activa de todos modos en la hora exacta, siempre, incluso si se activó hace solo un minuto? ¿Es solo porque es una aplicación del sistema? ¿Qué sucede si se instala como una aplicación de usuario, en un dispositivo que no lo tiene incorporado?
Si usted dice que es porque es una aplicación del sistema, he encontrado otra aplicación que puede disparar una alarma dos veces en 2 minutos, aquí , aunque creo que podría utilizar un servicio de plano algunas veces.
EDITAR: hizo un pequeño repositorio de Github para probar ideas, aquí .
EDITAR: finalmente encontró una muestra que es de código abierto y no tiene este problema. Lamentablemente es muy complejo y todavía trato de averiguar qué lo hace tan diferente (y cuál es el código mínimo que debo agregar a mi POC) que permite que sus alarmas permanezcan programadas después de eliminar la aplicación de las tareas recientes
fuente
Respuestas:
No tenemos nada que hacer.
Una vez que su aplicación no esté en la lista blanca, siempre se eliminará una vez que se elimine de las aplicaciones recientes.
Porque el fabricante de equipos originales (OME) viola constantemente el cumplimiento de Android .
Por lo tanto, si su aplicación no está en la lista blanca del dispositivo Fabricación, no activará ningún trabajo de fondo, incluso alarmas , en caso de que su aplicación se elimine de las aplicaciones recientes.
Puede encontrar una lista de dispositivos con ese comportamiento aquí TAMBIÉN puede encontrar una solución secundaria, sin embargo, no funcionará bien.
fuente
Encontré una solución extraña (muestra aquí ) que parece funcionar para todas las versiones, incluso para Android R:
En Android R deberás tenerlo también otorgado. Antes, no parece que sea necesario otorgarlo, solo declararlo. No estoy seguro de por qué esto cambió en R, pero puedo decir que SAW podría ser necesaria como una posible solución para comenzar las cosas en segundo plano, como está escrito aquí para Android 10.
FakeActivity.kt
También puede hacer que esta Actividad sea casi invisible para el usuario usando este tema:
Lamentablemente, esta es una solución extraña. Espero encontrar una mejor solución para esto.
La restricción habla de iniciar la Actividad, por lo que mi idea actual es que tal vez si inicio un servicio en primer plano por una fracción de segundo también me ayudará, y para esto ni siquiera necesitaré el permiso SAW.
EDITAR: OK Intenté con un servicio en primer plano (muestra aquí ), y no funcionó. No tengo idea de por qué una Actividad está funcionando pero no un servicio. Incluso intenté reprogramar la alarma allí e intenté dejar que el servicio se quedara un poco, incluso después de volver a programar. También probé un servicio normal pero, por supuesto, se cerró de inmediato, ya que la tarea se eliminó y no funcionó en absoluto (incluso si creé un hilo para ejecutar en segundo plano).
Otra posible solución que no probé es tener un servicio en primer plano para siempre, o al menos hasta que se elimine la tarea, pero esto es un poco extraño y no veo las aplicaciones que he mencionado usándolo.
EDITAR: trató de tener un servicio en primer plano ejecutándose antes de eliminar la tarea de la aplicación, y un poco después, y la alarma aún funcionaba. También traté de que este servicio fuera el encargado del evento de eliminación de tareas, y que se cerrara de inmediato cuando ocurriera, y aún funcionaba (muestra aquí ). La ventaja de esta solución alternativa es que no tiene que tener el permiso SAW en absoluto. La desventaja es que tiene un servicio con una notificación mientras la aplicación ya está visible para el usuario. Me pregunto si es posible ocultar la notificación mientras la aplicación ya está en primer plano a través de la Actividad.
EDITAR: Parece que es un error en Android Studio (se informa aquí , incluidos los videos que comparan versiones). Cuando inicias la aplicación desde la versión problemática que probé, podría hacer que se borren las alarmas.
Si inicia la aplicación desde el iniciador, funciona bien.
Este es el código actual para configurar la alarma:
Ni siquiera tengo que usar "pendienteShowList". Usar nulo también está bien.
fuente
SYSTEM_ALERT_WINDOW
permiso?Intent.FLAG_RECEIVER_FOREGROUND
bandera.https://developer.android.com/about/versions/oreo/background#broadcasts
setExactAndAllowWhileIdle()
cuando apunte a API 23+.https://developer.android.com/about/versions/oreo/background#migration
fuente
Sé que esto no es eficiente, pero podría ser más consistente con una precisión de 60 segundos.
https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK
Si este receptor de transmisión se usa dentro de un servicio en primer plano, puede verificar la hora cada minuto y tomar una decisión sobre la acción.
fuente
Creo que puede pedirle al usuario que establezca el permiso para deshabilitar el modo de ahorro de energía y advertirle al usuario que si no lo usa, no se lograrán los tiempos exactos.
Aquí está el código para solicitarlo:
fuente
Soy el autor del proyecto de código abierto que ha mencionado en su pregunta ( reloj despertador simple) .
Me sorprende que usar AlarmManager.setAlarmClock no funcionó para usted, porque mi aplicación hace exactamente eso. El código está en el archivo AlarmSetter.kt. Aquí hay un fragmento:
Básicamente no es nada especial, solo asegúrese de que la intención tenga una acción y una clase objetivo, que en mi caso es un receptor de transmisión.
fuente