Android AlarmManager setExact()并不准确

Gol*_*ene 20 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

triggerTime 计算 SystemClock.elapsedRealtime() + 600_000;

当警报触发时,首先我计划新警报,然后我才会执行我的计划任务.

setAlarm();
mySheduledTask;
Run Code Online (Sandbox Code Playgroud)

WAKE_LOCK我的清单中有权限.

当我在Android 4上测试它时 - 它工作得很完美(偏差可能是12-15 毫秒).

但是当我在小米Redmi Note 3 Pro(5.1.1)上运行应用程序时 - 偏差可能长达15秒!

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

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

PS.我使用IntentServicePendingIntent = PendingIntent.getService(context, 0, myIntent, 0);

Dus*_*Dus 33

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

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

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

  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);
Run Code Online (Sandbox Code Playgroud)

  • 哦,我需要向用户显示某种消息:“对不起,您没有听到警报并且没有准时起床,因为确切的警报触发会导致电池问题”。 (3认同)
  • 好点,谢谢。但问题出在 Lollipop 上,而不是 Marshmellow 上。另外,系统警报是如何工作的(在计划的时候效果很好)? (2认同)
  • 在第二个`if`棒棒糖应该用Kitkat代替. (2认同)

小智 11

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

AlarmManagerCompat.setExact(...);
Run Code Online (Sandbox Code Playgroud)

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


Fed*_*ini 7

可能的解决方法可能是这样的:您在预期时间前约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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

比接收器继续循环:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

我希望它有所帮助.

  • 有趣的解决方案,赞成它。但它很hacky,只会将它用作最后的手段。谢谢! (2认同)

Mak*_*nov 5

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

Android的股票闹钟/座钟的应用程序使用的组合setAlarmClocksetExactAndAllowWhileIdle

以下代码用于更新通知:

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

alarmManager.setAlarmClock(info, operation);
Run Code Online (Sandbox Code Playgroud)


同时以下代码用于调度实际警报:

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)
}
Run Code Online (Sandbox Code Playgroud)


setExactAndAllowWhileIdle 中 设置的 Pending 意图触发警报,而setAlarmClock 的意图则被简单地忽略。


安卓谷歌资源

  • 欢迎来到 Android 的世界,在这个世界中,即使是 Google 开发人员也必须破解。 (11认同)

sJy*_*sJy 2

来自AlarmManager的android文档

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

另外,在使用 setExact() 时:

警报将尽可能接近请求的触发时间。

所以仍然不能保证 setExact 一定是 Exact。

  • 我确实阅读了文档。好的,那么设备上的闹钟是如何工作的呢?效果很好,没有任何偏差。 (6认同)