17

I have created a service to fetch current location of the device in periodic intervals. I want the service to run in the background even if the app is cleared from recently opened apps. Currently the service runs in background only until app is present in the recently opened apps but stop immediately when app is swiped off (or killed in some other way). I have tried all sort of help available in stack overflow yet I am unable to solve this. Please help. Here is my code for the service.

package com.packr.services;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.location.Location;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.text.DateFormat;
import java.util.Date;

/**
 * Created by Arindam on 11-Dec-15.
 */
public class LocationService extends Service implements
        GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
    protected static final String TAG = "packrMATE";
    /**
     * The desired interval for location updates. Inexact. Updates may be more or less frequent.
     */
    public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;

    /**
     * The fastest rate for active location updates. Exact. Updates will never be more frequent
     * than this value.
     */
    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    // Keys for storing activity state in the Bundle.
    protected final static String REQUESTING_LOCATION_UPDATES_KEY = "requesting-location-updates-key";
    protected final static String LOCATION_KEY = "location-key";
    protected final static String LAST_UPDATED_TIME_STRING_KEY = "last-updated-time-string-key";

    /**
     * Provides the entry point to Google Play services.
     */
    protected GoogleApiClient mGoogleApiClient;

    /**
     * Stores parameters for requests to the FusedLocationProviderApi.
     */
    protected LocationRequest mLocationRequest;

    /**
     * Represents a geographical location.
     */
    protected Location mCurrentLocation;

    /**
     * Tracks the status of the location updates request. Value changes when the user presses the
     * Start Updates and Stop Updates buttons.
     */
    protected Boolean mRequestingLocationUpdates;

    /**
     * Time when the location was updated represented as a String.
     */
    protected String mLastUpdateTime;

    @Override
    public void onCreate() {
        Log.d(TAG,"Service started");
        super.onCreate();
        mRequestingLocationUpdates = false;
        mLastUpdateTime = "";

        // Kick off the process of building a GoogleApiClient and requesting the LocationServices
        // API.
        buildGoogleApiClient();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"Service fucking started");
        mGoogleApiClient.connect();
        if (mGoogleApiClient.isConnected()) {
            startLocationUpdates();
        }
        return Service.START_STICKY;

    }

    @Override
    public void onDestroy() {
        mGoogleApiClient.disconnect();
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.i(TAG, "Connected to GoogleApiClient");

        // If the initial location was never previously requested, we use
        // FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store
        // its value in the Bundle and check for it in onCreate(). We
        // do not request it again unless the user specifically requests location updates by pressing
        // the Start Updates button.
        //
        // Because we cache the value of the initial location in the Bundle, it means that if the
        // user launches the activity,
        // moves to a new location, and then changes the device orientation, the original location
        // is displayed as the activity is re-created.
        if (mCurrentLocation == null) {
            mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
            Toast.makeText(getApplicationContext(),"Hello Babe",Toast.LENGTH_SHORT).show();
        }

        // If the user presses the Start Updates button before GoogleApiClient connects, we set
        // mRequestingLocationUpdates to true (see startUpdatesButtonHandler()). Here, we check
        // the value of mRequestingLocationUpdates and if it is true, we start location updates.
            startLocationUpdates();
    }

    @Override
    public void onConnectionSuspended(int i) {
        // The connection to Google Play services was lost for some reason. We call connect() to
        // attempt to re-establish the connection.
        Log.i(TAG, "Connection suspended");
        mGoogleApiClient.connect();
    }

    @Override
    public void onLocationChanged(Location location) {
        mCurrentLocation = location;
        mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
        Toast.makeText(this, String.valueOf(location.getLatitude() + " "+ String.valueOf(location.getLongitude())),
                Toast.LENGTH_SHORT).show();
        Log.e(TAG,"fuck man location found");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        // Refer to the javadoc for ConnectionResult to see what error codes might be returned in
        // onConnectionFailed.
        Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
    }

    protected synchronized void buildGoogleApiClient() {
        Log.i(TAG, "Building GoogleApiClient");
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        createLocationRequest();

    }
    /**
     * Sets up the location request. Android has two location request settings:
     * {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control
     * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
     * the AndroidManifest.xml.
     * <p/>
     * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
     * interval (5 seconds), the Fused Location Provider API returns location updates that are
     * accurate to within a few feet.
     * <p/>
     * These settings are appropriate for mapping applications that show real-time location
     * updates.
     */
    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);

        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    /**
     * Requests location updates from the FusedLocationApi.
     */
    protected void startLocationUpdates() {
        // The final argument to {@code requestLocationUpdates()} is a LocationListener
        // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html).
        LocationServices.FusedLocationApi.requestLocationUpdates(
                mGoogleApiClient, mLocationRequest, this);
    }
    /**
     * Removes location updates from the FusedLocationApi.
     */
    protected void stopLocationUpdates() {
        // It is a good practice to remove location requests when the activity is in a paused or
        // stopped state. Doing so helps battery performance and is especially
        // recommended in applications that request frequent location updates.

        // The final argument to {@code requestLocationUpdates()} is a LocationListener
        // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html).
        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
    }
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Log.e("FLAGX : ", ServiceInfo.FLAG_STOP_WITH_TASK + "");
        Intent restartServiceIntent = new Intent(getApplicationContext(),
                this.getClass());
        restartServiceIntent.setPackage(getPackageName());

        PendingIntent restartServicePendingIntent = PendingIntent.getService(
                getApplicationContext(), 1, restartServiceIntent,
                PendingIntent.FLAG_ONE_SHOT);
        AlarmManager alarmService = (AlarmManager) getApplicationContext()
                .getSystemService(Context.ALARM_SERVICE);
        alarmService.set(AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + 1000,
                restartServicePendingIntent);

        super.onTaskRemoved(rootIntent);
    }
}
superjos
  • 12,189
  • 6
  • 89
  • 134
