java Android 警报管理器设置在特定时间重复
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26138587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android Alarm Manager Set Repeating at Specific Timing
提问by forcewill
I am having some problem with alarm manager in Android. So what I am trying to do is set the alarm to repeat to run the DB insertion every day around 12.01AM.
我在 Android 中遇到了警报管理器的一些问题。所以我想要做的是将警报设置为每天在凌晨 12 点左右重复运行数据库插入。
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 0 );
calendar.set(Calendar.MINUTE, 1);
notificationCount = notificationCount + 1;
AlarmManager mgr = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent notificationIntent = new Intent(context,
ReminderAlarm.class);
notificationIntent.putExtra("NotifyCount", notificationCount);
PendingIntent pi = PendingIntent.getBroadcast(context,
notificationCount, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi);
So basically I've came up with these code. However, the alarm manager execute again after the minute I set it.
所以基本上我想出了这些代码。但是,警报管理器在我设置的那一分钟后再次执行。
Let's say I run the apps on 01/10/2014 5.48PM. I wanted this to run the DB insertion when onReceive every day after I set it around 12.01AM only. But somehow, the alarm manager execute at 01/10/2014 5.49PM which is one minute after I set it and it stopped working.
假设我在 01/10/2014 5.48PM 运行应用程序。我希望它在每天上午 12 点左右设置后在 onReceive 时运行数据库插入。但不知何故,警报管理器在 01/10/2014 5.49PM 执行,这是我设置它后一分钟,它停止工作。
I wonder which part I did wrongly.
我想知道我做错了哪一部分。
Thanks in advance.
提前致谢。
EDIT
编辑
Recurring classFor this class, it will trigger the alarm manager everyday and pass the variables along to reminder alarm class for DB insertion.
Recurring class对于这个类,它会每天触发警报管理器并将变量传递给提醒警报类以进行数据库插入。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recurring);
context = this;
buildListView();
if(!alarmInitialized(this)) {
scheduleAlarms(this);
}
}
// And the few methods you suggested to schedule the alarm
public static void scheduleAlarms(Context context) {
Calendar calendar = Calendar.getInstance();
if (hasRunnedToday(context)) { // if the alarm has run this day
calendar.add(Calendar.DATE, 1); // schedule it to run again starting
// tomorrow
}
long firstRunTime = calendar.getTimeInMillis();
AlarmManager mgr = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent notificationIntent = new Intent(context, ReminderAlarm.class);
PendingIntent pi = PendingIntent.getActivity(context, 0,
notificationIntent, 0);
mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstRunTime,
AlarmManager.INTERVAL_DAY, pi);
ComponentName receiver = new ComponentName(context, BootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
BootReceiver class
BootReceiver 类
public void onReceive(Context context, Intent i) {
if (i.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Recurring.scheduleAlarms(context);
}
}
ReminderAlarm classBasically for this class it just grab the variable passed from Recurring class and execute the DB insertion. I did inserted some Toast.makeText to test if it is retrieving but no luck by testing it.
ReminderAlarm 类基本上对于这个类,它只是获取从 Recurring 类传递的变量并执行 DB 插入。我确实插入了一些 Toast.makeText 来测试它是否正在检索,但通过测试没有运气。
public class ReminderAlarm extends BroadcastReceiver {
private NotificationManager mNotificationManager;
private Notification notification;
@Override
public void onReceive(Context context, Intent intent) {
String recurID = null;
String recurStartDate = null;
String currentDate = null;
String description = null;
String type = null;
String amount = null;
String categoryName = null;
String frequencyStr = null;
String nextPaymentDate = null;
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
DatabaseAdapter mDbHelper = new DatabaseAdapter(context);
mDbHelper.createDatabase();
mDbHelper.open();
RecurringController rc = new RecurringController(mDbHelper.open());
ArrayList<RecurringModel> recur_list = rc.getAllRecurring();
// THIS PART TO GET DATA FROM DATABASE
for (int i = 0; i < recur_list.size(); i++) {
recurID = recur_list.get(i).getRecurringID();
recurStartDate = recur_list.get(i).getRecurringStartDate();
currentDate = dateFormat.format(new Date());
description = recur_list.get(i).getRecurringDesc();
type = recur_list.get(i).getRecurringType();
amount = Float.toString(recur_list.get(i).getRecurringAmount());
categoryName = recur_list.get(i).getCategoryID();
frequencyStr = recur_list.get(i).getFrequency();
Toast.makeText(context,
description, Toast.LENGTH_LONG)
.show();
Toast.makeText(context,
recurStartDate Toast.LENGTH_LONG)
.show();
Calendar cal = Calendar.getInstance();
try {
cal.setTime(dateFormat.parse(recurStartDate));
if (frequencyStr.equals("Daily")) {
cal.add(Calendar.DAY_OF_MONTH, 1);
nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
cal.add(Calendar.DAY_OF_MONTH, -1);
} else if (frequencyStr.equals("Weekly")) {
cal.add(Calendar.WEEK_OF_YEAR, 1);
nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
cal.add(Calendar.WEEK_OF_YEAR, -1);
} else if (frequencyStr.equals("Monthly")) {
cal.add(Calendar.MONTH, 1);
nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
cal.add(Calendar.MONTH, -1);
} else if (frequencyStr.equals("Yearly")) {
cal.add(Calendar.YEAR, 1);
nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
cal.add(Calendar.YEAR, -1);
}
} catch (ParseException e) {
e.printStackTrace();
}
// If dates match then execute the SQL statements
if (currentDate.equals(nextPaymentDate)) {
// mDbHelper.createDatabase();
// mDbHelper.open();
TransactionRecModel trm = new TransactionRecModel();
CategoryController cc = new CategoryController(mDbHelper.open());
trm.setDate(currentDate);
trm.setTransDescription(description);
trm.setType(type);
trm.setAmount(Float.parseFloat(amount));
// Get the categoryID based on categoryName
String catID = cc.getCatIDByName(categoryName);
trm.setCategory(catID);
// Check if the recurring record exists before insert new
// transaction record
boolean recurExist = rc.checkRecurExist(recurStartDate,
description, catID);
if (recurExist == true) {
TransactionRecController trc = new TransactionRecController(
mDbHelper.open());
// Check if the transaction record exists to prevent
// duplication
boolean moveNext = trc.checkTransExist(trm);
if (moveNext == false) {
if (trc.addTransactionRec(trm)) {
// Update recurring start date after insertion of
// transaction
RecurringModel rm = new RecurringModel();
rm.setRecurringID(recurID);
rm.setRecurringStartDate(currentDate);
if (rc.updateRecurringDate(rm)) {
mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent
.getActivity(
context,
Integer.parseInt(intent
.getExtras()
.get("NotifyCount")
.toString()),
new Intent(), 0);
notification = new Notification(
R.drawable.ic_launcher, "Notification",
System.currentTimeMillis());
notification.setLatestEventInfo(context,
description, nextPaymentDate,
contentIntent);
mNotificationManager
.notify(Integer.parseInt(intent
.getExtras().get("NotifyCount")
.toString()), notification);
mDbHelper.close();
}
}
}
}
mDbHelper.close();
}
}
mDbHelper.close();
Recurring.updateAlarmLastRun(context);
}
}
I've added this part of codes in the part you suggested to schedule the alarm to call the BootReceiver class. Then from BootReceiver class, I will call back to the Recurring class and Reminder Alarm class:
我已经在您建议安排警报调用 BootReceiver 类的部分中添加了这部分代码。然后从 BootReceiver 类,我将回调到 Recurring 类和 Reminder Alarm 类:
ComponentName receiver = new ComponentName(context, BootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
回答by forcewill
The problem is in calendar.getTimeInMillis()
in
问题出 calendar.getTimeInMillis()
在
mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi);
The second argument to setInexactRepeating
quoting the doc
setInexactRepeating
引用文档的第二个参数
triggerAtMillis time in milliseconds that the alarm should first go off, using the appropriate clock (depending on the alarm type). This is inexact: the alarm will not fire before this time, but there may be a delay of almost an entire alarm interval before the first invocation of the alarm.
triggerAtMillis 使用适当的时钟(取决于警报类型)首先响起警报的时间(以毫秒为单位)。这是不准确的:在此时间之前警报不会触发,但在第一次调用警报之前可能会有几乎整个警报间隔的延迟。
Meaning it will run the first time aproximally one minute after you set it because of
这意味着它将在您设置后大约一分钟第一次运行,因为
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 0 );
calendar.set(Calendar.MINUTE, 1);
If you wan't the first run of the alarm to be the next day do a calendar.add(Calendar. DATE, 1);`
如果您不想在第二天第一次运行闹钟,请执行 calendar.add(Calendar.DATE, 1);`
As to the it stopped working, did you reboot de device ?
AlarmCalendar alarms don't persist to device reboot, you can register a BroadcastReceiver
to receive BOOT_COMPLETED
event and register the alarm again check
does Alarm Manager persist even after reboot?
至于它停止工作,您是否重新启动了设备?警报日历警报不会持续到设备重启,您可以注册一个BroadcastReceiver
接收BOOT_COMPLETED
事件并再次注册警报,检查警报管理器是否
在重启后仍然存在?
Update: as you requested here is some help after reviewing your code
更新:正如您所要求的,在查看您的代码后可以提供一些帮助
In your BOOT_COMPLETED
Receiverclass:
在您的BOOT_COMPLETED
Receiver类中:
public void onReceive(Context context, Intent i) {
if (i.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
ReminderAlarm.scheduleAlarms(this);
}
}
In your ReminderAlarmclass
在您的ReminderAlarm类中
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recurring);
if(!alarmInitialized(this) {
scheduleAlarms(this);
}
}
}
public static void scheduleAlarms(Context context) {
Calendar calendar = Calendar.getInstance();
if(hasRunnedToday(context)) { //if the alarm has run this day
calendar.add(Calendar.DATE, 1); //schedule it to run again starting tomorrow
}
long firstRunTime = calendar.getTimeInMillis();
AlarmManager mgr = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent notificationIntent = new Intent(context, ReminderAlarm.class);
PendingIntent pi = PendingIntent.getActivity(context, 0,
notificationIntent, 0);
mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
firstRunTime, AlarmManager.INTERVAL_DAY, pi);
}
public static boolean alarmInitialized(Context context) {
SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);
long alarmLastRun = preferences.getLong("AlarmLastRun", -1);
return alarmLastRun != -1;
}
public static void updateAlarmLastRun(Context context) {
SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);
preferences.edit()
.putLong("AlarmLastRun", new Date().getTime())
.apply();
}
public static boolean hasRunnedToday(Context context) {
SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);
long alarmLastRun = preferences.getLong("AlarmLastRun", -1);
if(alarmLastRun == -1) {
return false;
}
//check by comparing day, month and year
Date now = new Date();
Date lastRun = new Date(alarmLastRun);
return now.getTime() - lastRun.getTime() < TimeUnit.DAYS.toMillis(1);
}
Each time your Reminderclass alarm runs you should call updateAlarmLastRun
to update the last time the alarm has run, this is necessary because the alarm may be schedule to be run on a day and the user reboots the device before the alarm has run in that case we don't want to
use calendar.add(Calendar.DATE, 1);
since that would skip a day.
每次您的提醒类警报运行时,您都应该调用updateAlarmLastRun
更新警报运行的最后时间,这是必要的,因为警报可能被安排在一天运行并且用户在警报运行之前重新启动设备,在这种情况下我们不想使用, calendar.add(Calendar.DATE, 1);
因为那会跳过一天。
On your Manifest.xml
在你的 Manifest.xml
<receiver android:name=".BootReceiver" android:enabled="true" android:exported="false" android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Notes:
笔记:
- You shouldn't do
context = this
if context is a class field since the object holds a reference to its fieldcontext
andcontext
field holds a reference to the object that would leak - Your
Receiver
'onReceive` doesn't has the extras you assumed to have like "notificationCount" onReceive by the system when your device finish boot. - Once your alarm runs call
updateAlarmLastRun
- 你不应该这样做
context = this
,因为对象持有到该领域的引用,如果上下文是一个类的字段context
和context
字段保存到会泄漏的对象的引用 - 您的
Receiver
“onReceive`不具有你所假设的系统时,您的设备完成开机能有像‘notificationCount’的onReceive的花絮。 - 一旦您的闹钟响起,请致电
updateAlarmLastRun
Hope any of this helps
希望这些有帮助