androidservicegpsandroid-wake-lockandroid-async-http

Android Wakelock for GPS Service


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;
    }
}

Solution

  • 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!