web-dev-qa-db-ja.com

Android AlarmManager setExact()は正確ではありません

スケジュールされたタスクを10分ごとに計画する必要があります。

Lollipop以降のバージョンsetRepeating()は不正確であるため、setExact()を使用し、(アラームの発動時に)新しい正確なアラームを10分で設定します。

_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が計算されますSystemClock.elapsedRealtime() + 600_000;

アラームが鳴ったら、まず新しいアラームを計画します。その後、スケジュールされたタスクを実行します。

_setAlarm();
mySheduledTask;
_

マニフェストに_WAKE_LOCK_権限があります。

これをAndroid 4-でテストすると完璧に動作します(偏差は12-15 ミリ秒かもしれません)。

しかし、Xiaomi Redmi Note 3 Pro(5.1.1)でアプリを実行すると、偏差は15秒!

たとえば、ログファイルに次のように表示されます。最初の実行は1467119934477(RTC time)で、2番目-1467120541683でした。違いは607_206ミリ秒ではなく、 600_0、計画どおり!

私は何が欠けていますか?システムアラームの動作をシミュレートする方法は何ですか(私のタックを説明できる最も近いユースケースです)?

PS。PendingIntent = PendingIntent.getService(context, 0, myIntent, 0);にIntentServiceを使用します

19
Goltsev Eugene

OSは、指定した時間を考慮して、アラームの動作方法を選択します。そのため、電話が「セミスリープ」モードになると、希望するときにリソースを使用する必要がなくなります。基本的に、OSが開くための「ウィンドウ」を待機し、実行するアラームのみが実行されるため、時間のギャップが発生します。

これはMarshmallow OSで導入されたもので、デバイスのバッテリーを改善しようとしているGoogleの一部として、Nougat OSでも継続されます。

ここに、2つのオプションがあります。

  1. 遅延時間を受け入れます(ただし、 JobScheduler を使用することをお勧めします。これをお勧めします。バッテリーを節約できます)。
  2. setExactAndAllowWhileIdle を使用すると、バッテリーの問題が発生する可能性があります(これを慎重に使用すると、あまりにも多くのアラームがバッテリーに悪影響を及ぼします)。このメソッドは繰り返されないため、pendingIntentが開くサービスで実行される次のジョブを宣言する必要があります。

オプション2を選択した場合、ここから始まります。

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

おそらく、次のような回避策が考えられます。Handler.postDelayedを使用して残り時間をカバーするよりも、アラームを予定時刻の約1分前にスケジュールします。

ここでは、この種の実装の例を見つけることができます。アクティビティは最初のアラームを設定するだけです:

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

}

受信者がループを継続するよりも:

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

    }

}

役に立てば幸いです。

システムアラームに関する質問に答えるには...

Androidの標準の目覚まし時計/デスクトップ時計アプリは、setAlarmClocksetExactAndAllowWhileIdleの組み合わせを使用します。

次のコードは、通知を更新するために使用されます。

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

alarmManager.setAlarmClock(info, operation);


同時に、次のコードを使用して実際のアラームをスケジュールします。

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


setExactAndAllowWhileIdleで設定された保留中のインテントはアラームをトリガーしますが、setAlarmClockのインテントは単に無視されます。


Android Googlesource

3
Maksim Ivanov

Support.v4からメソッドを呼び出すことができます。

AlarmManagerCompat.setExact(...);

内部実装には、SDKバージョンによるチェックが含まれています。

3
northerngirl

AlarmManager.setAlarmClock 多分あなたを助けることができます。

使用しているBroadcastReceiverのタイプを確認する必要があるもう1つのことは、 WakefulBroadcastReceiver を使用することをお勧めします。

ところで、サポートのためにアラームマネージャーを使用するためのロジックを変更する必要がありますAndroid M、次のようなことができます:

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

From Android documentation of AlarmManager

API 19(KitKat)以降、アラーム配信は不正確です。OSは、ウェイクアップとバッテリー使用を最小限に抑えるためにアラームをシフトします。厳密な配信保証が必要なアプリケーションをサポートする新しいAPIがあります。 setWindow(int、long、long、PendingIntent)およびsetExact(int、long、PendingIntent)を参照してください。 targetSdkVersionがAPI 19より前のアプリケーションでは、要求されたときにすべてのアラームが正確に配信される以前の動作が引き続き表示されます。

また、setExact()を使用している場合:

アラームは、要求されたトリガー時間に可能な限り近いで配信されます。

したがって、setExactがExactであることはまだ保証されていません。

2
sJy