Come impostare una sveglia da programmare in un momento esatto dopo tutte le restrizioni più recenti su Android?


27

Nota: ho provato varie soluzioni scritte qui su StackOverflow (esempio qui ). Non chiudere questo senza verificare se la tua soluzione da ciò che hai trovato funziona utilizzando il test che ho scritto di seguito.

sfondo

Esiste un requisito sull'app, che l'utente imposta un promemoria da programmare in un momento specifico, quindi quando l'app viene avviata in questo momento, fa qualcosa di minuscolo in background (solo alcune operazioni di query DB) e mostra un semplice notifica, per raccontare il promemoria.

In passato, ho usato un semplice codice per impostare qualcosa da programmare in un momento relativamente specifico:

            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)

Il problema

Ora ho testato questo codice sugli emulatori su nuove versioni di Android e su Pixel 4 con Android 10, e non sembra innescarsi, o forse si innesca dopo molto tempo da quando l'ho fornito. Sono ben consapevole del comportamento terribile che alcuni OEM hanno aggiunto alla rimozione di app dalle attività recenti, ma questo è sia su emulatori che su dispositivo Pixel 4 (stock).

Ho letto i documenti sull'impostazione di un allarme, che è stato limitato per le app in modo che non si verifichi troppo spesso, ma questo non spiega come impostare un allarme in un momento specifico e non spiega come mai l' app di Google Clock riesce a farlo.

Non solo, ma secondo quello che ho capito, dice che le restrizioni dovrebbero essere applicate soprattutto per lo stato di basso consumo del dispositivo, ma nel mio caso, non avevo questo stato, sia sul dispositivo che sugli emulatori. Ho impostato gli allarmi per attivarli tra circa un minuto.

Visto che molte app per le sveglie non funzionano più come una volta, penso che manchi qualcosa nei documenti. Esempio di tali app è la popolare app Timely che è stata acquistata da Google ma non ha mai ricevuto nuovi aggiornamenti per gestire le nuove restrizioni e ora gli utenti la vogliono indietro. . Tuttavia, alcune app popolari funzionano bene, come questa .

Quello che ho provato

Per provare che effettivamente l'allarme funziona, eseguo questi test quando provo ad attivare l'allarme tra un minuto, dopo aver installato l'app per la prima volta, il tutto mentre il dispositivo è collegato al PC (per vedere i registri):

  1. Verifica quando l'app è in primo piano, visibile all'utente. - ci sono voluti 1-2 minuti.
  2. Il test quando l'app è stata inviata in background (utilizzando il pulsante Home, ad esempio) - ha richiesto circa 1 minuto
  3. Verifica quando l'attività dell'app è stata rimossa dalle attività recenti. - Ho aspettato più di 20 minuti e non ho visto l'allarme essere attivato, scrivendo nei registri.
  4. Come il n. 3, ma spegni anche lo schermo. Probabilmente sarebbe peggio ...

Ho provato a usare le cose successive, tutte non funzionano:

  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. combinazione di uno dei precedenti, con:

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

  5. Ho provato a usare un servizio invece di BroadcastReceiver. Ho anche provato un processo diverso.

  6. Ho provato a far sì che l'app fosse ignorata dall'ottimizzazione della batteria (non ha aiutato), ma poiché altre app non ne hanno bisogno, non dovrei usarla neanche.

  7. Ho provato usando questo:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
  1. Ho provato ad avere un servizio che avrà un trigger di Ho onTaskRemoved , per riprogrammare l'allarme lì, ma anche questo non ha aiutato (il servizio ha funzionato bene però).

Per quanto riguarda l'app Orologio di Google, non ho visto nulla di speciale al riguardo tranne che mostra una notifica prima di essere attivata e non la vedo nemmeno nella sezione "non ottimizzata" della schermata delle impostazioni di ottimizzazione della batteria.

Visto che questo sembra un bug, ho riferito di questo qui , incluso un progetto di esempio e un video per mostrare il problema.

Ho controllato più versioni dell'emulatore e sembra che questo comportamento sia iniziato dall'API 27 (Android 8.1 - Oreo). Guardare i documenti , non vedo menzionare AlarmManager, ma invece è stato scritto su vari lavori in background.

