¿Cómo configurar una alarma para que se programe a una hora exacta después de todas las restricciones más recientes en Android?


27

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

  1. Pruebe cuando la aplicación esté en primer plano, visible para el usuario. - tomó 1-2 minutos.
  2. Pruebe cuándo se envió la aplicación a un segundo plano (usando el botón de inicio, por ejemplo): tomó aproximadamente 1 minuto
  3. 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.
  4. Como # 3, pero también apaga la pantalla. Probablemente sería peor ...

Traté de usar las siguientes cosas, todas no funcionan:

  1. alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)

  2. alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  3. AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  4. combinación de cualquiera de los anteriores, con:

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. Intenté usar un servicio en lugar de BroadcastReceiver. También probé en un proceso diferente.

  6. 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.

  7. 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)
  1. 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

  1. ¿Cómo establecemos que algo se active en un momento relativamente exacto hoy en día?

  2. ¿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?

  3. ¿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


Ha pasado mucho tiempo que trabajé en el servicio (ni siquiera soy un desarrollador profesional para sugerir), pero puedo sugerirle que evite alarmManager para casos como configurar la alarma por debajo de 5 minutos, debido a las restricciones de Android después de un servicio horario que se ejecuta en el backend se llama cada 5 minutos o más, no menos de 5 minutos. En cambio, usé Handler. Y para ejecutar mi servicio continúa en segundo plano me referí [ github.com/fabcira/neverEndingAndroidService]
Blu

Entonces, ¿cuáles son las restricciones exactas? ¿Cuál es el tiempo mínimo que se garantiza que un disparador funcionará en un tiempo de entrega relativamente preciso?
Desarrollador de Android

No recuerdo las restricciones exactas, pero cuando estaba trabajando en ello busqué en Google durante días para superar el servicio en segundo plano que me mataba automáticamente. Y por observación personal, noté un problema en Samsung, Xiaomi, etc., no puede llamar a alarmManger en un intervalo de 5 minutos, tuve un servicio de carga de datos implementado usando alarmManger que se activa cada 1 minuto, pero decepcionó a nuestro cliente que se quejó del servicio no se está ejecutando en absoluto. Para los emuladores funciona bien.
Blu

Sé que no puede comenzar la actividad desde el fondo en Android Q, pero no parece que sea su caso.
marcinj

@ greeble31 Lo intenté ahora. ¿Qué solución ves que funciona? Por alguna razón, todavía no consigo que funcione. Configuré la alarma, eliminé la aplicación de las tareas recientes y no veo que se active la alarma, a pesar de que la pantalla está encendida y el dispositivo está conectado a un cargador. Sucede tanto en un dispositivo real (Pixel 4 con Android 10) como en un emulador (API 27, por ejemplo). ¿Funciona para ti? ¿Puedes por favor compartir el código completo? Tal vez en Github?
Desarrollador de Android

Respuestas:


4

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.


Soy muy consciente de este problema de los OEM chinos. Pero como escribí, sucede incluso en el emulador y el dispositivo Pixel 4. No es un OEM chino que lo hizo como tal. Verifique el emulador y / o el dispositivo Pixel. El problema existe allí también. Configure una alarma, elimine la aplicación de las tareas recientes y vea que la alarma no se active. Veo esto como un error e informé aquí (incluye un video y un proyecto de muestra si quieres probar): issuetracker.google.com/issues/149556385. He actualizado mi pregunta para que quede clara. La pregunta es cómo es que alguna aplicación tuvo éxito.
Desarrollador de Android

@androiddeveloper Creo que debería funcionar en el emulador. ¿Qué emulador tienes?
Ibrahim Ali

También creí, hasta que lo intenté. Simplemente pruébelo en API 29, por ejemplo, de lo que Android Studio tiene para ofrecer. Estoy seguro de que lo mismo ocurrirá en versiones un poco más antiguas también.
Desarrollador de Android

4

Encontré una solución extraña (muestra aquí ) que parece funcionar para todas las versiones, incluso para Android R:

  1. Tener el permiso SAW permiso declarado en el manifiesto:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

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.

  1. Tenga un servicio que detectará cuándo se eliminaron las tareas y, cuando lo haga, abra una Actividad falsa que todo lo que hace es cerrarse:
class OnTaskRemovedDetectorService : Service() {
    override fun onBind(intent: Intent?) = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.e("AppLog", "onTaskRemoved")
        applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        stopSelf()
    }

}

FakeActivity.kt

class FakeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "FakeActivity")
        finish()
    }
}

También puede hacer que esta Actividad sea casi invisible para el usuario usando este tema:

    <style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

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:

        val timeToTrigger = System.currentTimeMillis() + 10 * 1000
        val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)

Ni siquiera tengo que usar "pendienteShowList". Usar nulo también está bien.


