49

I am running a Web service that allows users to record their trips (kind of like Google's MyTracks) as part of a larger app. The thing is that it is easy to pass data, including coords and other items, to the server when a user starts a trip or ends it. Being a newbie, I am not sure how to set up a background service that sends the location updates once every (pre-determined) period (min 3 minutes, max 1 hr) until the user flags the end of the trip, or until a preset amount of time elapses.

Once the trip is started from the phone, the server responds with a polling period for the phone to use as the interval between updates. This part works, in that I can display the response on the phone, and my server registers the user's action. Similarly, the trip is closed server-side upon the close trip request.

However, when I tried starting a periodic tracking method from inside the StartTrack Activity, using requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) where minTime is the poll period from the server, it just did not work, and I'm not getting any errors. So it means I'm clueless at this point, never having used Android before.

I have seen many posts here on using background services with handlers, pending intents, and other things to do similar stuff, but I really don't understand how to do it. I would like the user to do other stuff on the phone while the updates are going on, so if you guys could point me to a tutorial that shows how to actually write background services (maybe these run as separate classes?) or other ways of doing this, that would be great.

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
Mark
  • 503
  • 1
  • 5
  • 5

3 Answers3

75

I recently wrote one of these and decided it is not a good idea to leave a background service running. It will probably be shut down by the operating system anyway, or it could be. What I did was use a filter for the boot intent and then set an alarm using the alarm manager so that my app was restarted at regular intervals, and then it sent the data. You can find good info on services and the alarm manager in the Android documentation.

