web-dev-qa-db-fra.com

Android AlarmManager setExact () n'est pas exact

Je dois planifier une tâche planifiée toutes les 10 minutes.

Comme dans Lollipop et les versions supérieures setRepeating() est inexacte, j'utilise setExact() et (au déclenchement de l'alarme) je règle une nouvelle alarme exacte en 10 minutes.

private void setAlarm(long triggerTime, PendingIntent pendingIntent) {
        int ALARM_TYPE = AlarmManager.ELAPSED_REALTIME_WAKEUP;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat) {
            alarmManager.setExact(ALARM_TYPE, triggerTime, pendingIntent);
        } else {
            alarmManager.set(ALARM_TYPE, triggerTime, pendingIntent);
        }
    }

triggerTime est calculé SystemClock.elapsedRealtime() + 600_000;

Lorsque l'alarme se déclenche, tout d'abord, j'en planifie une nouvelle, seulement après avoir exécuté ma tâche planifiée.

setAlarm();
mySheduledTask;

J'ai l'autorisation WAKE_LOCK Dans mon manifeste.

Lorsque je teste cela sur Android 4 - cela fonctionne parfaitement (l'écart peut être de 12 à 15 millisecondes).

Mais quand j'exécute l'application sur Xiaomi Redmi Note 3 Pro (5.1.1) - l'écart peut aller jusqu'à 15 secondes!

Par exemple, je vois dans mon fichier journal: la première exécution a eu lieu à 1467119934477 (de RTC heure), la seconde - à 1467120541683. La différence est 607_206 millisecondes, pas - 600_0, comme prévu!

Qu'est-ce qui me manque? Quel est le moyen de simuler le comportement d'une alarme système (c'est le cas d'utilisation le plus proche qui puisse décrire mon virement)?

PS. J'utilise IntentService pour PendingIntent = PendingIntent.getService(context, 0, myIntent, 0);

19
Goltsev Eugene

Le système d'exploitation choisit le mode de fonctionnement des alarmes, en tenant compte de l'heure que vous avez spécifiée. Pour cette raison, lorsque le téléphone passe en mode "semi-sommeil", il n'utilisera pas nécessairement la ressource au moment où vous le souhaitez. Fondamentalement, il attend les `` fenêtres '' que le système d'exploitation ouvre pour lui, et ce n'est qu'alors que l'alarme que vous souhaitez exécuter se déclenchera, c'est pourquoi vous rencontrez des écarts de temps.

Cela a été introduit sur Marshmallow OS et continuera également sur Nougat OS, dans le cadre de Google essayant d'améliorer la batterie de l'appareil.

Voici la chose, vous avez 2 options:

  1. Acceptez les délais (mais envisagez peut-être d'utiliser JobScheduler , ce qui est plus recommandé et vous fera économiser de la batterie).
  2. Utilisez setExactAndAllowWhileIdle ce qui pourrait vous causer des problèmes de batterie (utilisez-le soigneusement, trop d'alarmes seront mauvaises pour votre batterie). Cette méthode ne se répète pas, vous devez donc déclarer le prochain travail à exécuter au service ouvert par le attendantIntent.

Si vous choisissez l'option 2, voici le début:

AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int ALARM_TYPE = AlarmManager.RTC_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
    am.setExactAndAllowWhileIdle(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat)
    am.setExact(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
else
    am.set(ALARM_TYPE, calendar.getTimeInMillis(), pendingIntent);
32
Dus

Une solution de contournement pourrait probablement être quelque chose comme ceci: vous planifiez l'alarme environ 1 minute avant l'heure prévue, que vous utilisez un Handler.postDelayed pour couvrir le temps restant.

Vous trouverez ici un exemple de ce type d'implémentation. L'activité vient de configurer la première alarme:

public class MainActivity extends AppCompatActivity {

    private static int WAIT_TIME = 60*1000; //1 minute
    public static int DELAY_TIME = 10*60*1000; // delay between iterations: 10min
    public static String UPDATE_TIME_KEY = "update_time_key";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setAlarm(this,(new Date().getTime())+DELAY_TIME);
    }

    public static void setAlarm(Context context, long delay) {

        long fireDelay = delay-WAIT_TIME;
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        sharedPreferences.edit().putLong(UPDATE_TIME_KEY,delay).apply();

        Intent startIntent = new Intent(context, UpdateReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, startIntent,PendingIntent.FLAG_UPDATE_CURRENT );
        AlarmManager alarmManager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        int ALARM_TYPE = AlarmManager.RTC;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat) {
            alarmManager.setExact(ALARM_TYPE, fireDelay, pendingIntent);
        } else {
            alarmManager.set(ALARM_TYPE, fireDelay, pendingIntent);
        }
    }

}

que le récepteur continue la boucle:

public class UpdateReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, Intent intent) {
        Log.e("RECEIVED","RECEIVED");
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        long fireTime = sharedPreferences.getLong(MainActivity.UPDATE_TIME_KEY, (new Date()).getTime());

        long fireDelay  =(fireTime-(new Date().getTime())>0)?fireTime-(new Date().getTime()):0;

        (new Handler()).postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.e("RECEIVED","PERFORMED");
                MainActivity.setAlarm(context,(new Date()).getTime()+MainActivity.DELAY_TIME);
            }
        },fireDelay);

    }

}

