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);
}
}
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.
replace return Service.START_NOT_STICKY; with return START_STICKY;
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)
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"
...
Related
In my application, I use location based service in background. So I need to restart my service when it gets destroyed.
But I got this message in logcat
Spurious death for ProcessRecord{320afaf6 20614:com.odoo.crm:my_odoo_gps_service/u0a391}, curProc for 20614: null
My service onTaskRemoved
#Override
public void onTaskRemoved(Intent rootIntent) {
System.out.println("onTaskRemoved called");
Intent restartServiceIntent = new Intent(App.getAppContext(), this.getClass());
restartServiceIntent.setPackage(getPackageName());
PendingIntent restartServicePendingIntent =
PendingIntent.getService(App.getAppContext(), 1, restartServiceIntent,
PendingIntent.FLAG_ONE_SHOT);
AlarmManager alarmService =
(AlarmManager) App.getAppContext().getSystemService(Context.ALARM_SERVICE);
alarmService.set(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 1000,
restartServicePendingIntent);
}
My service onDestroy
#Override
public void onDestroy() {
System.out.println("destroy service");
super.onDestroy();
wakeLock.release();
}
My service onStartCommand
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_STICKY;
}
I don`t know what is the error. I searched both in google & stackoverflow.
All of them refer Service.START_STICKY. but I already used it.
Same service restart works in KitKat, but with some delay(~5 mins).
Any help is appreciated.
You can restart it by using a BroadcasteReceiver which handles the broadcast sent from onDestroy() of your service.
How to do this:
StickyService.java
public class StickyService extends Service
{
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
sendBroadcast(new Intent("IWillStartAuto"));
}
#Override
public void onDestroy() {
super.onDestroy();
sendBroadcast(new Intent("IWillStartAuto"));
}
}
RestartServiceReceiver.java
public class RestartServiceReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context.getApplicationContext(), StickyService.class));
}
}
Declare the components in manifest file:
<service android:name=".StickyService" >
</service>
<receiver android:name=".RestartServiceReceiver" >
<intent-filter>
<action android:name="IWillStartAuto" >
</action>
</intent-filter>
</receiver>
Hope this will help you.
Your code in onTaskRemoved is preventing the system to run the killProcess commands. The delay on Kitkat is caused by using alarmService.set, which is inexact from API 19. Use setExact instead.
If you have a service that you want to keep alive, it is recommended that you attach a notification to it and make it foreground. That way the likeliness of it being killed would be lowered.
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.IBinder;
import android.support.v7.app.NotificationCompat;
import java.io.File;
import java.io.IOException;
import activity.MainActivity;
import activity.R;
import fragment.MainFragment;
public class MyService extends Service {
public static final int NOTIFICATION_CODE = 1;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(NOTIFICATION_CODE, getNotification());
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
#Override
public boolean stopService(Intent name) {
return super.stopService(name);
}
/**
* Create and return a simple notification.
*/
private Notification getNotification() {
Notification notification;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setColor(getResources()
.getColor(R.color.material_deep_teal_500))
.setAutoCancel(true);
notification = builder.build();
notification.flags = Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTO_CANCEL;
return notification;
}
}
You can modify this code to accomodate your needs but this is the basic structure to start foreground service. Which restarts if gets killed.
how you check issocketalive that socket is connected or not ?
if sockettimeoutexception is generated then try to on set getinputstream and getoutputstream.
other issue that may be socket not closed properly.
So if possible then put your socket code here
this worked for me
Add this attribute in android:allowBackup="false" in manifest file in application tag.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="false"
tools:replace="android:allowBackup">
</application>
</manifest>
The idea of having a service ALWAYS running in background in Android is just wrong 99% of the times.
The system need to "shut down" CPU, and switch to a low battery usage profile.
You are saying you have a location based service. I assume you are using Google Play Services FusedLocationProvider, if not you should.
The FusedLocationProvider allow you to register for location changes using a PendingIntent. Meaning your services doesn't need to run all the time, it just need to register for location changes and then react when a new location come and do its stuff.
See the FusedLocationProviderApi official documentation.
To start listening for location updates
connect to the GoogleClient using the LocationServices.API API
Build your LocationRequest according to your needs (see the doc)
Call requestLocationUpdates() using the PendingIntent version
To stop listening
connect to the GoogleClient using the LocationServices.API API
Call removeLocationUpdates() using the same PendingIntent
Your PendingIntent can launch another service to handle the new location.
For example doing this from a service:
public void startMonitoringLocation(Context context) {
GoogleApiClient client = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.build()
ConnectionResult connectionResult = mApiClient.blockingConnect();
if (connectionResult.isSuccess()) {
LocationServices.FusedLocationApi
.requestLocationUpdates(client, buildLocationRequest(), buildPendingIntent(context));
} else {
handleConnectionFailed(context);
}
}
Then the service can immediately stop.
The first time this code run it WILL fail. The connection to the google client usually require the user to take some actions. The ConnectionResult.hasResolution() method will return true if this is the case. Otherwise the reason is something else and you can't recover from it. Meaning the only thing you can do is inform the user the feature will not work or have a nice fallback.
The ConnectionResult.getResolution() give you a PendingIntent you need to use an Activity and startIntentSenderForResult() method on the Activity to resolve this intent. So you would create a Notification starting your Activity to resolve that, and in the end call your Service again.
I usually just start an Activity dedicated to do all the work. It's lot easier but you don't want to call connectBlocking() in it. Check out this on how to do it.
You may ask why not requesting location updates directly in the Activity. That's actually perfectly fine, unless you need the location monitor to automatically start with the device, even if the user didn't explicitly opened the App.
<receiver android:name=".BootCompletedBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
This way you can just run your service to connect and request location updates when the device is rebooted.
Example on how you can build your location request:
public LocationRequest buildLocationRequest() {
LocationRequest locRequest = LocationRequest.create();
// Use high accuracy
locRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// how often do you need to check for the location
// (this is an indication, it's not exact)
locRequest.setInterval(REQUIRED_INTERVAL_SEC * 1000);
// if others services requires the location more often
// you can still receive those updates, if you do not want
// too many consider setting this lower limit
locRequest.setFastestInterval(FASTEST_INTERVAL_SEC * 1000);
// do you care if the user moved 1 meter? or if he move 50? 1000?
// this is, again, an indication
locRequest.setSmallestDisplacement(SMALLEST_DISPLACEMENT_METERS);
return locRequest;
}
And your pending intent:
public PendingIntent buildPendingIntent(Context context) {
Intent intent = new Intent(context, LocationUpdateHandlerService.class);
intent.setAction(ACTION_LOCATION_UPDATE);
intent.setPackage(context.getPackageName());
return PendingIntent.getService(context, REQUEST_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);
}
Your LocationUpdateHandlerService can be an IntentService if you need to do work in background:
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
Location location = extras.getParcelable(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
handleLocationChanged(location);
} else {
Log.w(TAG, "Didn't receive any location update in the receiver");
}
}
}
But can also be a Broadcast or anything that suits you.
Finally I achieved with help of Evernote JobService
Github link - https://github.com/evernote/android-job
Step 1: Add evernote jobservice dependency
implementation 'com.evernote:android-job:1.3.0-alpha03'
Step 2: Create DemoJobCreator.java class
public class DemoJobCreator implements JobCreator {
#Override
#Nullable
public Job create(#NonNull String tag) {
switch (tag) {
case DemoSyncJob.TAG:
return new DemoSyncJob();
default:
return null;
}
}
}
Step 3: Create DemoSyncJob.java class
public class DemoSyncJob extends Job {
public static final String TAG = ">>>> job_demo_tag";
#Override
#NonNull
protected Result onRunJob(Params params) {
// run your job here
Log.d(TAG, "onRunJob: ");
if(!isMyServiceRunning(this.getContext(), TestService.class)){
Intent intent=new Intent(context,TestService.class);
context.startService(intent);
}
scheduleJob();
return Job.Result.SUCCESS;
}
public static void scheduleJob() {
new JobRequest.Builder(DemoSyncJob.TAG)
.setExecutionWindow(2_000L, 2_000L)
//.setPeriodic(900000) -> recommended. but it will work after 15 min (if you used this no need scheduleJob(); inside onRunJob();)
.build()
.schedule();
}
public static boolean isMyServiceRunning(Context context, Class<?> serviceClass) {
try {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
}catch (Exception e){
Log.e(TAG, "isMyServiceRunning: ",e );
}
return false;
}
}
Step 4: In your Application file (If not available create it) add following line in onCreate()
JobManager.create(this).addJobCreator(new DemoJobCreator());
Step 5: Finally start JobService in your Activity
DemoSyncJob.scheduleJob();
This JobService will check service running or not (every 2 second) If service not running it will restart the service.
Disclaimer : This may be not right solution. But it will 100% working.
I hope it helps atleast anyone in future.
I'm having trouble sending a string extra with my PendingIntent that I pass to LocationServices.FusedLocationApi.requestLocationUpdates(GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent).
It appears that the username extra i'm putting onto the Intent is mangling the location that requestLocationUpdates is trying to hand off to my IntentService as intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED) returns null.
EDIT
I've tried making a User class that implements Parcelable and putting it as an extra:
mRequestLocationUpdatesIntent.putExtra("username", new User(username));
and I've also tried to put the Parcelable User inside a Bundle as suggested via comment in this bug report https://code.google.com/p/android/issues/detail?id=81812:
Bundle userBundle = new Bundle();
userBundle.putParcelable("user", new User(username));
mRequestLocationUpdatesIntent.putExtra("user", userBundle);
in my service:
Bundle userBundle = intent.getBundleExtra("user");
User user = userBundle.getParcelable("user");
String username = user.getUsername();
However neither of these approaches has made any difference. Whenever I put any extra onto my intent, the location is never added to the intent when the updates occur.
I setup this IntentService to handle location updates:
public class LocationUpdateService extends IntentService {
private final String TAG = "LocationUpdateService";
public LocationUpdateService() {
super("LocationUpdateService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
Bundle extras = intent.getExtras();
Log.d(TAG, "keys found inside intent: " + TextUtils.join(", ", extras.keySet()));
String username = intent.getStringExtra("username");
if (username != null) {
Log.d(TAG, "username: " + username);
} else {
Log.d(TAG, "username: null");
}
if (!intent.hasExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
Log.d(TAG, "intent does not have location :(");
}
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
if (location == null) {
Log.d(TAG, "location == null :(");
}
Log.d(TAG, "latitude " + String.valueOf(location.getLatitude()));
Log.d(TAG, "longitude " + String.valueOf(location.getLongitude()));
...
}
}
When the user clicks a button, the startLocationUpdates is called in my main activity:
main activity class:
...
Boolean mLocationUpdatesEnabled = false;
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(LOCATION_UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(LOCATION_UPDATE_FASTEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
protected void startLocationUpdates() {
Log.d(TAG, "startng location updates...");
mLocationUpdatesEnabled = true;
if (mLocationRequest == null) {
createLocationRequest();
}
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
// create a PendingIntent
mRequestLocationUpdatesPendingIntent = PendingIntent.getService(getApplicationContext(), 0,
mRequestLocationUpdatesIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
// request location updates
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates started");
}
protected void stopLocationUpdates() {
Log.d(TAG, "stopping location updates...");
mLocationUpdatesEnabled = false;
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates stopped");
}
This all works well and good; When the user presses the button, toggleLocationUpdates is called, which calls LocationServices.FusedLocationApi.requestLocationUpdates which calls my LocationUpdateService where I'm able to get the location.
The trouble comes when I tried to put a string extra onto my Intent using Intent.putExtra(String, String):
main activity class:
...
protected void startLocationUpdates(String username) {
....
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
//////////////////////////////////////////////////////////////////
//
// When I put this extra, IntentService sees my username extra
// but the parcelableExtra `location` == null :(
//
//////////////////////////////////////////////////////////////////
mRequestLocationUpdatesIntent.putExtra("username", username);
...
}
...
EDIT I had started the next sentence as a statement rather than a question: "I am using..."
Am I using the correct approach to sending some extra data to this location update handling IntentService or is there a more-sane way to go about this?
Is this a bug or just poor documentation?
Using the IntentService coupled with the FusedLocationProviderAPI will present issues. From the Developer Docs titled Receiving Location Updates:
Depending on the form of the request, the fused location provider
either invokes the LocationListener.onLocationChanged() callback
method and passes it a Location object, or issues a PendingIntent that
contains the location in its extended data. The accuracy and frequency
of the updates are affected by the location permissions you've
requested and the options you set in the location request object
Further, a PendingIntent is used for extending permissions for another piece of code (FusedLocationProviderAPI in Google Play Services) to execute their code within your apk. An IntentService is used to start a Service defined within the scope of your apk.
So, the method requires an implementation of LocationListener for foreground updates, or a PendingIntent for background updates coupled with a Broadcast Receiver.
This is a working example of some methods used to request location updates from a PendingIntent coupled with extra values.
Note: LocalStorage.java is a utility class for storing local variables, it is not part of the Android API
GPSPlotter
/**
* Private helper method to initialize the Google Api Client with the
* LocationServices Api and Build it for use.
*/
private void initializeGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
/**
* Private helper method to determine whether or not GooglePlayServices
* are installed on the local system.
*
* #return services are installed.
*/
private boolean googlePlayServicesInstalled() {
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
return result == ConnectionResult.SUCCESS;
}
/**
* Private method to build the Api Client for use with the LocationServices API.
*/
private synchronized void buildApiClient() {
Log.w(TAG, "Building Google Api Client...");
initializeGoogleApiClient();
}
/**
* Private method used to connect the ApiClient to the Api hosted by Google for
* Accessing Locations.
*/
private void connectClient() {
mGoogleApiClient.connect();
}
/**
* User passes in a requested interval polling time in seconds as an
* integer.
*
* #param theAccount is a reference to the parent activity used for updating views.
*/
public void beginManagedLocationRequests(MyAccount theAccount) {
if (mAccount == null)
mAccount = theAccount;
startBackgroundUpdates();
}
/**
* Public method to end the managed Location Requests.
*/
public void endManagedLocationRequests() {
endBackgroundUpdates();
}
/**
* This method handles the switch in polling rates by stopping and then starting once more the
* background udpates, which in turn sets the interval in another method in the call stack.
* #param theInterval the desired interval polling rate
*/
public void changeRequestIntervals(int theInterval) {
mIntentInterval = theInterval;
if (LocalStorage.getRequestingBackgroundStatus(mContext)) {
endBackgroundUpdates();
startBackgroundUpdates();
}
}
/**
* Private helper method to build an Intent that will be couple with a pending intent uses
* for issuing background Location requests.
*
* #return theIntent
*/
private Intent buildBackgroundRequestIntent() {
Intent intent = new Intent(mContext, BackgroundLocationReceiver.class);
intent.setAction(BACKGROUND_ACTION);
intent.putExtra(User.USER_ID, mUserID);
return intent;
}
/**
* Private helper method used to generate a PendingIntent for use when the User requests background service
* within the FusedLocationApi until the Interval is changed.
*
* #return pendingIntent
*/
private PendingIntent buildRequestPendingIntent(Intent theIntent) {
Log.w(TAG, "building pending intent");
return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}
/**
* Private method to start the Location Updates using the FusedLocation API in the background.
*/
private void startBackgroundUpdates() {
Log.w(TAG, "Starting background updates");
if (googlePlayServicesInstalled()) {
LocalStorage.putBackgroundRequestStatus(true, mContext);
LocalStorage.putLocationRequestStatus(true, mContext);
registerAlarmManager();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, buildLocationRequest(), buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
}
/**
* Private method to end background updates.
*/
private void endBackgroundUpdates() {
Log.w(TAG, "Ending background updates");
LocalStorage.putBackgroundRequestStatus(false, mContext);
LocalStorage.putLocationRequestStatus(false, mContext);
unregisterAlarmManager();
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
BackgroundLocationReceiver
public class BackgroundLocationReceiver extends BroadcastReceiver {
private static final String TAG = "BLocRec: ";
private static final String UPLOAD_ERROR_MESSAGE = "Background Service to Upload Coordinates Failed.";
private static final String UPLOAD_MESSAGE = "Coordinate Batch Pushed to Database.";
public BackgroundLocationReceiver() {
//Default, no-arg constructor
}
/**
* This method handles any location updates received when the app is no longer in focus. Coordinates are
* stored in the local database and uploaded once every hour.
* #param context the application context
* #param intent is the pending intent
*/
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().matches(GPSPlotter.BACKGROUND_ACTION)) {
Log.w(TAG, "BLR Received-background");
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
storeLocation(location, context, intent.getStringExtra(User.USER_ID));
}
EDIT
The method below builds a LocationRequest necessary for invoking the requestLocationUpdates() method
/**
* Private helper method used to generate a LocationRequest which will be used to handle all location updates
* within the FusedLocationApi until the Interval is changed.
*
* #return locationRequest
*/
private LocationRequest buildLocationRequest() {
int dateConversion = 1000;
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(mIntentInterval * dateConversion);
locationRequest.setFastestInterval((mIntentInterval / 2) * dateConversion);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Log.w(TAG, "Building location request");
return locationRequest;
}
EDIT
After a long discussion in chat with Catherine, we came to the conclusion that google play services library 7.5 has a bug that does not process the Parcelable Extra Location passed from FusedLocationProviderAPI when other extras are put into the Intent. However, 7.0 does provide this capability. She said that she will submit a bug and we'll see how long it takes the Android team to resolve
Objective: To save current locations in Service in database after exact 15 min with in service (using less battery).I use these location at various points in my app.
locationrequest = LocationRequest.create();
locationrequest.setInterval(5*60000);
locationrequest
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
Problem: I'm using the above code does not request location according to set interval value.Although, I'm aware that This interval is inexact. You may not receive updates at all, or you may receive them slower than requested. You may also receive them faster than requested. Sometimes, the location is updated after 1 min , I don't want to waste processing and battery to get locations at small intervals.
public class LoginActivity extends Activity implements OnClickListener
,
GooglePlayServicesClient.ConnectionCallbacks,GooglePlayServicesClient.OnConnectionFailedListener,LocationListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_screen);
///my code
mIntentService = new Intent(LoginActivity.this,LocationService.class);
mIntentService.putExtra("time",String.valueOf(System.currentTimeMillis()) );
mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);
int resp =GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(resp == ConnectionResult.SUCCESS){
locationclient = new LocationClient(this,this,this);
locationclient.connect();
}
else{
Toast.makeText(this, "Google Play Service Error " + resp, Toast.LENGTH_LONG).show();
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
// TODO Auto-generated method stub
}
#Override
public void onConnected(Bundle arg0) {
// TODO Auto-generated method stub
Log.i("fused", " onConnected " );
// mIntentService = new Intent(LoginActivity.this,LocationService.class);
// mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);
locationrequest = LocationRequest.create();
locationrequest.setInterval(5*60000);
// locationrequest
// .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
// locationrequest = LocationRequest.create();
// locationrequest.setInterval(1000);//??
// locationclient.requestLocationUpdates(locationrequest, this);
}
#Override
public void onDisconnected() {
// TODO Auto-generated method stub
}
}
LocationService
public class LocationService extends IntentService {
private String TAG = this.getClass().getSimpleName();
public LocationService() {
super("Fused Location");
}
public LocationService(String name) {
super("Fused Location");
}
#Override
protected void onHandleIntent(Intent intent) {
// Log.i("fused", "onHandleIntent LocationService");
Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
if(location !=null){
String time= intent.getStringExtra("time");
Log.i("fused", "onHandleIntent LocationService " +time+"---"+ location.getLatitude() + "," + location.getLongitude());
updateTransientLocation(getApplicationContext(), location);
}
}
Also, I need to save these locations periodically in database in background only and hence cannot use requestLocationUpdates without pending intent to service.
I have refered to this for the code
Thanks.
EDIT -SOLUTION This is how my problem was solved
Code in Activity
Intent myIntent = new Intent(context,LocationReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, myIntent, 0);
alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
// 120000,pi);
I removed the location Service class and added location receiver
LocationReceiver
public class LocationReceiver extends BroadcastReceiver implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {
SharedPreferences prefs = null;
LocationClient locationclient = null;
Context contxt;
/** For location poller NO LONGER IN USE **/
#Override
public void onReceive(Context context, Intent intent) {
contxt=context;
//Log.i("locationreciever", "in location rec");
Log.i("fused", "in location rec");
int resp = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
if (resp == ConnectionResult.SUCCESS) {
locationclient = new LocationClient(context, this, this);
locationclient.connect();
} else {
Log.i("fused", "loc client Google Play Service Error");
}
}
#Override
public void onLocationChanged(Location location) {
Log.i("fused", " onLocationChanged Location Request :" + location.getLatitude() + "," + location.getLongitude());
updateTransientLocation(contxt, location);
if (locationclient != null) {
if (locationclient.isConnected()) {
locationclient.removeLocationUpdates(this);
locationclient.disconnect();
}
}
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i("fused", "loc client connection failed");
}
#Override
public void onConnected(Bundle arg0) {
Log.i("fused", "loc client onConnected");
LocationRequest locationrequest = LocationRequest.create();
locationrequest
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, this);
}
#Override
public void onDisconnected() {
Log.i("fused", "loc client disconnected");
}
}
The best solution would be to use your current approach. You're tell the OS that you don't need locations more often, but something else might be requesting locations, in which case you might as well just accept it, now that the phone has already woken up to get a GPS fix and broadcast it to every process that's interested in a location. This way, your application may actually never have to turn on the GPS, because you're basically just using a location fix that was requested by another process more often that every 15 minutes. The keyword to search for here is the new fused location provider.
If you insist on getting a location exactly every 15 minutes, you can, instead of scheduling a location request, use an AlarmManager to schedule a job to run every 15 minutes. In your alarm manager, you can then immediately request a new single location, and then completely stop requesting new locations until your job is scheduled to run again. If you go down this route, you'll likely run into problems with your service ending before you get a result, because of the asynchronous nature of the location service. Therefore, you want to poll for a location in your alarm manager. You can use a project like CWAC LocationPoller for that
The documentation has examples of how to schedule recurring events:
https://developer.android.com/training/scheduling/alarms.html
Depending on your need, you should be think about the fact that a location may not be available every 15 minutes. Maybe the user is outside of GPS/wifi/phone range. So it may or may not be beneficial to start a task a bit early, or more often, to make sure you have a reasonable fix after your 15 minute window has elapsed.
With all that said, here's the code snippet you're actually interested in to solve your specific problem (taken directly from the CWAC locationpoller site):
1. Create a recurring alarm manager
mgr=(AlarmManager)getSystemService(ALARM_SERVICE);
Intent i=new Intent(this, LocationPoller.class);
Bundle bundle = new Bundle();
LocationPollerParameter parameter = new LocationPollerParameter(bundle);
parameter.setIntentToBroadcastOnCompletion(new Intent(this, LocationReceiver.class));
// try GPS and fall back to NETWORK_PROVIDER
parameter.setProviders(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER});
parameter.setTimeout(60000);
i.putExtras(bundle);
pi=PendingIntent.getBroadcast(this, 0, i, 0);
mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
PERIOD,
pi);
2. Create a BroadcastReceiver to receive your location data
Bundle b=intent.getExtras();
LocationPollerResult locationResult = new LocationPollerResult(b);
Location loc=locationResult.getLocation();
String msg;
if (loc==null) {
loc=locationResult.getLastKnownLocation();
if (loc==null) {
msg=locationResult.getError();
}
else {
msg="TIMEOUT, lastKnown="+loc.toString();
}
}
else {
msg=loc.toString();
}
if (msg==null) {
msg="Invalid broadcast received!";
}
From http://developer.android.com/reference/android/app/AlarmManager.html#setRepeating%28int,%20long,%20long,%20android.app.PendingIntent%29
as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.
So you will have to do something like this:
public void startTheClock(int interval) {
Intent pingerIntent = new Intent(this, findLoc.class);
pingerIntent.setAction("start_clock");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this.getApplicationContext(),
0,
pingerIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) this.getSystemService(
Context.ALARM_SERVICE);
alarms.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval,
pendingIntent);
}
And in the class that captures that intent (in this example, findLoc.java):
public class findLoc extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
callMethodThatSearchesForLocation();
startTheClock(INTERVAL);
}
}
Where interval is a constant in miliseconds.
NOTE: I actually had some problems with that because it displayed an error on setExact(..) since my minimum SDK did not support this. Which is a bit of a paradox if you want the same behaviour on SDK lower than 19 and higher or equal to 19.
I have a wierd problem in which I have configured a location listener (Google play services fused locations) inside a service. I use the pending intent method (recommended for services).
The thing is that when I'm debugging with Eclipse everything works fine, I receive periodic location updates. But when I compile the apk and run it, the behaviour changes and location updates is called only once.
Here is the simplified code:
public class TrackingService extends Service implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener {
//fields
private PendingIntent locPendingIntent;
// ====== CONFIGURATION ========
BroadcastReceiver locationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Do this when the system sends the intent
if (intent.getAction().equals(AppManager.ACTION_LOCATION_READY)) {
// Called only once on compiled apk. Working fine when debugging
Bundle b = intent.getExtras();
Location loc = (Location) b
.get(LocationClient.KEY_LOCATION_CHANGED);
if(loc != null) {
onLocationChanged(loc);
}
}
}
};
/*
* Create a new location client, using the enclosing class to handle
* callbacks.
*/
private void setUpLocationClientIfNeeded() {
if (mLocationClient == null)
mLocationClient = new LocationClient(this, this, this);
}
// ======== LIFE CYCLE ========
#Override
public void onCreate() {
super.onCreate();
mInProgress = false;
// Create the LocationRequest object
mLocationRequest = LocationRequest.create();
mLocationRequest
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
// Set the update interval to 5 seconds
mLocationRequest.setInterval(UPDATE_INTERVAL);
// Set the fastest update interval to 2.5 second
mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
setUpLocationClientIfNeeded();
registerReceiver(locationReceiver, new IntentFilter(AppManager.ACTION_LOCATION_READY));
}
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
if (mLocationClient.isConnected() || mInProgress)
return START_STICKY;
setUpLocationClientIfNeeded();
if (!mLocationClient.isConnected() || !mLocationClient.isConnecting()
&& !mInProgress) {
mInProgress = true;
mLocationClient.connect();
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// ===== GOOGLE PLAY SERVICES CALLBACKS ======
/*
* Called by Location Services when the request to connect the client
* finishes successfully. At this point, you can request the current
* location or start periodic updates
*/
public void onConnected(Bundle bundle) {
startListeningForLocations();
}
/*
* Called by Location Services if the connection to the location client
* drops because of an error.
*/
public void onDisconnected() {
// Turn off the request flag
mInProgress = false;
// Destroy the current location client
mLocationClient = null;
}
/*
* Called by Location Services if the attempt to Location Services fails.
*/
public void onConnectionFailed(ConnectionResult connectionResult) {
mInProgress = false;
/*
* Google Play services can resolve some errors it detects. If the error
* has a resolution, try sending an Intent to start a Google Play
* services activity that can resolve error.
*/
if (connectionResult.hasResolution()) {
} else {
// If no resolution is available, display an error dialog
}
}
private void startListeningForLocations() {
Intent intent = new Intent(AppManager.ACTION_LOCATION_READY);
locPendingIntent = PendingIntent.getBroadcast(
getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
mLocationClient.requestLocationUpdates(mLocationRequest,
locPendingIntent);
}
// Define the callback method that receives location updates
public void onLocationChanged(Location location) {
// process with location
}
}
Manifest permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
If anyone needs more details, just ask me.
Thank you very much.
I had the same problem but I think I could fix it.
In my code, I have an Activity and a Service. I was calling the Location Client from the service. In the debug mode it worked fine, but when I bulid the release APK, it didn't work.
I create a method that calls the Location Client and instead calling from the Service, I called it from the activity (at the end of the onServiceConnected method) and it worked.
I hope it work for you.
Best regards,
Leonardo.
To retrieve fused location in background, I have created a library which is very similar to cwac-locpoll library created by Commonsguy.
Inside PollerThread , I am trying to connect, request and retrieve the locations using LocationClient.
I am able to get connected by receiving callback on onConnected method but I am not able to get callback on onLocationChanged method.so my onTimeout thread executes as per decided interval.
NOTE: This issue happens only when screen light goes off.otherwise it works completely fine.
I suspect there might be bug in new Location Api.
Here is the implementation of my PollerThread,
private class PollerThread extends WakefulThread implements GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,LocationListener{
private static final String TAG = "PollerThread";
//context
private Context mContext=null;
private LocationClient mLocationClient=null;
private LocationRequest mLocationRequest=null;
private LocationManager locMgr=null;
private Intent intentTemplate=null;
private Handler handler=new Handler();
private Runnable onTimeout = new Runnable() {
#Override
public void run() {
Log.e(TAG, "onTimeout");
//prepare broadcast intent
Intent toBroadcast=new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_ERROR, "Timeout!");
toBroadcast.putExtra(
FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, false);
toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
mLocationClient.getLastLocation());
sendBroadcast(toBroadcast);
//stop the thread
quit();
}
};
PollerThread(Context mContext,LocationRequest mLocationRequest,PowerManager.WakeLock lock, LocationManager locMgr,
Intent intentTemplate) {
super(lock, "LocationPoller-PollerThread");
Log.e(TAG, "PollerThread");
this.mContext=mContext;
this.mLocationRequest=mLocationRequest;
this.locMgr=locMgr;
this.intentTemplate=intentTemplate;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
Log.e(TAG, "onPreExecute");
//setup timeout
setTimeoutAlarm();
//initiate connection
initiateConnection();
}
#Override
protected void onPostExecute() {
super.onPostExecute();
Log.e(TAG, "onPostExecute");
//remove timeout
removeTimeoutAlarm();
//disconnect
initiateDisconnection();
}
/**
* Called when the WakeLock is completely unlocked.
* Stops the service, so everything shuts down.
*/
#Override
protected void onUnlocked() {
Log.e(TAG, "onUnlocked");
stopSelf();
}
private void setTimeoutAlarm() {
Log.e(TAG, "setTimeoutAlarm");
handler.postDelayed(onTimeout, FusedLocationUtils.DEFAULT_TIMEOUT);
}
private void removeTimeoutAlarm()
{
Log.e(TAG, "removeTimeoutAlarm");
handler.removeCallbacks(onTimeout);
}
private void initiateConnection()
{
Log.e(TAG, "initiateConnection");
mLocationClient = new LocationClient(this.mContext, this, this);
mLocationClient.connect();
}
private void initiateDisconnection()
{
Log.e(TAG, "initiateDisconnection");
if(mLocationClient.isConnected())
{
mLocationClient.disconnect();
}
}
#Override
public void onConnected(Bundle arg0) {
Log.e(TAG, "onConnected");
Log.e(TAG, "provider: GPS-"+locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)+" NETWORK-"+locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
if (!(locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)) && !(locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
Log.e(TAG, "both disabled");
//get last location and broadcast it
getLastLocationAndBroadcast();
//stop the thread
quit();
}
else
{
Log.e(TAG, "provider enabled");
//get latest location and broadcast it
getLatestLocationAndBroadcast();
//don't quit from here,quit from onLocationChanged
}
}
#Override
public void onDisconnected() {
Log.e(TAG, "onDisconnected");
// TODO Auto-generated method stub
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.e(TAG, "onConnectionFailed");
// TODO Auto-generated method stub
}
#Override
public void onLocationChanged(Location location) {
Log.e(TAG, "onLocationChanged");
//prepare broadcast intent
Intent toBroadcast=new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_LOCATION, location);
sendBroadcast(toBroadcast);
//stop further updates
stopUpdates();
//stop the thread
quit();
}
private void getLatestLocationAndBroadcast() {
Log.e(TAG, "getLatestLocationAndBroadcast");
if(mLocationClient.isConnected() && servicesConnected())
{
Log.e(TAG, "going to request updates");
Log.e(TAG, "lockStatic.isHeld(): "+lockStatic.isHeld());
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
else
{
Log.e(TAG, "not going to request updates");
}
}
private void stopUpdates() {
Log.e(TAG, "stopUpdates");
if(servicesConnected())
{
Log.e(TAG,getString(R.string.location_updates_stopped));
mLocationClient.removeLocationUpdates(this);
}
else
{
Log.e(TAG,"can't do:"+getString(R.string.location_updates_stopped));
}
}
private void getLastLocationAndBroadcast() {
Log.e(TAG, "getLastLocationAndBroadcast");
if(mLocationClient.isConnected() && servicesConnected())
{
Log.e(TAG, "going to get last location: "+mLocationClient.getLastLocation());
Intent toBroadcast = new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_ERROR,
"Location Provider disabled!");
toBroadcast.putExtra(
FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, true);
toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
mLocationClient.getLastLocation());
sendBroadcast(toBroadcast);
}
else
{
Log.e(TAG, "not going to get last location");
}
}
}
and servicesConnected method implementation,
/**
* Verify that Google Play services is available before making a request.
*
* #return true if Google Play services is available, otherwise false
*/
private boolean servicesConnected() {
Log.e(TAG, "servicesConnected");
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
// In debug mode, log the status
Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_available));
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Display an error dialog
Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_unavailable));
Toast.makeText(this, getString(R.string.play_services_unavailable), Toast.LENGTH_SHORT).show();
return false;
}
}
If you want to listen to frequent location updates in the background (e.g., every second), you should be running your code inside a Service:
http://developer.android.com/reference/android/app/Service.html
Activities can be ended by the Android platform at any point in time in which they are not in the foreground.
When using a Service, I would recommend having the Service implement the LocationListener directly, and not a Thread inside the Service. For example, use:
public class LocListener extends Service implements com.google.android.gms.location.LocationListener, ...{
I've used this design of implementing the LocationListener directly on the Service with the LocationClient and fused location provider in my GPS Benchmark app and I can confirm that this works even when the screen is off and the app is running in the background.
If you want to listen to occasional location updates in the background (e.g., every minute) using the fused location provider, a better design is to use PendingIntents, using the LocationClient.requestLocationUpdates(Location Request, PendingIntent callbackIntent) method:
https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20android.app.PendingIntent)
From the above Android doc:
This method is suited for the background use cases, more specifically for receiving location updates, even when the app has been killed by the system. In order to do so, use a PendingIntent for a started service. For foreground use cases, the LocationListener version of the method is recommended, see requestLocationUpdates(LocationRequest, LocationListener).
Any previous LocationRequests registered on this PendingIntent will be replaced.
Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value on the intent.
See the Activity Recognition example for a more detailed description of using PendingIntents to get updates while running in the background:
https://developer.android.com/training/location/activity-recognition.html
Modified excerpts from this documentation are below, changed by me to be specific to location updates.
First declare the Intent:
public class MainActivity extends FragmentActivity implements
ConnectionCallbacks, OnConnectionFailedListener {
...
...
/*
* Store the PendingIntent used to send location updates
* back to the app
*/
private PendingIntent mLocationPendingIntent;
// Store the current location client
private LocationClient mLocationClient;
...
}
Request updates as you currently are, but this time pass in the pending intent:
/*
* Create the PendingIntent that Location Services uses
* to send location updates back to this app.
*/
Intent intent = new Intent(
mContext, LocationIntentService.class);
...
//Set up LocationRequest with desired parameter here
...
/*
* Request a PendingIntent that starts the IntentService.
*/
mLocationPendingIntent =
PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
/*
* Request location updates
*/
mLocationClient.requestLocationUpdates(mLocationRequest, callbackIntent);
Handle Location Updates
To handle the Intent that Location Services sends for each update interval, define an IntentService and its required method onHandleIntent(). Location Services sends out ... updates as Intent objects, using the the PendingIntent you provided when you called requestLocationUpdates(). Since you provided an explicit intent for the PendingIntent, the only component that receives the intent is the IntentService you're defining.
Define the class and the required method onHandleIntent():
/**
* Service that receives Location updates. It receives
* updates in the background, even if the main Activity is not visible.
*/
public class LocationIntentService extends IntentService {
...
/**
* Called when a new location update is available.
*/
#Override
protected void onHandleIntent(Intent intent) {
Bundle b = intent.getExtras();
Location loc = (Location) b.get(LocationClient.KEY_LOCATION_CHANGED);
Log.d(TAG, "Updated location: " + loc.toString());
}
...
}
IMPORTANT - to be as efficient as possible, your code in onHandleIntent() should return as quickly as possible to allow the IntentService to shut down. From IntentService docs:
http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)
This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().
My understanding of the IntentService design is that you can spawn Threads inside onHandleIntent() to avoid blocking other location updates via platform calls to onHandleIntent(), just be aware that the Service will continue to run until all the running threads terminate.
I've spent days trying to get WiFi and cell-based locations with locked screen with Android 6.0 on Nexus 6.
And looks like the native android location service simple does not allow to do it.
Once device got locked it still collects location update events for 10-15 minutes then stops to providing any of location updates.
In my case the solution was to switch from native Android location service to Google Play Services wrapper called com.google.android.gms.location: https://developers.google.com/android/reference/com/google/android/gms/location/package-summary
Yes, I know that some of Android devices lack of GMS, but for my application this is the only solution to perform.
It does not stop sending location updates even when in the background and device screen is locked.
Me personally prefer RxJava library to wrap this service into a stream (examples included): https://github.com/mcharmas/Android-ReactiveLocation