First I created a broadcast receiver that simply starts my service when an internet connection is opened (I'm only interested if there is a connection - you might want to filter for the boot event as well). The launch receiver must be short-lived, so just start your service:

public class LaunchReceiver extends BroadcastReceiver {

    public static final String ACTION_PULSE_SERVER_ALARM = 
            "com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM";

    @Override
    public void onReceive(Context context, Intent intent) {
        AppGlobal.logDebug("OnReceive for " + intent.getAction());
        AppGlobal.logDebug(intent.getExtras().toString());
        Intent serviceIntent = new Intent(AppGlobal.getContext(),
                MonitorService.class);
        AppGlobal.getContext().startService(serviceIntent);
    }
}

In the manifest I have:

<receiver
    android:name="LaunchReceiver"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM" />
    </intent-filter>
</receiver>

Notice how I have a filter for my own alarm, which is what allows me to shut the service and have it restarted after it's done its work.

The top of my monitor service looks like:

public class MonitorService extends Service {

    private LoggerLoadTask mTask;
    private String mPulseUrl;
    private HomeBoySettings settings;
    private DataFile dataFile;
    private AlarmManager alarms;
    private PendingIntent alarmIntent;
    private ConnectivityManager cnnxManager;

    @Override
    public void onCreate() {
        super.onCreate();
        cnnxManager = (ConnectivityManager) 
                getSystemService(Context.CONNECTIVITY_SERVICE);
        alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intentOnAlarm = new Intent(
                LaunchReceiver.ACTION_PULSE_SERVER_ALARM);
        alarmIntent = PendingIntent.getBroadcast(this, 0, intentOnAlarm, 0);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        // reload our data
        if (mPulseUrl == null) {
            mPulseUrl = getString(R.string.urlPulse);
        }
        AppGlobal.logDebug("Monitor service OnStart.");
        executeLogger();
    }

executeLogger starts an asyncTask, which is probably me being excessively cautious (this was only my third Android app). The asyncTask grabs the GPS data, sends it to the internet and finally sets the next alarm:

private void executeLogger() {
    if (mTask != null
        && mTask.getStatus() != LoggerLoadTask.Status.FINISHED) {
        return;
    }
    mTask = (LoggerLoadTask) new LoggerLoadTask().execute();
}

private class LoggerLoadTask extends AsyncTask<Void, Void, Void> {

    // TODO: create two base service urls, one for debugging and one for live.
    @Override
    protected Void doInBackground(Void... arg0) {
        try {
            // if we have no data connection, no point in proceeding.
            NetworkInfo ni = cnnxManager.getActiveNetworkInfo();
            if (ni == null || !ni.isAvailable() || !ni.isConnected()) {
                AppGlobal
                        .logWarning("No usable network. Skipping pulse action.");
                return null;
            }
            // / grab and log data
        } catch (Exception e) {
            AppGlobal.logError(
                    "Unknown error in background pulse task. Error: '%s'.",
                    e, e.getMessage());
        } finally {
            // always set the next wakeup alarm.
            int interval;
            if (settings == null
                || settings.getPulseIntervalSeconds() == -1) {
                interval = Integer
                        .parseInt(getString(R.string.pulseIntervalSeconds));
            } else {
                interval = settings.getPulseIntervalSeconds();
            }
            long timeToAlarm = SystemClock.elapsedRealtime() + interval
                * 1000;
            alarms.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToAlarm,
                    alarmIntent);
        }
        return null;
    }
}

I notice that I am not calling stopSelf() after setting the alarm, so my service will sit around doing nothing unless shut down by the op sys. Since I am the only user of this app, that doesn't matter but for a public app, the idea is you set the alarm for the next interval then stopSelf to close down.

Update See the comment from @juozas about using 'alarms.setRepeating()'.

Rob Kent
  • 5,183
  • 4
  • 33
  • 54
  • Rob, thanks. I am now searching the documentation to see how to use the intent filters and how to set an alarm to periodically call the service, instead of having it run all the time. – Mark May 06 '10 at 16:09
  • 1
    Your solution is fantastic. I finally got mine to work, but I definitely have taken several notes from your approach. For example, I didn't check for an existing network connection at all, so that when there was no connectivity I would get an error. Thumbs up! – Mark May 19 '10 at 18:40
  • The ni.isAvailable() call, if I remember rightly, stops you opening a connection if they have Data Roaming turned off. – Rob Kent May 19 '10 at 22:06
  • @Rob Kent don't you use `LocationListener` or `LocationManager`? – noloman Aug 22 '11 at 13:05
  • @noloman - My app is not really doing anything with Location. It just needs to communicate with a web server at intervals. The questioner's app is sending location, I guess, in which case he could wake up when the location changes. The crux of his question was how to do something periodically though, which may need to happen regardless of location changes. – Rob Kent Aug 30 '11 at 10:25
  • 2
    Very good example, thanks! I have a question though. Why do you do continuous alarm rescheduling, doesn't alarms.setRepeating() do the same? – Juozas Kontvainis Oct 09 '11 at 15:25
  • 1
    @Juozas, yes you might be right. That would be an improvement, if so. Thanks – Rob Kent Oct 10 '11 at 12:31
  • @Rob Kent Thanks for the great example with explanation – Krishna Jan 18 '13 at 05:27
  • 1
    Re: `setRepeating()` the recommended way is`setinexactRepeating()` (and would ineed simplify your code) - my question : does this work _even when the phone falls asleep_ ? – Mr_and_Mrs_D May 09 '13 at 18:28
  • Sorry to be dumb, but what is `AppGlobal`? – Mawg says reinstate Monica Oct 24 '13 at 03:36
  • 2
    @Mawg That is just my own application class in which I keep various helper methods such as logging etc. You can ignore that. In fact, I should have stripped out all that extraneous code. – Rob Kent Oct 24 '13 at 08:22
  • 1
    What do you return in AppGlobal.getContext() ? I'm confused as the function call is static, but no static reference to any kind of context seems to be possible.... – Kevin Nov 22 '13 at 08:29
  • AppGlobal is a helper class that inherits from Application. It has various 'global' utility methods that need a context: public final class AppGlobal extends android.app.Application { private static AppGlobal instance; public AppGlobal() { instance = this; } public static Context getContext() { return instance; } // other helper methods here... } – Rob Kent Nov 27 '13 at 10:07
  • Nice solution! Just one thing: I think it`s better to use a Handler instead of a Alarm to run the next execution after a while. – Renato Lochetti May 27 '14 at 14:35
  • Sorry to necro bump this, but where's your onBind? – Razgriz Jul 19 '14 at 04:09
  • @Razgriz Sorry, I haven't looked at this code for a few years and am not sure if it even compiles. – Rob Kent Jul 21 '14 at 17:06
  • 1
    Just a note for those having troubles, in order for this to work, you need to call stopSelf() after setting the alarm. Otherwise new service will not be started. – paradite Mar 29 '15 at 14:44
  • appglobal variable type? is this static or not static? – Faisal Ashraf Nov 28 '15 at 07:32
  • Is there a more modern example? I'm concerned that as the Android OS has evolved, things here might not work, or not work as well as a more modern approach (i.e. one that knows about and utilizes features added since this code was written). – Dale May 23 '18 at 20:53
  • I would hope so! I wrote that five years ago. – Rob Kent May 24 '18 at 08:42
  • What's the main issue of @juozas comment?. i'm looking for a never ending background service, but the OS is killing my app, after being running for a while in doze mode. i have tried START_STICKY, FOREGROUND service, recreate the service, onTaskRemoved, onDestroyed, .. services, and broadcast receivers are killed by OS, (i have the main service, and listener service for connectivity changes, to start the app, one connected, and reboot receiver and connectivity receiver). connectivity receiver will be stopped also, and not trying to restart the service, once connected. Plz help me with that. – Houssem Chlegou Oct 14 '18 at 00:58
17

You need to create a separate class that is a subclass of the Service class.

Service Documentation

Your primary application should can call startService and stopService to start up the background process. Theres also some other useful calls in the context class to manage the service:

Context Documentation

Mark
  • 2,932
  • 18
  • 15
  • Thanks a million. I have been able to build the service, start and stop it. I now have to make sure that the system doesn't kill it, and that it is called as needed. – Mark May 06 '10 at 15:45
  • 2
    Keep in mind that the OS will periodically stop (and restart) your service as it sees fit: http://developer.android.com/reference/android/app/Service.html#ProcessLifecycle – jscharf May 06 '10 at 17:47
  • jscharf, how do I ensure that the service is guaranteed to be called periodically, and only run then, until the user performs a particular action to stop the calls? I'm looking to use an alarm and going through the docs on how to use them... – Mark May 06 '10 at 18:25
  • 2
    You can either have an ALARM_SERVICE send your service an Intent at some periodic time or you can spawn a new thread for your Service and have it check the time and sleep() for the correct amount of time you want in between wake ups. Your primary application is responsible for calling stopService to shut it down so it needs to catch the user events. Also remember that the service runs within the process of your main application. Your main application should make sure it frees as many resources as possible in its onStop() method to ensure it doesn't load the system. – Mark May 06 '10 at 19:13
  • Mark, I have set it up using the alarm service. The alarm fires fine using the poll period from the server, but now I get a serialization runtime error when I try to create the soap package with the location update data within in the service. This is really frustrating... – Mark May 07 '10 at 03:46
  • OK guys, I have fixed the serialization issue. I changed the type returned from the location object from double to string. Now, the lat/longs are not showing up on the screen, and so are not going off to the server. Communication with the server is fine, but for some reason I'm not getting the locations now. The location manager is used in the onCreate() method of the main function, and then in the service after the user completes interaction with the main activity (note the main activity moves to another and back). Should I use onResume() then? HELP! Thanks guys. – Mark May 08 '10 at 22:17
  • @Mark using alarm service can i upload a file to server periodically? – AndroidOptimist Nov 22 '13 at 09:42
  • @Mark:Can u plz share some code snippet.It will be great help – Saurabh Ahuja Oct 01 '15 at 18:26
2

I agree with Rob Kent, and in additional I think could be beter to extends WakefulBroadcastReceiver in your BroadcastReceiver and use it's static method startWakefulService(android.content.Context context,android.content.Intent intent), because it garanted your service will not shut by os.

public class YourReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Intent service = new Intent(context, YourService.class);
        startWakefulService(context, service);
    }
}

Official documentation

brucemax
  • 754
  • 8
  • 15