Arindam Dawn
  • 1,769
  • 2
  • 18
  • 35

4 Answers4

30

Override onTaskRemoved() in your service and use alarm manager to start the service again. Below is code from our app that does the same and works fine:

@Override
public void onTaskRemoved(Intent rootIntent) {
    super.onTaskRemoved(rootIntent);

    Log.d(TAG, "TASK REMOVED");

    PendingIntent service = PendingIntent.getService(
            getApplicationContext(),
            1001,
            new Intent(getApplicationContext(), MyService.class),
            PendingIntent.FLAG_ONE_SHOT);

    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, service);
}  

As you may want to send location periodically even in the case if the service gets killed on low memory (or for any reason), I suggest you to handle the uncaughtException to restart it after N seconds. This is how we have done in our app that works perfectly:

private Thread.UncaughtExceptionHandler defaultUEH;
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        Log.d(TAG, "Uncaught exception start!");
        ex.printStackTrace();

        //Same as done in onTaskRemoved()
        PendingIntent service = PendingIntent.getService(
                getApplicationContext(),
                1001,
                new Intent(getApplicationContext(), MyService.class),
                PendingIntent.FLAG_ONE_SHOT);

        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, service);
        System.exit(2);
        }
    };

Note: I THINK and I remember I verified it on Kitkat that START_STICKY does not work on Kitkat and higher API levels. Please verify this for yourself.

MORE:
As you do loc sending periodically, you may have to consider the deep sleep mode. To get things work in deep sleep, use WakefulBroadcastReceiver combined with AlarmManager. Take a look at my other post How to use http in deep sleep mode.

UPDATE:
This solution does not work (in fact need not to work) if user "FORCE STOP" the application from Settings. This is good in fact as restarting the service is not a good way if user himself wants to stop application. So, it is fine.

Community
  • 1
  • 1
