I'm developing an app which gets GPS Location every ten minutes and sends it to a server, in order to do that I use a Timer, requestLocationUpdates and Android Asynchronous Http Client library. The positions need to be saved for up to 12 hours every ten minutes.
To keep an app alive, I use wakelock in onCreate inside Service
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "whatever");
wl.acquire();
and onDestroy()
wl.release();
The service is started and stopped by buttons in MainActivity. My main problem is that I only get the position twice (on start and after 10mins, no position update after 20mins) if user presses Home Button and moves the app to back. It seems to be okay as long as I lock the screen on the app, but the process seems to be killed after few minutes when I lock on Home Screen.
Here is the whole code of my GPS Service:
public class UpdatePositionService extends Service {
private PowerManager.WakeLock wl;
private Handler mHandler = new Handler(Looper.getMainLooper());
private AsyncHttpClient client = new AsyncHttpClient();
private SharedPreferences preferences;
public static final String USER_PREFERENCES = "userPreferences";
private Timer updatingTimer;
private TimerTask task = new TimerTask() {
@Override
public void run() {
mHandler.post(new Runnable() {
public void run() {
preferences = getSharedPreferences(USER_PREFERENCES, MODE_PRIVATE);
final String uid = preferences.getString("userid", "");
final String pause = readFromFile("pause.txt");
final String userState = readFromFile("user.txt");
final String workid = readFromFile("work.txt");
final String consid = readFromFile("cons.txt");
if (!pause.equals("1") && !userState.equals("0")) {
Log.e("mmd:test:123", "dochodzi " + userState);
final LocationManager[] lm = {(LocationManager)
getSystemService(Context.LOCATION_SERVICE)};
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.POWER_HIGH);
_if();
lm[0].requestLocationUpdates(LocationManager
.GPS_PROVIDER, 6000, 10, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
String updatedPause = readFromFile("pause.txt");
_if();
lm[0].removeUpdates(this);
lm[0] = null;
if (!updatedPause.equals("1")) {
RequestParams params = new RequestParams();
params.put("uid", uid);
params.put("lat", location.getLatitude());
params.put("long", location.getLongitude());
params.put("workid", workid);
params.put("type", userState);
params.put("cons", consid);
String url = "http://example.com/api/event/add";
client.post(url, params,
new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode,
Header[] headers, byte[] responseBody) {
String response = new String(
responseBody);
if (!response.equals("ERROR")) {
} else {
}
}
@Override
public void onFailure(
int statusCode, Header[] headers,
byte[] responseBody,
Throwable error) {}
});
}
}
@Override
public void onStatusChanged(String provider, int
status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {}
});
}
}
});
}
};
private void _if() {
if (ActivityCompat.checkSelfPermission(UpdatePositionService.this,
Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(UpdatePositionService.this,
Manifest.permission.ACCESS_COARSE_LOCATION) !=
PackageManager.PERMISSION_GRANTED) {}
}
@Override
public void onCreate() {
super.onCreate();
updatingTimer = new Timer();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager
.ACQUIRE_CAUSES_WAKEUP, "whatever");
wl.acquire();
}
@Override
public void onDestroy() {
updatingTimer.cancel();
mHandler.removeCallbacksAndMessages(null);
wl.release();
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int time = 1000 * 60 * 10; // 10 mins
updatingTimer.scheduleAtFixedRate(task, 3000, time);
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
private String readFromFile(String filename) {
String ret = "";
try {
InputStream inputStream = openFileInput(filename);
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader
(inputStream);
BufferedReader bufferedReader = new BufferedReader
(inputStreamReader);
String receiveString = "";
StringBuilder stringBuilder = new StringBuilder();
while ((receiveString = bufferedReader.readLine()) != null) {
stringBuilder.append(receiveString);
}
inputStream.close();
ret = stringBuilder.toString();
}
} catch (FileNotFoundException e) {
Log.e("login activity", "File not found: " + e.toString());
} catch (IOException e) {
Log.e("login activity", "Can not read file: " + e.toString());
}
return ret;
}
}
EDIT 2024.11.08: this answer seems to not be valid for Android 12 see comments
To expand @CommonsWare comment. Setting up a service for this is a bad idea - and also does not work as you have seen. Android kills it and the party is over. To do any periodic task you have to setup an alarm using the alarm manager. To do this register an alarm - better in some receiver that is setup to receive on boot - cause when rebooting all your alarms go bye bye:
private void setupAlarm(Context context, boolean setup) {
am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
pi = PendingIntent.getBroadcast(context, NOT_USED, yourIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if (setup) { // setup the alarms
try {
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + SOME_DELAY, THE_INTERVAL_BETWEEN_ALARMS, pi);
} catch (InstantiationException e) {
// should not happen
throw new RuntimeException(UNABLE_TO_SET_ALARMS, e);
} catch (IllegalAccessException e) {
// should not happen
throw new RuntimeException(UNABLE_TO_SET_ALARMS, e);
}
} else { // teardown the alarms
// send message to the monitors that the party is over
Intent i = new Intent(YOUR_ABORTING_ACTION, Uri.EMPTY, context, YOUR_SERVICE_CLASS);
WakefulIntentService.sendWakefulWork(context, i);
// cancel the alarms
am.cancel(pi);
}
d("alarms " + (setup ? "enabled" : "disabled"));
}
Then in the onReceive of the receivers registered to receive the broadcast from the AlarmManager (they hold wakelocks for you that never fail) delegate to a WakefulIntentService
@Override
final public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (ac_setup_alarm.equals(action) || ac_cancel_alarm.equals(action)) {
monitoringIntent = new Intent(context, this.getClass());
monitoringIntent.setAction(ac_monitor.toString());
final boolean enable = ac_setup_alarm.equals(action);
setupAlarm(context, enable);
} else if (ac_monitor.equals(action)) {
// monitoring - got broadcast from ALARM
WakefulIntentService.sendWakefulWork(context, YOUR_SERVICE_CLASS);
} else if (ac_reschedule_alarm.equals(action)) {
monitoringIntent = new Intent(context, this.getClass());
monitoringIntent.setAction(ac_monitor.toString());
rescheduleAlarm(context);
} else {
w("Received bogus intent : " + intent);
}
}
Alternatively to use a wakeful intent service you can use a WakefulBroadcastReceiver - but you have to write the service. The (my) code is here - works!