requestLocationUpdates after a exact fixed interval

1.6k Views Asked by At

Objective: To save current locations in Service in database after exact 15 min with in service (using less battery).I use these location at various points in my app.

locationrequest = LocationRequest.create();
        locationrequest.setInterval(5*60000);
locationrequest
        .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        locationclient.requestLocationUpdates(locationrequest, mPendingIntent);

Problem: I'm using the above code does not request location according to set interval value.Although, I'm aware that This interval is inexact. You may not receive updates at all, or you may receive them slower than requested. You may also receive them faster than requested. Sometimes, the location is updated after 1 min , I don't want to waste processing and battery to get locations at small intervals.

public class LoginActivity extends Activity implements OnClickListener  
    , 

GooglePlayServicesClient.ConnectionCallbacks,GooglePlayServicesClient.OnConnectionFailedListener,LocationListener {

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_screen);
///my code 
mIntentService = new Intent(LoginActivity.this,LocationService.class);
        mIntentService.putExtra("time",String.valueOf(System.currentTimeMillis()) );
          mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);

          int resp =GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
          if(resp == ConnectionResult.SUCCESS){
           locationclient = new LocationClient(this,this,this);
           locationclient.connect();
          }
          else{
           Toast.makeText(this, "Google Play Service Error " + resp, Toast.LENGTH_LONG).show();
          }


    @Override
    public void onLocationChanged(Location location) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onConnectionFailed(ConnectionResult arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onConnected(Bundle arg0) {
        // TODO Auto-generated method stub
        Log.i("fused", " onConnected " );
//      mIntentService = new Intent(LoginActivity.this,LocationService.class);
//        mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);

        locationrequest = LocationRequest.create();
        locationrequest.setInterval(5*60000);
//      locationrequest
//      .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
//       locationrequest = LocationRequest.create();
//         locationrequest.setInterval(1000);//??
//         locationclient.requestLocationUpdates(locationrequest, this);

    }

    @Override
    public void onDisconnected() {
        // TODO Auto-generated method stub

    }

}

LocationService

public class LocationService extends IntentService {

    private String TAG = this.getClass().getSimpleName();
    public LocationService() {
        super("Fused Location");
    }

    public LocationService(String name) {
        super("Fused Location");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
//      Log.i("fused", "onHandleIntent LocationService");

            Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
            if(location !=null){
                String time= intent.getStringExtra("time");
                Log.i("fused", "onHandleIntent LocationService " +time+"---"+ location.getLatitude() + "," + location.getLongitude());


                updateTransientLocation(getApplicationContext(), location);


            }

    }

Also, I need to save these locations periodically in database in background only and hence cannot use requestLocationUpdates without pending intent to service.

I have refered to this for the code

Thanks.

EDIT -SOLUTION This is how my problem was solved

Code in Activity

    Intent myIntent = new Intent(context,LocationReceiver.class);


        PendingIntent pi = PendingIntent.getBroadcast(context, 0, myIntent, 0);

        alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime(),
//              120000,pi);
  • I removed the location Service class and added location receiver

LocationReceiver

public class LocationReceiver extends BroadcastReceiver implements
        GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {
    SharedPreferences prefs = null;
    LocationClient locationclient = null;
    Context contxt;

    /** For location poller NO LONGER IN USE **/


    @Override
    public void onReceive(Context context, Intent intent) {
        contxt=context;
        //Log.i("locationreciever", "in location rec");
        Log.i("fused", "in location rec");


        int resp = GooglePlayServicesUtil
                .isGooglePlayServicesAvailable(context);

        if (resp == ConnectionResult.SUCCESS) {
            locationclient = new LocationClient(context, this, this);
            locationclient.connect();
        } else {
            Log.i("fused", "loc client Google Play Service Error");
        }
}

@Override
    public void onLocationChanged(Location location) {
        Log.i("fused", " onLocationChanged Location Request :" + location.getLatitude() + "," + location.getLongitude());
        updateTransientLocation(contxt, location);
        if (locationclient != null) {
            if (locationclient.isConnected()) {
                locationclient.removeLocationUpdates(this);
                locationclient.disconnect();
            }
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult arg0) {
        Log.i("fused", "loc client connection failed");
    }

    @Override
    public void onConnected(Bundle arg0) {
        Log.i("fused", "loc client onConnected");
        LocationRequest locationrequest = LocationRequest.create();
        locationrequest
    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        locationclient.requestLocationUpdates(locationrequest, this);
    }

    @Override
    public void onDisconnected() {
        Log.i("fused", "loc client disconnected");
    }
}
2

There are 2 best solutions below

1
jkgeyti On BEST ANSWER

The best solution would be to use your current approach. You're tell the OS that you don't need locations more often, but something else might be requesting locations, in which case you might as well just accept it, now that the phone has already woken up to get a GPS fix and broadcast it to every process that's interested in a location. This way, your application may actually never have to turn on the GPS, because you're basically just using a location fix that was requested by another process more often that every 15 minutes. The keyword to search for here is the new fused location provider.

If you insist on getting a location exactly every 15 minutes, you can, instead of scheduling a location request, use an AlarmManager to schedule a job to run every 15 minutes. In your alarm manager, you can then immediately request a new single location, and then completely stop requesting new locations until your job is scheduled to run again. If you go down this route, you'll likely run into problems with your service ending before you get a result, because of the asynchronous nature of the location service. Therefore, you want to poll for a location in your alarm manager. You can use a project like CWAC LocationPoller for that

The documentation has examples of how to schedule recurring events: https://developer.android.com/training/scheduling/alarms.html

Depending on your need, you should be think about the fact that a location may not be available every 15 minutes. Maybe the user is outside of GPS/wifi/phone range. So it may or may not be beneficial to start a task a bit early, or more often, to make sure you have a reasonable fix after your 15 minute window has elapsed.

With all that said, here's the code snippet you're actually interested in to solve your specific problem (taken directly from the CWAC locationpoller site):

1. Create a recurring alarm manager

mgr=(AlarmManager)getSystemService(ALARM_SERVICE);

Intent i=new Intent(this, LocationPoller.class);

Bundle bundle = new Bundle();
LocationPollerParameter parameter = new LocationPollerParameter(bundle);
parameter.setIntentToBroadcastOnCompletion(new Intent(this, LocationReceiver.class));
// try GPS and fall back to NETWORK_PROVIDER
parameter.setProviders(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER});
parameter.setTimeout(60000);
i.putExtras(bundle);


pi=PendingIntent.getBroadcast(this, 0, i, 0);
mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                                    SystemClock.elapsedRealtime(),
                                    PERIOD,
                                    pi);

2. Create a BroadcastReceiver to receive your location data

Bundle b=intent.getExtras();

LocationPollerResult locationResult = new LocationPollerResult(b);

Location loc=locationResult.getLocation();
String msg;

if (loc==null) {
  loc=locationResult.getLastKnownLocation();

  if (loc==null) {
    msg=locationResult.getError();
  }
  else {
    msg="TIMEOUT, lastKnown="+loc.toString();
  }
}
else {
  msg=loc.toString();
}

if (msg==null) {
  msg="Invalid broadcast received!";
}
0
Tom Klino On

From http://developer.android.com/reference/android/app/AlarmManager.html#setRepeating%28int,%20long,%20long,%20android.app.PendingIntent%29

as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.

So you will have to do something like this:

public void startTheClock(int interval) {       
    Intent pingerIntent = new Intent(this, findLoc.class);
    pingerIntent.setAction("start_clock");

    PendingIntent pendingIntent = PendingIntent.getBroadcast(
         this.getApplicationContext(), 
         0,
         pingerIntent,
         PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager alarms = (AlarmManager) this.getSystemService(
             Context.ALARM_SERVICE);

    alarms.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + interval,
            pendingIntent);

}

And in the class that captures that intent (in this example, findLoc.java):

public class findLoc extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {

        callMethodThatSearchesForLocation();

        startTheClock(INTERVAL);

    }

}

Where interval is a constant in miliseconds.

NOTE: I actually had some problems with that because it displayed an error on setExact(..) since my minimum SDK did not support this. Which is a bit of a paradox if you want the same behaviour on SDK lower than 19 and higher or equal to 19.