cgr
  • 4,578
  • 2
  • 28
  • 52
  • Thanks Dawn, have a nice coding ;) – cgr Dec 11 '15 at 12:20
  • 1
    I added MORE to the answer that deals about deep sleep mode. See if you have to take care of that as well. – cgr Dec 11 '15 at 12:33
  • Suppose the user actually wants the app to stop running. Can they do this? It may be nice for you that your service / process runs all the time, but it's really the user's prerogative whether or not it should do so. – Karakuri Dec 18 '15 at 19:34
  • Good point. The above code won't restart service if user 'Force Stop' the app from Settings. If it is the nature of the app then I am sure that's fine and we can restart if he removes app from recent tasks. Say, my app is security related one. It tracks user location behind (less frequently) so his family knows where he is. I can not avoid restarting the service when he kills from recent tasks. Most of the times, user intention is not to stop app itself by removing app from recent tasks. But force stop app is what he actually meant. Makes sense ? – cgr Dec 18 '15 at 20:00
  • onTaskRemoved is never called @cgr – PriyankaChauhan Jul 26 '16 at 08:42
  • @priyankaChauhan it should work. See if you've done any mistakes in overriding it. https://developer.android.com/reference/android/app/Service.html#onTaskRemoved(android.content.Intent) – cgr Jul 26 '16 at 08:51
  • @cgr thanks its was my mistake but now service is not restarting from on taskremved – PriyankaChauhan Jul 26 '16 at 09:59
  • @cgr START_STICKY does not working on so what to do for that ? – PriyankaChauhan Jul 26 '16 at 10:36
  • @priyankaChauhan that is expected Priyanka. It does not work from KK.That is why we have to override onTaskRemoved and handle the stuff as in answer. Also try to understand the Doze mode in M and N so that you can write flexible code. Do not miss this video https://www.youtube.com/watch?v=VC2Hlb22mZM – cgr Jul 26 '16 at 10:44
  • 2
    @cgr I am confused about the part where I should use the uncaughtExcwptionHandler. should it be inside onLowMemory() ?? – Farhan Ibn Wahid Nov 09 '19 at 07:02
  • @cgr Maybe you know how can pass extras to the intent that triggered with alarmManager? I need to run WS on app close and from what i understand(and from what i tried) i could not preform http request in onTaskRemoved, and the solution here: https://stackoverflow.com/a/43760343/9092944 suggested to pass extras to the intent. In android 8 this solution is not working. Maybe you could help? – Moshe Yamini Nov 09 '20 at 19:55
  • Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags – Fəqan Çələbizadə Dec 23 '22 at 15:16
0

replace return Service.START_NOT_STICKY; with return START_STICKY;

Hojjat Imani
  • 333
  • 1
  • 15
0

If you ONLY want to restart service after it kill from Recent Task, simple use

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_STICKY;
}

If you use START_STICKY, when you kill app from recent task, your service will be killed (onTaskRemoved fired, onDestroy NOT fired) THEN it will auto start again (onCreate fired, onStartComand fired)

Linh
  • 57,942
  • 23
  • 262
  • 279
-1

I use android 9 and the solution works partially for me. I had a case with foreground service (working 24/7), which I wanted to restart after the application was crashed. When the event uncaughtExceptionHandler was caught, the application got frozen besides public void onTaskRemoved(Intent rootIntent) { event doesn't work anymore in latest Android versions (I suppose O+). My application has only one activity with fragments if you need solution for more activities just use this link. To solve that problem I've added a function which checks if the activity is working (to kill it) and some instructions to kill the process:

class MyApplication : Application() {

    private var currentActivity: Activity? = null

    override fun onCreate() {
        super.onCreate()
        StorageManager.init()

        this.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityPaused(activity: Activity) {

            }

            override fun onActivityStarted(activity: Activity) {
                currentActivity = activity
            }

            override fun onActivityDestroyed(activity: Activity) {
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            }

            override fun onActivityStopped(activity: Activity) {
                currentActivity = null
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            }

            override fun onActivityResumed(activity: Activity) {
            }
        })

        Thread.setDefaultUncaughtExceptionHandler { _, e ->
            // Close current activity
            currentActivity?.finish()

            val service : PendingIntent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                // Start service in Oreo and latest
                PendingIntent.getForegroundService(
                    applicationContext,
                    8888,
                    Intent(applicationContext, SensorService::class.java),
                    PendingIntent.FLAG_ONE_SHOT)
            } else {
                // Start service in Nougat and older
                PendingIntent.getService(
                    applicationContext,
                    8888,
                    Intent(applicationContext, MyService::class.java),
                    PendingIntent.FLAG_ONE_SHOT)
            }

            // The great solution introduced by @cgr
            val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
            alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, service)

            // Kill the current application process to avoid freezing activity
            android.os.Process.killProcess(android.os.Process.myPid())
            exitProcess(10)
        }
    }
}

Add to manifest:

    <application
        android:name="com.example.MyApplication"
        ...
LukaszTaraszka
  • 801
  • 1
  • 10
  • 26