I am running a service in the background that reads GPS/Network location and needs to do the following:
run in background without interruptions on app restart and keep it alive as much as possible without being killed (This is solved with the help of Merlin's comment bellow)
on a new location received, call a web service and send the read location
have a repetitive task running every 60 seconds and resend last location to the web service. This will help in case the user stays in the same position.
There are a few things I have considered and I'm not sure if I understood right. The service runs in the same thread as the main app, so sending the location to the server on same thread as the UI thread may trigger UI freezes and this is not good. Also I'm not sure if GPS/Network listeners have their own threads or use the same thread as the app.
Here is a shortened code of the service to make things clearer:
public class GPSLoggerService extends Service {
#Override
public void onCreate() {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 50, locationListenerNetwork);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 50, locationListenerGps);
scheduleTaskExecutor = Executors.newScheduledThreadPool(5);
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
updateLocation(lastLocation);
}, 60, 60, TimeUnit.SECONDS);
return START_STICKY;
}
LocationListener locationListenerGps = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
updateLocation(location);
}
...
}
LocationListener locationListenerNetwork = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
updateLocation(location);
}
...
}
private void updateLocation(Location readLocation) {
//web service call
String response = WebServiceCalls.updateLocation(readLocation);
//log data in a local Sqlite database
saveLocation(readLocation)
}
My main concern is how to handle the updateLocation call and have it in a separate thread from the main app thread. The scheduleTaskExecutor I belive it's not the way to go. Sometimes, even after I call stopService() the service remains alive, even if I tell the TaskExecutor to shutDown. I can't find another explanation for which the service isn't stoping.
So to recap: I need to send the location each time the listeners receive a new location and resend it every 60 seconds. I also need to be able to stop the service quickly with active threads canceling.
How would you recommend me to handle my case ?
I'd use an IntentService and just use the AlarmManager to fire off intents.
The major advantage of this is that there is no Thread code to worry about as it does its work in the background
UPDATE
Another interesting approach can be found in https://stackoverflow.com/a/7709140/808940
The Service runs the same process as main app, not thread. Also if you want to run service in another process, then you can use the android:process tag.
I'm not sure why you want to call WebService every 60 secs, because 60 secs is too less. Also you should skip calling the WebService when location has not changed, because it requires a network communication and it is a costly operation.
There is no need to use the Executors. You should keep the number of threads as less as possible. To perform a task at particular interval, use AlarmManager to deliver intent at a particular time. Check setRepeating() method for setting the alarm.
Another thing is, you should try to avoid doing any task in Listener. Because there is a timeout of 10 seconds that the system allows before considering the receiver/listener to be blocked and a candidate to be killed. You should use the Handler to perform tasks in the background thread (i.e. whenever you receive the update from listener, add a message to Handler queue and it will be picked when Handler thread is free).
For prevent your service to destroy you can start your service as a foreground service.
And after getting a location from onLocationChanged() method you can use a asynctask for send a location to the webservice so it will not block your UI.
Edit
You can set the minimum time and minimum distance traveled in your requestLocationUpdates method. So I don't think you should use the scheduler task for send location to server. According to the argument about min time and min distance the location manager will check the location. If there is a location changed then it will call onLocationChanged() method with new Location.
Now for your solution about user stays in the same position. you can change some logic to server side like if there is a 1 hour difference between two successive locations location1 and location2 means that user has stayed 1 hour at location1.
You can use a single LocationListener class to listen GPS and NETWORK location.
When you get location in the onLocationChanged() method you can send that location using a asynctask.
After getting the location you can save that location in the preference or database to check weather the GPS and Network provider sending you same location so if you will track then you can save your webAPI call and so you can save some portion of the battery.
You should use AsynchTask:
public class RefreshTask extends AsyncTask<Integer, Void, Integer> {
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
public RefreshTask() {
}
protected Integer doInBackground(Integer... millis) {
try{
int waitTime = 0;
while(waitTime<60000){
Thread.sleep(100);
waitTime += 100;
}
//update location here
}catch(InterruptedException e){
}
return 1;
}
/**
* The system calls this to perform work in the UI thread and delivers
* the result from doInBackground()
*/
protected void onPostExecute(Integer result) {
new RefreshTask.execute();
}
}
Related
i currently work on an app that needs a lot of battery in order to support background gps tracking. my experience shows that people just forget about the app runnning in the background when they dont really need the tracking anymore. therefore i setup some code that should close the application after 4 hours.
public class SelfDestructor {
private static SelfDestructor instance;
private final long IDLE_TIME_UNTIL_AUTO_DESTRUCT = 4 * 60 * 60 * 1000; // 4 hours
private Handler handler;
private Runnable closeApp = new Runnable() {
#Override
public void run() {
System.exit(0);
}
};
public static SelfDestructor getInstance() {
if (SelfDestructor.instance == null) {
SelfDestructor.instance = new SelfDestructor();
}
return SelfDestructor.instance;
}
public void keepAlive() {
if (handler == null) {
handler = new Handler();
}
handler.removeCallbacks(closeApp);
handler.postDelayed(closeApp, IDLE_TIME_UNTIL_AUTO_DESTRUCT);
}
}
now in my main activity i call keepAlive().
#Override
protected void onResume() {
super.onResume();
SelfDestructor.getInstance().keepAlive();
}
#Override
protected void onStart() {
super.onStart();
SelfDestructor.getInstance().keepAlive();
}
now if i set the time to an hours or so and debug the that functionality everything works fine. if i set the time to 4 hours the System.exit(0); is never called. i am assuming the app thread with the close callback is just put on hold by the android system after a while and therefore will not be executed anymore while gps will continue to run. any ideas how to properly get this to work?
handler and postDelayed are not suited for long timers. At most they should be used within a few seconds and personally I think I never used one for anything more than 2 seconds.
Said all that, Android have an appropriate class for "stuff that should happen after a long time", it's called AlarmManager: http://developer.android.com/reference/android/app/AlarmManager.html
you can get the references to the system service AlarmManager by calling Context.getSystemService(Context.ALARM_SERVICE)
and then set it by calling am.set(AlarmManager.ELAPSED_REALTIME, IDLE_TIME_UNTIL_AUTO_DESTRUCT, operation)
the operation is a PendingIntent to a BroadcastReceiver that you register in the AndroidManifest.xml via the <receiver> tag. Then you do the close application code inside this broadcast receiver.
Also I should add that it's NEVER good to call System.exit(0);, as this just destroy the VM without much of a warning. It's a better, more organised/structured shut down if you pass a command to the Service that is holding the GPS (I believe you're running a service), then this service will cancel the GPS request, and call stopSelf();
I am trying the 'LocationUpdates' sample from http://developer.android.com/training/location/receive-location-updates.html . This application gets and prints location notifications.
I am trying to change the interval of the location updates according to my latest location.
So - I had added mLocationRequest.setInterval() into onLocationChanged
The result is very wrong. My application is bombarded with many location updates (few a second!!!!)
My only change to the sample is this:
private int x=0;
#Override
public void onLocationChanged(Location location) {
// Report to the UI that the location was updated
mConnectionStatus.setText(R.string.location_updated);
// In the UI, set the latitude and longitude to the value received
mLatLng.setText(String.valueOf(x++));
mLocationRequest.setInterval(1000); // Change 1
mLocationClient.requestLocationUpdates(mLocationRequest, this); // Change 2
}
How can I change the interval inside onLocationChanged ?
I think that the problem is that requestLocationUpdates resets the last request, and then immediately sends another notification. so a loop is created. (faster than the fastest interval). so I need a reliable way to change the interval of a 'live' LocationRequest
You are not supposed to call mLocationClient.requestLocationUpdates(mLocationRequest, this); inside onLocationChanged(Location location)
since you are registering the listener again, and you will get the first call immediately.
so what i would do would be:
dont call mLocationClient.requestLocationUpdates(mLocationRequest, this); and see if anyways mLocationRequest.setInterval(1000); is taking effect
if this doesnt work, try to unregister the listener, and then use a trick to wait before registering it again with the new settings, something like:
Handler h = new Handler();
#Override
public void onLocationChanged(Location location) {
//... all your code
mLocationRequest.setInterval(1000);
mLocationClient.removeLocationUpdates(LocationListener listener)
h.postDelayed (new Runnable(){
public void run(){
mLocationClient.requestLocationUpdates(mLocationRequest, YOUROUTTERCLASS.this);
}
}, 1000);
}
So during one second there is not registered listener, so you wont get any updated, and after that, the listener is registerered with that interval.
Try using mLocationRequest.setFastestInterval(long millis)
As mentioned in developer.android.com :
This allows your application to passively acquire locations at a rate faster than it actively acquires locations, saving power. Unlike setInterval(long), this parameter is exact. Your application will never receive updates faster than this value.
Try to use :
mLocationRequest.requestLocationUpdates("gps", 1000, 0, this);
However I don't agree to do a requestLocationUpdates in onLocationChanged event; In my opinion should be setted outside onLocationChanged Event...
Details:
I have a service that needs to do the following:
- listen constantly for GPS locations, with 50 meters as parameter (works fine) and send it to a server
- each 60 seconds, if no GPS location was read, start listening for a Network location and send it to the server
This may sound weird, but this is the project requirement. So the user is constantly being tracked using GPS. When he stops, or GPS is not locked, or is inside a building, every 60 seconds start a quick Network location read, and send this location to the server. As the service uses the same thread as the main app, each server update is done in its own thread. And another important thing: each location read should be sent one after another, so for instance if the user is driving, and multiple reads are done, each should be send to the server, after the previous one has been sent. That's why I decided to use ScheduledExecutorService as I can submit threads and they will be execute one after another.
Here is the code:
private ScheduledExecutorService scheduleTaskExecutor;
Handler locationHandler = new Handler();
private Location lastNetworkLocation;
#Override
public void onStartCommand() {
scheduleTaskExecutor = Executors.newSingleThreadScheduledExecutor();
//prepare to execute Network reading every 60 seconds
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
initNetworkReadings();
//usually a network location read is done almost instantly
//however after 5 seconds I check if a location has been read by the Network listener inside the locationRunnable
locationHandler.postDelayed(locationRunnable, 5000);
}
}
}, 60, 60, TimeUnit.SECONDS);
locationRunnable = new Runnable() {
#Override
public void run() {
if (lastNetworkLocation !=null){
//send location to the server if valid
}
lastNetworkLocation = null;
}
}
}
private void initNetworkReadings() {
locationManager.removeUpdates(locationListenerNetwork);
try {
isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
if (isGpsEnabled) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
}
}
LocationListener locationListenerNetwork = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
lastNetworkLocation = location;
// stop listening for Network locations
locationManager.removeUpdates(locationListenerNetwork);
}
...
}
Each time I read a GPS location I add it to the threads queue as:
scheduleTaskExecutor.submit(new Runnable() {
#Override
public void run() {
updateLocationOnServer(readLocation);
}
});
The problem I have is that the Network location listener never gets onLocationChanged() called,when I use it like in the code above, in the Runnable. BUT if I add on service start, the initNetworkReadings(), I get onLocationChanged() fired right away. So I believe it has something to do with being used in scheduleAtFixedRate.
What do you think it could be the problem ? Anything bad in the way I thought of the workflow ?
What if you try to set up your repitition with a Handler and a Runnable instead of scheduleAtFixedRate()?
Handler h = new Handler();
Runnable run = new Runnable(){
//Do your repeititive work here!
h.postDelayed(run, 60 * 1000);
};
h.post(run);
Same problem,
I have a background service that detect locations.
If network is avaible, it uses networks, otherwise it uses gps.
It works well with many smartphones
( nexus s 4.1, galaxy nexus 4.2, galaxy note) , but with Galaxy s3 jb (4.1) network location never rises any location.
Same code with gps locations, works just fine.
Scenario
Step 1: Init the location manager to read GPS locations every 50 meters:
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 50, locationListenerGps);
Step 2: each time a location is read:
#Override
public void onLocationChanged(Location location) {
new Thread(new Runnable() {
#Override
public void run() {
sendLocation(location);
}
}).start();
}
Step 3: on sendLocation there are a few things I do:
query the local sqlite database for failed to send records
if any, send them together together with the current location to a web service
if none, send only the current location
if sending failed (mostly because of data connectivity), insert location in database for future readings
if sending succeed, delete all rows from the database
The problem
All this is done in background in a service. For each sendLocation call I make a new thread. While connectivity is ok, everything works fine. But, when sending fails and the user is driving, the location read happens very often and there are big chances that there are 2-3 threads all trying to send the same unsent locations. If Thread1 receives the list and tries to send it, Thread2 and Thread3 should not be able to read it and try to send it as Thread1 may send it successfully. How can I prevent this from happening ? How can I make sure Thread2 does not read the list ?
From what I am thinking now, I could add a new field in the table "processing" and for all the rows retrieved for sending, update the field to true. In this case Thread2 will only get the processing=false rows. Is this a solution ? Any other recommendations ? I still believe that there is a slight change for Thread2 to get the data, while Thread1 is updating processing... Thanks.
Later edit: Extra thoughts and ideas I have tried this approach
private ExecutorService threadPool;
#Override
public void onCreate() {
scheduleTaskExecutor = Executors.newSingleThreadScheduledExecutor();
threadPool = Executors.newSingleThreadExecutor();
//also need to send location every 60 seconds if no other location was read
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
sendLocation(lastLocation);
}
}, 60, 60, TimeUnit.SECONDS);
}
#Override
public void onLocationChanged(Location location) {
threadPool.execute(new Runnable() {
#Override
public void run() {
sendLocation(location);
}
});
}
#Override
public void onDestroy() {
threadPool.shutdownNow();
}
From what I read, this threadPool should force threads to execute one after another, right ? (even I do have a feeling I misunderstood its purpose) If so, what happens if I get no connectivity for an hour ? For each location read, a new thread is added... but how long does this thread last ? I am concern what happens if the user is driving really fast, I could get locations read every 1-2 seconds, would this mechanism hold my web access in a queue, one thread after another ?
In another order of thoughts, what if onCreate method of the service I make a new thread. Something like:
#Override
public void onCreate() {
new Thread(new Runnable() {
#Override
public void run() {
startLocationListener();
}
}).start();
}
and in startLocationListener() I start GPS location read. Will onLocationChanged be executed on this thread and won't interfere with my UI thread ?
Would it be wiser to use a Service that runs in its own thread ? So I won't have to worry about threading ?
Using the current approach, the app does the job but there is something wrong happening, randomly and can't figure out the reason: one of my activities binds to the service to receive updates, I carefully unbind it when the app gets onPause... but sometimes the service keeps running, as I can see its notification icon displayed. I will investigate this more, but I need to settle a strong/reliable way of handling location reading and sending.
Later later edit
How about this approach:
private ExecutorService scheduleTaskExecutor;
#Override
public void onCreate() {
scheduleTaskExecutor = Executors.newSingleThreadScheduledExecutor();
//also need to send location every 60 seconds if no other location was read
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
sendLocation(lastLocation);
}
}, 60, 60, TimeUnit.SECONDS);
}
#Override
public void onLocationChanged(Location location) {
scheduleTaskExecutor.submit(new Runnable() {
#Override
public void run() {
sendLocation(location);
}
});
}
#Override
public void onDestroy() {
scheduleTaskExecutor.shutdownNow();
}
So let me get this straight: you want to send locations one after the other from a background thread. A simple scheme to achieve this would be (similar to your edited code, but I don't see the reason for ScheduledExecutor):
private ExecutorService exec;
#Override
public void onCreate() {
exec = Executors.newSingleThreadExecutor();
}
#Override
public void onLocationChanged(Location location) {
exec.submit(new Runnable() {
#Override
public void run() {
sendLocation(location);
}
});
}
#Override
public void onDestroy() {
exec.shutdownNow();
}
What this does under the hood is basically to create a background thread and a queue of tasks. Every time a location is read a new task is put into the queue. The thread continuously polls the queue and executes the tasks in order.
For each sendLocation call I make a new thread.
Why?
But, when sending fails and the user is driving, the location read happens very often and there are big chances that there are 2-3 threads all trying to send the same unsent locations.
This is why I asked "Why?" above.
If Thread1 receives the list and tries to send it, Thread2 and Thread3 should not be able to read it and try to send it as Thread1 may send it successfully. How can I prevent this from happening ? How can I make sure Thread2 does not read the list ?
IMHO, by not having Thread2 and Thread3 in the first place. Use a single thread at a time, that sends all unsent data. That is probably a long-lived thread, working off of a work queue (coupled with some sort of timer mechanism to handle the case where you failed to update before and wish to make sure you try again after X period of time, if no other events forced you to try sooner than that). I don't see why you would need more than that to achieve your aims.
Currently, I am trying to develop an Android application. Application reads Location data from GPS first(thru onLocationUpdates(GPS_PROVIDER, 0, 0 , Listener) and if GPS is not able to retrieve data in specific time ( in my case 5 sec ),it again asks to Network provider ( thru onLocationUpdates(NETWORK_PROVIDER,0,0, Listener) ).
In my program I have handled this task as follows.
mWorker = new WorkerThread();
mWorker.start(); // Thread creation and Calling run() Method
class WorkerThread extends Thread{
public void run() {
try {
if(isGPSEnabled){
//Call GPS_PROVIDER for Location updates,
//Will get location value in Location Change Listener
requestLocationUpdates(GPS_PROVIDER,0,0,Listener);
Log.d(TAG,"GPS Listener is registered and thread is going for Sleep");
Thread.sleep(THREAD_SLEEP_TIME);
}else if (isNetworkEnbles){
// if GPS_PROVIDER is not available, will call to NETWORK_PROVIDER
// Will get location value to OnLocationChangeListener
requestLocationUpdates(NETWORK_PROVIDER,0,0,listener);
Log.d(TAG,"NETWORK Listener is registered and thread is going for Sleep");
Thread.sleep(THREAD_SLEEP_TIME);
}
} catch ( InterruptedException e ){
// Thread is interrupted on OnLocationChange Listener
// Thread has been Interrupted, It means.. data is available thru Listener.
}
// Thread had sleep for THREAD_SLEEP_TIME , but Still data is not available
If (GPS or Network data isnt available thru GPS listener or Network Listener ) {
// Do try for one more time.
}
}
Now My OnLocationChanged Listener code as Follows:
public void onLocationChanged(Location location) {
// Listener got location
// First Intrupt the sleeping Thread
mWorker.interrupt();
if ( loc != null )
{
//get Location
}
}
Now my problem is , when I run this code , Worker Thread never calls OnLocationChanged Listener.
I have inserted looper.Loop() in worker thread , OnLocationChanged is called. But it runs continuously and looper not get stop. How should I stop looper.loop()?
Is there any other way thru which I can manage my above task except workerThread.
If you need more clarification, just let me know.
Don't use Thread.sleep() on the main thread. You are blocking it from running. There is no need to have a separate thread for what you are doing, you just need to allow your main thread to continue running so that you can handle the events through your callbacks as they are received.