My application for students downloads marks in a background service and checks if there are more marks than last time. If there are any new marks, it pushes notification and rewrites the number of marks in sharedPreferences. It works, but sometimes it creates multiple notifications for the same mark at the same time.
The IntentService which triggers a broadcast receiver:
public int onStartCommand(Intent intent, int flags, int startId) {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(getApplicationContext(), CheckNewMarksReceiver.class);
PendingIntent alarmIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, i, 0);
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
0,
2500, alarmIntent);
return START_STICKY;
}
The broadcast receiver onReceive method initializes sharedPreferences attribute and numberOfMarksSaved and starts AsyncTask that downloads data:
DownloadDataTask task = new DownloadDataTask();
task.execute(context.getString(R.string.LOGIN_PAGE_URL), username, password);
dataPreferences = context.getApplicationContext().getSharedPreferences(context.getString(R.string.SHARED_PREFERENCES), Context.MODE_PRIVATE);
numberOfMarksSaved = dataPreferences.getInt(context.getString(R.string.PREFERENCE_NUMBER_OF_MARKS_KEY), -1);
AsyncTasks onPostExecute method checks if the number of downloaded marks is greater than last time:
if (numberOfDownloadedMarks > numberOfMarksSaved) {
...
notifyUserAbout(newMarks, response); // method to fire a notification
}
dataPreferences
.edit()
.putInt(context.getString(R.string.PREFERENCE_NUMBER_OF_MARKS_KEY),
numberOfDownloadedMarks)
.apply();
As I said, this mostly works, but sometimes the BroadcastReceiver is triggered twice in a row immediately - the first one rewrites the value in sharedPreferences, but the second one doesn't "notice" that value in shared preferences was changed. How can I prevent that?
EDIT:
I tried even setRepeating instead of setInexactRepeating - nothing changed (I suspect android alarmManager time shifting). Here is my Log:
06-11 18:14:27.732 ... I/Just about to: download new data
06-11 18:14:27.761 ... I/Just about to: download new data
06-11 18:14:27.907 ... I/Saved & new: 89, 90
06-11 18:14:27.933 ... I/Notification baked - id: 1077819997
06-11 18:14:28.004 ... I/Saved & new: 89, 90
06-11 18:14:28.006 ... I/Notification baked - id: 1077820069
Because of how inexact repeating alarms work and the long running background operations, most probably there are multiple AsyncTask instances running at the same time when the problem occurs.
From the docs of AlarmManager:
Your alarm's first trigger will not be before the requested time, but
it might not occur for almost a full interval after that time. In
addition, while the overall period of the repeating alarm will be as
requested, the time between any two successive firings of the alarm
may vary.
For such low intervals i'd suggest to use setRepeating() instead of setInexactRepeating()
and
keep an instance of your AsyncTask in your Service.
Then, you could do something like this:
if(task.getStatus == AsyncTask.Status.FINISHED) {
task = new DownloadDataTask();
task.execute(context.getString(R.string.LOGIN_PAGE_URL), username, password);
}
To prevent multiple instances of your AsyncTask running at the same time.
Related
I am running a code, where the user selects a date and time. The user can select any date and time in the future. These dates and time are stored in sqlite database. After the user selects those dates and time, the activity calls a service class, where I am running a new thread in the following way
public int onStartCommand(Intent intent, int flags, int startId) {
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
final SqliteController db = new SqliteController(getApplicationContext());
new Thread(new Runnable() {
#Override
public void run() {
List<Greetings> greetings = db.getAllGreetings();
if (db.getGreetingsCount() >= 0) {
do {
for (Greetings g : greetings) {
Calendar cal = Calendar.getInstance();
.........
.........//other codes
This thread access the data from the database and matches the time and date with the system time and date. One the date and time matches, I am using alarm manager with broadcast receiver like this
if (dnt.equals(cdt)) {
Intent aint = new Intent(getApplicationContext(), AlarmReceiver.class);
aint.putExtra("msg", msg);
aint.putExtra("phone", phone);
aint.putExtra("id", id);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), id, aint, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
db.deleteGreetings(g);
}
I wanted to know, Is this the correct way to do it? When I run the program in emulator, sometimes it runs fine, but other times it shows "Application doing too much work in the main thread". So, am I doing something wrong? or is there a better way to do it?
Since nearly all of your posted code is running in a background thread, this would not cause your application to be doing too much work in the main thread. Your problem is likely coming from another source.
You could try profiling your app using traceview as described in the Android documentation here. Try to find which methods are consuming the most time to narrow down your search.
As an aside, you should use an IntentService instead of a Service if your service is just creating a single thread running this task in the background. You then implement the onHandleIntent method instead of the onStartCommand method. The IntentService will handle all operations it receives in a single background Looper.
i have setting repeated alarm manager to start service which fetch the location and send SMS but currently i am only writing time duration in file to check the alarm accuracy .
I find that the alarm manager not working fine , i set for one hours interval but it fired at 30 min. interval . I left it for a day and find that after 12'o clock the alarm accuracy is right. What happening ??
My Activity class which start alarm :
enter code here
public static final long ALARM_TRIGGER_AT_TIME = SystemClock.elapsedRealtime() + 20000;
public static final long ALARM_INTERVAL = 1000 * 60 * 60 ;
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent myIntent = new Intent(AutoMainActivity.this, TrackerService.class);
pendingIntent = PendingIntent.getService(AutoMainActivity.this, 0, myIntent, 0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,ALARM_TRIGGER_AT_TIME, 1000 * 60 * 60,pendingIntent);
AND my service class :
TraceService :
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
//writing in file to view time
java.util.Date systemDates = Calendar.getInstance().getTime();
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
currentTime = simpleDateFormat.format(systemDates);
// create file and write current time
generateNoteOnSD("ONSTART"+currentTime+"\n","Onstart.txt");
}
After executing this apps at 3:18 PM time .The file created by service show the alarm time . Check this :
alarm manager time
ONSTART2013-05-13-15:18:26
ONSTART2013-05-13-15:21:58
ONSTART2013-05-13-15:54:21
ONSTART2013-05-13-16:18:25
ONSTART2013-05-13-17:18:26
ONSTART2013-05-13-17:49:21
ONSTART2013-05-13-18:18:25
ONSTART2013-05-13-19:18:28
ONSTART2013-05-13-20:10:51
ONSTART2013-05-13-20:18:29
ONSTART2013-05-13-20:48:49
ONSTART2013-05-13-21:18:30
ONSTART2013-05-13-21:58:58
ONSTART2013-05-13-22:18:38
ONSTART2013-05-13-22:56:00
ONSTART2013-05-13-23:18:43
ONSTART2013-05-13-23:48:49
ONSTART2013-05-14-00:18:44
ONSTART2013-05-14-01:18:45
ONSTART2013-05-14-02:18:45
ONSTART2013-05-14-03:18:45
ONSTART2013-05-14-04:18:45
ONSTART2013-05-14-05:18:44
ONSTART2013-05-14-06:18:44
ONSTART2013-05-14-07:18:44
You can check that when alarm manager start at 15:18 PM , it start again after 30 min. approx. But after 12'o clock it work fine !!! How to fix it. I need that alarm start every one hour not before that .
Try with AlarmManager.RTC_WAKEUP once instead of AlarmManager.ELAPSED_REALTIME_WAKEUP
There is an example in this : Alarm Manager Example
I suspect that you have multiple alarms pending. I would recommend that you clear out all of your alarms before you set this one.
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
// Cancel alarms
try {
alarmManager.cancel(intent);
} catch (Exception e) {
Log.e(TAG, "AlarmManager update was not canceled. " + e.toString());
}
}
(how to cancel alarms is answered well in this answer)
Not sure why everyone complicates this issue so much. I'm seeing it everywhere. Answers and answers about calculating the next hour.
There's absolutely no need to calculate anything. The the HOUR from the Calender and use that as the interval. The HOUR is called everytime the hour changes. That's all there really is to it.
Some devices may need a little tweaking to make sure the HOUR being called is not the same one for consecutive times. This is due to some devices calling the HOUR sometimes when the minute changes.
I have an android activity where there is an EditText and as user types in, it calls the service on every key typed. I believe this is not efficient because more than required calls are being made. So the solution is to have some sort of pause checking there.
if (PauseOfThreeSeconds) {
// call the service here
}
How can I sense a pause and then only call the service?
Start a handler with post delayed for 3 seconds every time the key stroke is made. When ever you get a key store, cancel the runnable that is already in the queue and start a new runnable like i mentioned above.
You should schedule an alarm to start the service on each button press, but also to cancel any previously scheduled alarms so they don't go off as well:
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
long alarmWaitTime = 3000;
onButtonClicked(View v){
Intent i = new Intent(AndroidAlarmService.this, MyAlarmService.class);
PendingIntent pi = PendingIntent.getService(AndroidAlarmService.this, 0, i, 0);
// Cancel any previously set alarms
alarmManager.cancel(pi);
// set a new alarm
alarmManager.set(AlarmManager.RTC, System.getTimeInMillis() + alarmWaitTime , pi);
}
I'm trying to check if my alarm is active or not. The alarmIsSet method will return false before the alarm is set, true when the alarm is set. So far so good, however, after the alarm i canceled alarmIsSet will continue to return true until I reboot the device.
How do I fix this?
public class Alarm extends Activity {
private Intent intent = new Intent("PROPOSE_A_TOAST");
private void alarm (boolean activate) {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
if(activate == true) {
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = 3000;
long triggerTime = SystemClock.elapsedRealtime();
am.setRepeating(type, triggerTime, interval, pi);
} else {
am.cancel(pi);
}
}
private boolean alarmIsSet() {
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}
}
You just have to add
pi.cancel();
after
am.cancel(pi);
After having some headaches with this stuff myself, I found out that if I somehow had created a pending intent while testing stuff, that it actually was not cleared between tests. Even killing the app didn't do it. The intent still stayed in the system and kept returning true when checking for it. I actually had to write some code to kill it before it tested right.
Easiest way is to check the values of the (date and) time in the alarm variable, if it is not the same value as when an alarm has not been set (for you to check once what that is) then it would indicate the alarm is active and at the time of the check in the program it is either a time that has passed and the alarm has sounded or it is a time that is yet to arrive and the alarm has not yet sounded or gone off. Note that the rules may permit only one alarm activation per device session before a reboot or power off or every 12 or 24 hours and that could be why the status is not cleared.
I changed AlarmController.java in ApiDemo a little bit, so I want the alarm not to go off when the phone is sleeping by using AlarmManager.RTC.
Intent intent = new Intent(AlarmController.this, RepeatingAlarm.class);
PendingIntent sender = PendingIntent.getBroadcast(AlarmController.this,
0, intent, 0);
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC, //AlarmManager.ELAPSED_REALTIME_WAKEUP,
firstTime, 15*1000, sender);
The receiver code is like below:
public class RepeatingAlarm extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.d("DEBUG", "In RepeatingAlarm.onReceive, intent=" + intent);
Toast.makeText(context, R.string.repeating_received, Toast.LENGTH_SHORT).show();
}
}
I ran the modified app, but I still see many log messages as below after the phone wento sleep (the screen was black):
D/DEBUG ( 1390): In RepeatingAlarm.onReceive, intent=Intent { flg=0x4 cmp=com.example.android.apis/.app.RepeatingAlarm (has extras) }
This means the flag AlarmManager.RTC didn't work. Can someone tell me why?
Thanks.
Since you are using elapsedRealtime to get the alarm start time, I think you need to use the ELAPSED_REALTIME flag instead of the RTC flag.
My guess is that the alarm manager is thinking it's missed a ton of alarms because you are using the RTC flag which means the alarm manager is expecting you to send a time value in milliseconds since Jan 1st 1970, but instead you are sending elapsed milliseconds since the device booted, which is going to be a much much smaller number.
If you use the RTC flags you need to use System.currentTimeMillis() or get the time in milliseconds from a Java Date or Calendar object. If you use ELAPSED_REALTIME flags then you need to use SystemClock.elapsedRealtime().