Wednesday, February 6, 2013

Timer Class and Alarm Manager


My app was working well. Perhaps not perfectly, but pretty close such that I was very happy with how it was going. My app had a Timer running every second, with a whole bunch of integers iterating and keeping time sensitive tasks in check.

For instance, I need a GPS location for my app, and i need it fairly often. I basically try to get a GPS fix every 5 min with a 2 min 'timeout', i.e. every 5 min it will turn the GPS on, if it cannot find a fix in 2 min it will turn off. If it does find a fix, it will turn off.

So obviously here i need two different integers, one iterating for the 5min counter and the other for the 2min counter, i realise i could use one counter and just have boolean for the state of GPS, but this is not all the counters do (I promise, there is always method to my madness).

My Timer looked something like this:



public class SomeService extends Service
{
public int fiveMinCounter = 0,twoMinCounter = 0;


 @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

 @Override
    public void onCreate(){
    Intent startActIntent = new Intent(context,Splash.class);

    startActPending = PendingIntent.getActivity(context, 0,startActIntent, 0);
    Notification noti = new NotificationCompat.Builder(
          context).setContentTitle(getResources().getString(R.string.app_name))
          .setSmallIcon(R.drawable.icon).setContentIntent(startAct)
          .setWhen(System.currentTimeMillis())
          .setContentText("Touch to open UI").getNotification();
    this.startForeground(1,noti);
    Timer timer  = new Timer();
    timer.schedule(new mTimerTask(),1000,1000);
    }  

public class mTimerTask extends TimerTask{

  @Override
     public void run(){
     //Run some code here
     fiveMinCounter++;
     twoMinCounter++;
     }
  }

}

So this was working fine, doing what I needed it to. However at one stage I started to notice that the GPS would hang sometimes...it would not turn off at all. After some investigation, I realised that not only was the GPS staying on, and subsequently annihilating my phones battery, but the counters had stopped increasing.
The Timer had stopped. Dead.

It no longer ran through the code I had specified. It stopped while the GPS was on, a most unfortunate time to stop.

I did a bit of digging and discovered a few other people who had similar issues with the Timer class. For some reason after an unspecified amount of time, supposedly when the phone goes into deep sleep, the Timer will stop. Even when it is embedded in a Service such as the one above, which is also a foreground service and should not be stopped even by the System. It didn't happen very often, but the sad truth is, if it happens once its dangerous. Especially a bug like this which can stall the GPS in the on state. A battery can literally drain in about 3 to 4 hours with the GPS on permanently.

I need a solution. Something that can do a similar thing, but will not be killed by the System randomly.

I found the Alarm Manager Class, which I have used previously for a task which needed to execute every 24 hours or so.

I altered my code to look like this:

public class SomeService extends Service
{
public int fiveMinCounter = 0,twoMinCounter = 0;
public static Receiver receiver;
public static long second = (long) 1000;
 @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

 @Override
    public void onCreate(){
    Intent startActIntent = new Intent(context,Splash.class);

    startActPending = PendingIntent.getActivity(context, 0,startActIntent, 0);
    Notification noti = new NotificationCompat.Builder(
          context).setContentTitle(getResources().getString(R.string.app_name))
          .setSmallIcon(R.drawable.icon).setContentIntent(startAct)
          .setWhen(System.currentTimeMillis())
          .setContentText("Touch to open UI").getNotification();
  
    this.startForeground(1,noti);
    
    if (receiver == null) {
         receiver = new Receiver();
         registerReceiver(receiver, new IntentFilter("KEEP_CONNECTION_ALIVE"));
        }
    AlarmManager alarmManager = (AlarmManager) context
                    .getSystemService(ALARM_SERVICE);
    Intent intent = new Intent("KEEP_CONNECTION_ALIVE");
    PendingIntent pendingInt = PendingIntent.getBroadcast(this, 2, intent, 
                    PendingIntent.FLAG_UPDATE_CURRENT);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),
                    second, pendingInt);
    }  

public class Receiver extends BroadcastReceiver{

  @Override
     public void onReceive(Context context, Intent intent){
     //Run some code here
     fiveMinCounter++;
     twoMinCounter++;
     }
   }

}
That seems to have fixed the issue. There is another issue where there is a delay in the first execution of the BroadcastReceiver, but it seems to try and catch up executing the number of times it missed.

It runs as well now, and from what I have read about using AlarmManager.RTC_WAKEUP it will wake the phone up to run the code even if it is in deep sleep. So far I have noticed no negative impact on the battery life using this method.

I think the difference lies in the fact that the Timer Class is just that, a class where as the AlarmManager is a System service, so perhaps it is able to stay alive when the Timer would normally be killed.

Hope this helps someone who might have a similar issue to me.

Edit: I am trying to get all the code to be formatted and highlighted correctly, I am new to doing this in a blog, so give me a little break ;-)

No comments:

Post a Comment