Android: How to periodically send location to a server

Solution 1:

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()'.

Solution 2:

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

Solution 3:

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