Le domande

  1. Come possiamo impostare qualcosa da attivare in un momento relativamente esatto al giorno d'oggi?

  2. Come mai le soluzioni di cui sopra non funzionano più? Mi sto perdendo qualcosa? Autorizzazione? Forse dovrei usare un lavoratore invece? Ma allora non significherebbe che potrebbe non innescarsi in tempo?

  3. In che modo l'app "Orologio" di Google supera tutto questo e si innesca comunque all'ora esatta, sempre, anche se è stata attivata solo un minuto fa? È solo perché è un'app di sistema? Cosa succede se viene installato come app utente, su un dispositivo che non lo ha integrato?

Se dici che è perché è un'app di sistema, ho trovato un'altra app che può attivare un allarme due volte in 2 minuti, qui , anche se penso che a volte potrebbe usare un servizio di primo piano.

EDIT: realizzato un piccolo repository Github per provare idee, qui .


EDIT: finalmente trovato un campione che è sia open-source e non presenta questo problema. Purtroppo è molto complesso e cerco ancora di capire cosa lo rende così diverso (e qual è il codice minimo che dovrei aggiungere al mio POC) che consente ai suoi allarmi di essere programmati dopo aver rimosso l'app dalle attività recenti


è da tanto che ho lavorato sul servizio (non sono nemmeno uno sviluppatore professionista da suggerire), ma posso suggerire di evitare alarmManager per casi come impostare un allarme al di sotto di 5 minuti, a causa di restrizioni Android dopo un servizio a tempo in esecuzione nel il backend viene chiamato dopo ogni 5 minuti o più non meno di 5 minuti. Invece, ho usato Handler. E per l'esecuzione del mio servizio continua in background che ho indicato [ github.com/fabcira/neverEndingAndroidService]
Blu

Quindi quali sono le restrizioni esatte? Qual è il tempo minimo garantito per il funzionamento di un trigger in un tempo relativamente preciso?
sviluppatore Android il

Non riesco a ricordare le esatte restrizioni, ma quando ci stavo lavorando ho cercato su Google per giorni come giorni per superare il servizio in background che veniva ucciso automaticamente. E da un'osservazione personale ho notato un problema su Samsung, Xiaomi, ecc., Non è possibile chiamare alarmManger tra un intervallo di 5 minuti, ho implementato un servizio di caricamento dati tramite alarmManger che viene attivato ogni 1 minuto, ma ha deluso il nostro cliente che si è lamentato del servizio non funziona affatto. Per gli emulatori funziona bene.
Blu

So che non puoi iniziare l'attività dallo sfondo in Android Q, ma non sembra che sia il tuo caso.
marcinj

@ greeble31 Ho provato ora. Quale soluzione vedi che funziona? Per qualche motivo non riesco ancora a farlo funzionare. Ho impostato l'allarme, rimuovo l'app dalle attività recenti e non vedo l'attivazione dell'allarme, anche se lo schermo è acceso e il dispositivo è collegato a un caricabatterie. Succede sia su un dispositivo reale (Pixel 4 con Android 10) sia sull'emulatore (API 27 ad esempio). Per te funziona? Potete per favore condividere il codice completo? Forse a Github?
sviluppatore Android

Risposte:


4

Non abbiamo niente da fare.

Una volta che la tua app non è nella whitelist, verrà sempre uccisa una volta rimossa dalle app recenti.

Perché il produttore di apparecchiature originali (OME) viola costantemente la conformità di Android .

Quindi, se la tua app non è autorizzata dal dispositivo Fabbricazione, non genererà alcun lavoro in background, nemmeno allarmi , nel caso in cui l'app venga rimossa dalle app recenti.

Puoi trovare un elenco di dispositivi con quel comportamento qui ANCHE potresti trovare una soluzione laterale, tuttavia, non funzionerà bene.


Sono ben consapevole di questo problema degli OEM cinesi. Ma come ho scritto, succede anche sull'emulatore e sul dispositivo Pixel 4. Non è un OEM cinese che l'ha reso tale. Verificare l'emulatore e / o il dispositivo Pixel. Il problema esiste anche lì. Imposta un allarme, rimuovi l'app dalle attività recenti e verifica che l'allarme non sia attivato. Vedo questo come un bug e riportato qui (include un video e un progetto di esempio se si desidera provare): issuetracker.google.com/issues/149556385. Ho aggiornato la mia domanda per chiarire. La domanda è: come mai alcune app sono riuscite.
sviluppatore Android il

