Android AlarmManager setExact()并不准确

时间:2016-06-29 08:39:45

标签: android alarmmanager

我需要每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 毫秒)。

但是当我在小米Redmi Note 3 Pro(5.1.1)上运行app时 - 偏差可以达到 15秒!

例如,我在我的日志文件中看到:第一次运行是在1467119934477(RTC时间),第二次是在1467120541683.差异是 607_206 毫秒,而不是 600_000 ,按计划进行!

我缺少什么?有什么方法可以模拟系统警报的行为(它是可以描述我的大头钉的最接近的用例)?

PS。我对PendingIntent = PendingIntent.getService(context, 0, myIntent, 0);

使用IntentService

6 个答案:

答案 0 :(得分:30)

操作系统会根据您指定的时间选择警报的工作方式。因此,当手机进入“半睡眠”模式时,无需在您希望的时候使用该资源。基本上,它等待操作系统为其打开的“窗口”,然后只有你想要运行的警报才会运行,这就是你遇到时间差的原因。

这是在Marshmallow操作系统上引入的,并将继续在Nougat操作系统上,作为谷歌尝试改进设备电池的一部分。

这就是事情,你有两个选择:

  1. 接受时间延迟(但可以考虑使用JobScheduler,这是更推荐的,可以节省电池电量)。
  2. 使用setExactAndAllowWhileIdle可能会导致电池问题(请小心使用,太多警报会对您的电池造成不利影响)。 此方法不重复,因此您必须声明要在pendingIntent打开的服务上运行下一个作业。
  3. 如果你选择选项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);
    

答案 1 :(得分:6)

可能的解决方法可能是这样的: 您在预期时间前约1分钟安排警报,而不是使用Handler.postDelayed来覆盖剩余时间。

在这里您可以找到这种实现的示例。 该活动只是设置了第一个警报:

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

    }

}

我希望它有所帮助。

答案 2 :(得分:3)

回答关于系统警报的问题......

Android的股票闹钟/桌面时钟应用程序结合使用 setAlarmClock setExactAndAllowWhileIdle

以下代码用于更新通知:

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 :(得分:2)

来自AlarmManager

的Android文档
  

从API 19(KITKAT)开始,警报传递不准确:操作系统将移动警报以最小化唤醒和电池使用。有新的API支持需要严格交付保证的应用程序;请参阅setWindow(int,long,long,PendingIntent)和setExact(int,long,PendingIntent)。 targetSdkVersion早于API 19的应用程序将继续查看之前在请求时准确传递所有警报的行为。

同时使用setExact():

  

警报将尽可能发送到请求的触发时间。

所以它仍然不能保证setExact将是Exact。

答案 4 :(得分:2)

您可以尝试使用AlarmManager.setAlarmClock也许它可以帮助您。

您需要检查您正在使用哪种类型的BroadcastReceiver,最好使用WakefulBroadcastReceiver

顺便说一下你需要改变逻辑以使用Alarm Manager来支持Android M,你可以这样:

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

答案 5 :(得分:2)

您可以从support.v4调用该方法:

AlarmManagerCompat.setExact(...);

内部实现包含sdk版本的检查。