Solo quiero iniciar una actividad enReceive () en AndroidQ. ¿Hay alguna solución para eso sin el SYSTEM_ALERT_WINDOWpermiso?
doctorram

2
¿Por qué Google siempre hace que las vidas de los desarrolladores de Android sean un infierno para cosas simples?
doctorram

@doctorram Sí, está escrito en los documentos sobre las diversas excepciones: developer.android.com/guide/components/activities/… . Acabo de elegir SYSTEM_ALERT_WINDOW porque es la forma más fácil de probarlo.
Desarrollador de Android

Desde su última edición, ¿quiere decir que ahora no tenemos que usar ninguna solución alternativa que mencionó para persistir las alarmas después de eliminar la aplicación de la lista reciente?
user3410835

Quiero ejecutar un fragmento de código todos los días entre las 6 a.m. y las 7 a.m.en segundo plano, incluso si la aplicación se elimina de la lista reciente. ¿Debo usar WorkManager o AlarmManager? Intenté el siguiente código para mi caso de uso y no funcionó. ¿Cuál es el problema con el siguiente código? calendar.setTimeInMillis (System.currentTimeMillis ()); calendar.set (Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendienteIntent);
user3410835

1
  1. Asegúrate de que la intención que transmites sea explícita y tenga la Intent.FLAG_RECEIVER_FOREGROUNDbandera.

https://developer.android.com/about/versions/oreo/background#broadcasts

Intent intent = new Intent(context, Receiver.class);
intent.setAction(action);
...
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
  1. Úselo setExactAndAllowWhileIdle()cuando apunte a API 23+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
}
  1. Inicie su alarma como un servicio en primer plano:

https://developer.android.com/about/versions/oreo/background#migration

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
  1. Y no olvides los permisos:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

¿Por qué es importante sobre el servicio, si el BroadcastReceiver en sí mismo no recibe la Intención (o cerca del momento)? Ese es el primer paso ... Además, ¿AlarmManagerCompat ya no ofrece este mismo código? ¿Has probado esto usando las pruebas que escribí, incluida la eliminación de la aplicación de las tareas recientes? ¿Puedes mostrar el código completo? ¿Quizás compartir en Github?
Desarrollador de Android

@androiddeveloper actualizó la respuesta.
Maksim Ivanov

Todavía no parece funcionar. Aquí hay un proyecto de muestra: ufile.io/6qrsor7o . Pruebe Android 10 (el emulador también está bien), configure la alarma y elimine la aplicación de las tareas recientes. Si no elimina las tareas recientes, funciona bien y se activa después de 10 segundos.
desarrollador de Android

También actualicé la pregunta para tener un enlace a un informe de error que incluye un proyecto de muestra y un video, porque creo que es un error ya que no veo ninguna otra razón para que esto ocurra.
Desarrollador de Android

0

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.


Si tengo un servicio en primer plano, ¿por qué necesitaría esto? Podría usar un Handler.postDelayed o cualquier otra solución similar si quisiera ...
desarrollador de Android

0

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:

PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
            String packageName = "your Package name";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Intent i = new Intent();
                if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                    i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    i.setData(Uri.parse("package:" + packageName));
                    startActivity(i);

Ya probé esto porque me di cuenta de que ninguna otra aplicación lo hace y tenía curiosidad por saber si puede ayudar. No funciono Pregunta actualizada
Desarrollador de Android

0

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:

  val pendingAlarm = Intent(ACTION_FIRED)
                .apply {
                    setClass(mContext, AlarmsReceiver::class.java)
                    putExtra(EXTRA_ID, id)
                    putExtra(EXTRA_TYPE, typeName)
                }
                .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }

            val pendingShowList = PendingIntent.getActivity(
                    mContext,
                    100500,
                    Intent(mContext, AlarmsListActivity::class.java),
                    PendingIntent.FLAG_UPDATE_CURRENT
            )

            am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)

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.


Lamentablemente no funcionó. Eso es lo que probé. Ver archivos aquí: github.com/yuriykulikov/AlarmClock/issues/…
desarrollador de Android

He revisado tu código en GitHub. El receptor de transmisión funciona después de que la aplicación se elimine de los recientes en Moto Z2 Play. Puedo probarlo en un Pixel, pero el código me parece correcto. La detención forzada de la aplicación elimina la alarma programada, pero esto sucederá con cualquier aplicación que se detenga por la fuerza.
Yuriy Kulikov

Ya lo he mostrado varias veces: todo lo que hago después de programar es eliminar de las tareas recientes. Y lo hice tanto en el emulador como en Pixel 4.
desarrollador de Android

Compruebe si el uso de pendienteShowList evita el problema y, en caso afirmativo, actualizaré la respuesta. Tal vez sea útil para alguien.
Yuriy Kulikov
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.