@androiddeveloper Credo che dovrebbe funzionare su Emulator .. Quale emulatore hai?
Ibrahim Ali,

Ho anche creduto, fino a quando ho provato. Provalo ad esempio su API 29, di ciò che Android Studio ha da offrire. Sono sicuro che lo stesso accadrà anche su versioni un po 'più vecchie.
sviluppatore Android il

4

Abbiamo trovato una soluzione alternativa (esempio qui ) che sembra funzionare per tutte le versioni, incluso Android R:

  1. Richiedi l'autorizzazione SAW nel manifest:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Su Android R dovrai anche averlo concesso. Prima, non sembra che debba essere concesso, appena dichiarato. Non so perché questo sia cambiato su R, ma posso dire che SAW potrebbe essere richiesto come possibile soluzione per avviare le cose in background, come scritto qui per Android 10.

  1. Avere un servizio che rileverà quando le attività sono state rimosse e quando lo fa, aprire un'attività falsa che tutto ciò che fa è chiudersi:
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()
    }
}

Puoi anche rendere questa attività quasi invisibile all'utente usando questo 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>

Purtroppo, questa è una strana soluzione. Spero di trovare una soluzione migliore a questo.

La restrizione parla dell'avvio di Activity, quindi la mia idea attuale è che forse se avessi avviato un servizio in primo piano per una frazione di secondo mi sarebbe stato di aiuto, e per questo non avrei nemmeno bisogno del permesso SAW.

EDIT: OK, ho provato con un servizio in primo piano (esempio qui ), e non ha funzionato. Non ho idea del perché un'attività funzioni ma non di un servizio. Ho anche provato a riprogrammare l'allarme lì e ho cercato di lasciare il servizio per un po ', anche dopo una riprogrammazione. Ha anche provato un servizio normale, ma ovviamente si è chiuso immediatamente, poiché l'attività è stata rimossa e non ha funzionato affatto (anche se ho creato un thread per l'esecuzione in background).

Un'altra possibile soluzione che non ho provato è quella di avere un servizio in primo piano per sempre, o almeno fino a quando l'attività non viene rimossa, ma questo è un po 'strano e non vedo le app che ho menzionato usarlo.

EDIT: ha provato ad avere un servizio in primo piano in esecuzione prima della rimozione dell'attività dell'app, e per un po 'dopo, e l'allarme ha funzionato ancora. Ha anche cercato di avere questo servizio come responsabile dell'evento rimosso dall'attività e di chiudersi immediatamente quando si verifica, e ha funzionato ancora (esempio qui ). Il vantaggio di questa soluzione alternativa è che non è necessario disporre dell'autorizzazione SAW. Lo svantaggio è che hai un servizio con una notifica mentre l'app è già visibile all'utente. Mi chiedo se sia possibile nascondere la notifica mentre l'app è già in primo piano tramite l'attività.


EDIT: sembra che sia un bug su Android Studio (riportato qui , compresi i video che confrontano le versioni). Quando avvii l'app dalla versione problematica che ho provato, è possibile che gli allarmi vengano cancellati.

Se avvii l'app dal programma di avvio, funziona perfettamente.

Questo è il codice corrente per impostare l'allarme:

        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)

Non devo nemmeno usare "pendingShowList". Anche l'uso di null è ok.


Voglio solo lanciare un'attività suReceive () su AndroidQ. Esistono soluzioni alternative per questo senza l' SYSTEM_ALERT_WINDOWautorizzazione?
doctorram,

2
Perché Google rende sempre la vita degli sviluppatori Android un inferno vivente per cose semplici ?!
doctorram,

@doctorram Sì, è scritto sui documenti relativi alle varie eccezioni: developer.android.com/guide/components/activities/… . Ho appena scelto SYSTEM_ALERT_WINDOW perché è il modo più semplice per testarlo.
sviluppatore Android

Dalla tua ultima modifica, vuoi dire che ora non dobbiamo usare alcuna soluzione alternativa che hai menzionato per persistere negli allarmi dopo aver rimosso l'app dall'elenco recente ??
user3410835

