Keep location service alive when the app is closed
Asked Answered
S

3

10

I have a service which sends a notification when the user changes his/her location. This service is working fine, but the problem arises when the user closes the app as the service closes too.

How I can make the service still alive even though the application was closed?

My Service is:

public class LocationService extends Service implements LocationListener {
    public final static int MINUTE = 1000 * 60;


    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    boolean canGetLocation = false;

    Location location; // location
    double latitude = 0; // latitude
    double longitude = 0; // longitude
    String provider;

    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10;

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1 * MINUTE;

    // Declaring a Location Manager
    protected LocationManager locationManager;



    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        public LocationService getService() {
            // Return this instance of LocalService so clients can call public
            // methods
            return LocationService.this;
        }
    }

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

    public Location getLocation() {
        try {
            locationManager = (LocationManager) getBaseContext().getSystemService(LOCATION_SERVICE);

            // getting GPS status
            isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

            // getting network status
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (!isGPSEnabled && !isNetworkEnabled) {
                // no network provider is enabled. DEFAULT COORDINATES


            } else {
                this.canGetLocation = true;
                if (isNetworkEnabled) {
                    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES,
                            this);
                    Log.d("Network", "Network Enabled");
                    if (locationManager != null) {
                        location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
                        }
                    }
                }
                // if GPS Enabled get lat/long using GPS Services
                if (isGPSEnabled) {
                    if (location == null) {
                        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES,
                                this);
                        Log.d("GPS", "GPS Enabled");
                        if (locationManager != null) {
                            location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                            if (location != null) {
                                latitude = location.getLatitude();
                                longitude = location.getLongitude();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i("LOCATION", "Latitude: " + latitude + "- Longitude: " + longitude);


        return location;
    }

    @Override
    public void onLocationChanged(Location arg0) {

        NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
        Intent intent = null;

        intent = new Intent(this, CompleteSurveyActivity.class);

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher).setAutoCancel(true)
                .setContentIntent(contentIntent).setContentTitle(this.getString(R.string.app_name)).setContentText("text");

        // mBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify((int) System.currentTimeMillis() % Integer.MAX_VALUE, mBuilder.build());

        double longitude = location.getLongitude();
        double latitude = location.getLatitude();

        Log.i("LOCATION", "Latitude: " + latitude + "- Longitude: " + longitude);

    }

    @Override
    public void onProviderDisabled(String arg0) {
    }

    @Override
    public void onProviderEnabled(String arg0) {
    }

    @Override
    public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
    }

}

I called from here:

public class MyActivity extends Activity {

    LocationService mService;
    boolean mBound = false;


    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get
            // LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;

        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);

        exampleButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                initService();
            }
        });

    }

    public void initService() {
        if (mBound)
            mService.getLocation();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocationService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

Manifest.xml

  <service android:name=".LocationService" android:enabled="true"></service>
Struggle answered 29/1, 2014 at 19:53 Comment(1)
can you provide us with the new code of unbounded service ? @sanyStatistician
D
11

Oppositely to what @sven-menschner said, I think an unbound Service is exactly what you need, as bound services are subject to bind/unbind mechanisms that would kill your service. That's what I would do:

In your Manifest file, define your service:

<service
  android:name=".YourService"
  android:enabled="true"
  android:exported="true"
  android:description="@string/my_service_desc"
  android:label="@string/my_infinite_service">
  <intent-filter>
    <action android:name="com.yourproject.name.LONGRUNSERVICE" />
  </intent-filter>
</service>

Note: There's a list of already implemented actions, but you can define your own actions for the intent to launch the service. Simply create a singleton class and define the strings assigning them a String that should be unique. The "enabled" set to true is just to instantiate the service, and exported set to true is just in the case you need other applications sending intents to your Service. If not, you can safely set that last to false.

The following step would be starting your service from your activity. That can be easily done by:

public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Intent servIntent = new Intent("com.yourproject.name.LONGRUNSERVICE");
    startService(servIntent);

    ...
  }
}

The final step is to define your Service initializations. Keep an eye on the onBind() method. Since you don't want it to be bound, simply return null. It would be something like this:

public class MyService extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    // This won't be a bound service, so simply return null
    return null;
  }

  @Override
  public void onCreate() {
    // This will be called when your Service is created for the first time
    // Just do any operations you need in this method.
  }

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

Now your service will run even if you close your main Activity. There's just one step left: To help your Service not being finished, run it as a foreground service (do that within your Service). This will basically create a notification icon in the status bar. This doesn't mean your main Activity is running too (this is why you don't want a bound service), as Activities and Services have different life-cycles. In order to help that Service run for so long, try keeping your heap as low as possible so it will avoid the Android SO killing it.

One more acclaration: You cannot test whether the Service is still running killing the DVM. If you kill the DVM, you'll killing everything, thus also the Service.

Dative answered 3/2, 2014 at 16:32 Comment(2)
Oh no! I meant the right but wrote the wrong. Of course a started (or unbound) service is the correct one. Just messed up the order in first sentence... However it could be figured out what I intended from the rest of my answer ;)Morita
I using foreground service and alarm manager to getLastLocation with setting the interval. Problem: Location service is off when I close or kill the app. After 2 days finally found the issue is because my app target SDK 30 or Android 11. Solution: Need add ACCESS_BACKGROUND_LOCATION permission to AndroidManifest file and need go app setting -> permission -> Location -> "Allow all the time". Hope can help the people who face the same issue. ^^Endostosis
M
2

There are two kinds of Android Services: started and bound. You need to use the first one(started Service). The documentation shows how to use it, there is a nice lifecycle diagram below.

Instead of starting and binding the service in one step using bindService() you need to call startService() first. However startService() won't help you starting from Oreo. You need to usestartForegroundService() from there. Then it runs until you stop it, even if the app is closed.

Start Sticky Service if you want Android OS to pick your service again if it killed it.

Morita answered 3/2, 2014 at 12:19 Comment(0)
I
0

it has been changed for the API >29 to get the user location simply add these permissions:

android:name="android.permission.ACCESS_FINE_LOCATION" 
android:name="android.permission.ACCESS_COARSE_LOCATION"   
android:name="android.permission.ACCESS_BACKGROUND_LOCATION" 

ACCESS_BACKGROUND_LOCATION should be requested after ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION

then you can use any way to get the user's location such :

const val LOCATION_UPDATE_INTERVAL = 5000L
const val FASTEST_LOCATION_INTERVAL = 2000L
lateinit var fusedLocationProviderClient: FusedLocationProviderClient

fusedLocationProviderClient = FusedLocationProviderClient(this)
val request = LocationRequest().apply {
                interval = LOCATION_UPDATE_INTERVAL
                fastestInterval = FASTEST_LOCATION_INTERVAL
                priority = PRIORITY_HIGH_ACCURACY
            }

fusedLocationProviderClient.requestLocationUpdates(
                request,
                locationCallback,
                Looper.getMainLooper()
            )

val locationCallback = object : LocationCallback() {
    override fun onLocationResult(result: LocationResult?) {
        super.onLocationResult(result)
        if(isTracking.value!!) {
            result?.locations?.let { locations ->
                for(location in locations) {
                    Logd.d("NEW LOCATION: ${location.latitude}, 
                  ${location.longitude}")
                }
            }
        }
    }
Interlocutor answered 20/6, 2022 at 12:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.