J'espère que cela a aidé.

6

Pour répondre à la question sur l'alarme système ...

L'application Android Alarm Clock/Desk Clock d'Android utilise une combinaison de setAlarmClock et setExactAndAllowWhileIdle .

Le code suivant est utilisé pour mettre à jour les notifications:

final PendingIntent operation = PendingIntent.getBroadcast(context, 0, 
    AlarmStateManager.createIndicatorIntent(context), flags);
final AlarmClockInfo info = new AlarmClockInfo(alarmTime, viewIntent);

alarmManager.setAlarmClock(info, operation);


En même temps, le code suivant est utilisé pour programmer l'alarme réelle:

if (Utils.isMOrLater()) {
    // Ensure the alarm fires even if the device is dozing.
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
} else {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent)
}


L'intention en attente définie dans setExactAndAllowWhileIdle déclenche l'alarme pendant que setAlarmClock's l'intention est alors simplement ignorée.


Android Googlesource

3
Maksim Ivanov

Vous pouvez appeler la méthode à partir de support.v4:

AlarmManagerCompat.setExact(...);

L'implémentation interne contient des vérifications par version sdk.

3
northerngirl

Vous pouvez essayer d'utiliser AlarmManager.setAlarmClock peut-être que cela peut vous aider.

Une autre chose dont vous avez besoin pour vérifier quel type de BroadcastReceiver vous utilisez, il sera préférable d'utiliser WakefulBroadcastReceiver

Btw vous devez changer la logique pour travailler avec Alarm Manager pour le support Android M, vous pouvez quelque chose comme ceci:

if(Build.VERSION.SDK_INT < 23){
    if(Build.VERSION.SDK_INT >= 19) {
        setExact(...);
    } else {
        set(...);
    }
} else {
    setExactAndAllowWhileIdle(...);
}
2
Scott Key

De Android documentation de AlarmManager

À partir de la livraison des alarmes API 19 (KitKat) est inexacte: le système d'exploitation décalera les alarmes afin de minimiser les réveils et l'utilisation de la batterie. Il existe de nouvelles API pour prendre en charge les applications qui nécessitent des garanties de livraison strictes; voir setWindow (int, long, long, PendingIntent) et setExact (int, long, PendingIntent). Les applications dont targetSdkVersion est antérieure à API 19 continueront de voir le comportement précédent dans lequel toutes les alarmes sont délivrées exactement à la demande.

Aussi lors de l'utilisation de setExact ():

L'alarme sera délivrée aussi près que possible à l'heure de déclenchement demandée.

Il n'est donc toujours pas garanti que setExact soit Exact.

2
sJy