Voglio eseguire un pezzo di codice ogni giorno tra le 6:00 e le 7:00 in background anche se l'app viene rimossa dall'elenco recente. Dovrei usare WorkManager o AlarmManager ?? Ho provato il seguente codice per il mio caso d'uso e non ha funzionato. Qual è il problema con il codice seguente? calendar.setTimeInMillis (System.currentTimeMillis ()); calendar.set (Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendingIntent);
user3410835

1
  1. Assicurati che l'intento che trasmetti sia esplicito e abbia la Intent.FLAG_RECEIVER_FOREGROUNDbandiera.

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. Utilizzare setExactAndAllowWhileIdle()per il targeting dell'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. Avvia la sveglia come servizio in primo piano:

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. E non dimenticare le autorizzazioni:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Perché è importante il servizio, se il BroadcastReceiver stesso non ottiene affatto l'intento (o quasi)? Questo è il primo passo ... Inoltre, AlarmManagerCompat non offre già lo stesso codice? Hai provato questo usando i test che ho scritto, inclusa la rimozione dell'app dalle attività recenti? Puoi per favore mostrare l'intero codice? Forse condividi su Github?
sviluppatore Android

@androiddeveloper ha aggiornato la risposta.
Maksim Ivanov,

Ancora non sembra funzionare. Ecco un esempio di progetto: ufile.io/6qrsor7o . Prova su Android 10 (anche l'emulatore ok), imposta la sveglia e rimuovi l'app dalle attività recenti. Se non rimuovi dalle attività recenti, funziona bene e viene attivato dopo 10 secondi.
sviluppatore Android il

Ho anche aggiornato la domanda per avere un collegamento a una segnalazione di bug che includa un progetto e un video di esempio, perché penso che questo sia un bug in quanto non vedo nessun altro motivo perché ciò avvenga.
sviluppatore Android il

0

So che questo non è efficiente ma potrebbe essere più coerente con una precisione di 60 secondi.

https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

se questo ricevitore di trasmissione viene utilizzato all'interno di un servizio in primo piano, è possibile controllare l'ora ogni minuto e prendere la decisione di intraprendere un'azione.


Se ho un servizio in primo piano, perché dovrei averne bisogno? Potrei semplicemente usare un Handler.postDelayed o qualsiasi altra soluzione simile se lo desidero ...
Sviluppatore Android

0

Penso che puoi chiedere all'utente di impostare l'autorizzazione in modo che disabiliti la modalità di risparmio energetico e avvisare l'utente che se non lo utilizza, i tempi esatti non saranno raggiunti.

Ecco il codice per richiederlo:

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

Ho già provato questo perché ho notato che nessun'altra app lo fa ed era curioso di sapere se può aiutarti. Non ha funzionato Domanda aggiornata.
sviluppatore Android il

0

Sono l'autore del progetto open source che hai menzionato nella tua domanda ( semplice sveglia) .

Sono sorpreso che l'utilizzo di AlarmManager.setAlarmClock non abbia funzionato per te, perché la mia app fa esattamente questo. Il codice si trova nel file AlarmSetter.kt. Ecco uno snippet:

  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)

Fondamentalmente non è niente di speciale, assicurati solo che l'intento abbia un'azione e una classe target, che è un ricevitore di trasmissione nel mio caso.


Purtroppo non ha funzionato. Questo è quello che ho provato. Vedi i file qui: github.com/yuriykulikov/AlarmClock/issues/…
sviluppatore Android il

Ho verificato il tuo codice su GitHub. Il ricevitore Broadcast funziona dopo che l'app è stata rimossa dai recenti su Moto Z2 Play. Posso provarlo su un Pixel, ma il codice mi sembra ok. L'arresto forzato dell'applicazione rimuove l'allarme programmato, ma ciò accadrà a qualsiasi app che viene forzata.
Yuriy Kulikov

Ho già mostrato più volte: tutto ciò che faccio dopo la pianificazione è rimuovere dalle attività recenti. E l'ho fatto sia sull'emulatore che su Pixel 4.
sviluppatore Android il

Verifica se l'utilizzo di pendingShowList elude il problema e, in caso affermativo, aggiornerò la risposta. Forse sarà utile per qualcuno.
Yuriy Kulikov il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.