I have an Android app with a Google Map and automatic location updates via Google Play Services' new location API.
Implemented just like this:
https://developer.android.com/training/location/receive-location-updates.html
I'm specifically trying to receive GPS / accurate locations.
It works 100% perfectly and normally, GPS icon is on above, locations coming in every few seconds, no worries.
The odd problem seems to be that if you switch to Google Maps, wait a second, then switch back to my application, my app then gets exactly one more location update, and then stops receiving updates.
My app is properly stopping location updates onPause/onStop, and re-connecting and re-starting them onStart/onResume.
My debug Toasts show "Connected" after switching back from Google Maps, and show one more "Updated Location", then the updates stop. onDisconnected() is not getting called, and checks of mLocationClient.isConnected() report 'true'.
I have since added a hack work-around with a timer handler that runs every few seconds and if a location hasn't been found in the last 10 seconds, it calls stopPauseLocation() and checkStartLocation() below, which does fix the issue and locations start coming in again. Obviously this is an ugly hack and I'm not happy about it.
It seems to be like a bug, something conflicting between Google Maps and my own app, however, I can't for the life of me figure out a real solution.
Any ideas?
Here's the key code snippets:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the LocationRequest object
mLocationRequest = LocationRequest.create();
// Use high accuracy
mLocationRequest.setPriority(
LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the update interval to 2 seconds
mLocationRequest.setInterval(2000);
// Set the fastest update interval to 1 second
mLocationRequest.setFastestInterval(1000);
/*
* Create a new location client, using the enclosing class to
* handle callbacks.
*/
mLocationClient = new LocationClient(this, this, this);
mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
}
#Override
protected void onStart() {
super.onStart();
// Check our connection to play services
checkStartLocation();
}
/*
* Called when the Activity is no longer visible at all.
* Stop updates and disconnect.
*/
#Override
protected void onStop() {
stopPauseLocation();
}
/*
* 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
*/
#Override
public void onConnected(Bundle dataBundle) {
// Display the connection status
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
mLocationClient.requestLocationUpdates(mLocationRequest, this);
super.onStop();
}
private void stopPauseLocation()
{
// If the client is connected
if (mLocationClient.isConnected()) {
/*
* Remove location updates for a listener.
* The current Activity is the listener, so
* the argument is "this".
*/
mLocationClient.removeLocationUpdates(this);
}
/*
* After disconnect() is called, the client is
* considered "dead".
*/
mLocationClient.disconnect();
}
/**
* Helper to check if we're connected to play, and try to connect if not
*/
protected void checkStartLocation() {
if (!mLocationClient.isConnected())
{
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationClient.connect();
}
}
/*
* 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
*/
#Override
public void onConnected(Bundle dataBundle) {
// Display the connection status
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
#Override
public void onDisconnected() {
// Display the connection status
Toast.makeText(this, "Disconnected.",Toast.LENGTH_SHORT).show();
}
/*
* Called by Location Services if the attempt to connect to
* Location Services fails.
*/
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this, "onConnectionFailed", Toast.LENGTH_SHORT).show();
}
// Define the callback method that receives location updates
#Override
public void onLocationChanged(Location location) {
// Report to the UI that the location was updated
String msg = "Updated Location: " +
Double.toString(location.getLatitude()) + "," +
Double.toString(location.getLongitude());
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
The only method I've found around this is a hack, but this is the best version of the hack I've come up with.
The code below will check every 10s (and then incrementally 15s, 20s, up to 30s) for a location update. If no location has been received, it calls removeLocationUpdates() and requestLocationUpdates(), which seems to fix my issue.
private Handler locationCheck = null;
// Track the time of the last location update
private long lastLocationUpdate = System.currentTimeMillis();
private long lastLocationWaitTime = 0;
// How long to wait until we reconnect (10 sec)
private long WAIT_LOCATION_AGE_START = 10000;
// Increments of how much longer to wait before next check
// Increments on every failure to give the system more time to recover
private long WAIT_LOCATION_AGE_INCREMENT = 5000;
// Max time to wait
private long MAX_WAIT_LOCATION_AGE = 30000;
Add to onCreate in your class:
locationCheck = new Handler();
Add to onResume/onStart:
lastLocationUpdate = System.currentTimeMillis();
lastLocationWaitTime = WAIT_LOCATION_AGE_START;
locationCheck.removeCallbacks(locationCheckRunnable);
locationCheck.postDelayed(locationCheckRunnable, 1000);
Add to onStop/onPause:
locationCheck.removeCallbacks(locationCheckRunnable);
private Runnable locationCheckRunnable = new Runnable() {
#Override
public void run() {
if ((System.currentTimeMillis() - lastLocationUpdate) > lastLocationWaitTime)
{
// Verify our connection
checkStartLocation();
// Reset the timer
lastLocationUpdate = System.currentTimeMillis();
// On next check wait a bit longer
lastLocationWaitTime += WAIT_LOCATION_AGE_INCREMENT;
lastLocationWaitTime = Math.min(lastLocationWaitTime, MAX_WAIT_LOCATION_AGE);
// Re-request location updates
mLocationClient.removeLocationUpdates(MyParentClassName.this);
mLocationClient.requestLocationUpdates(mLocationRequest, LocationSocialBaseScreen.this);
}
locationCheck.postDelayed(this, 1000);
}
};
#Override
public void onLocationChanged(Location location) {
lastLocationUpdate = System.currentTimeMillis();
}
Related
I have following requirement in my app.
1. I select some files & click Upload button. On clicking Upload button, the app exits. (I am enqueueing the request & finishing the activity).
2. Once the app exits, these files need to get synced to server in background.
3. Also after certain time duration (I have set 16min interval), in background, I need to check if there is unsynced data, sync it in background.
4. If user is offline, the unsynced data should get synced in background once network connectivity is established.
I have used WorkManager's Periodic Work Request to achieve this.
But on testing it on my Asus Zenfone3, I observed that :
1. If my device goes into sleep mode, the doWork() does not get execute after that.
If I turn off mobile data & then turn it on, the doWork() does not get executed immediately.
I have set 1 constraint : .setRequiredNetworkType(NetworkType.CONNECTED). It gets executed only after its interval is completed. I need immediate sync on network.
Also sometimes, clicking on upload button does not immd call doWork().
Is WorkManager (2.3.2) the right approach I am following. Any guideline to achieve above req. will be appreciated.
WorkManager is the preferred solution on Android for deferrable tasks. If you want an immediate action, you may want to look into implementing the logic you want in a Foreground Service.
Regarding what happens when your device goes into doze mode, WorkManager tries to minimize battery consumption and respect it. Again, if you need to have your tasks to be executed even when the device is supposed to go into doze mode, WorkManager is not the right solution. But you need to have a good reason to justify this behavior as it is going to have a negative impact on battery life.
To be able to understand what is happening with the Asus Zenfone3, it would be helpful to see how you enqueue the WorkRequest and WorkManager's logs.
If you read android documentation work manager is not a good way to schedule recurring tasks and when the device goes to sleep mode the thread work manager is working on is also put to sleep to conserve battery try using handler instead.
https://developer.android.com/reference/android/os/Handler
in the code i have posted i have used handler and firebase jobdispatcher to log user location on fixed interval of 30 seconds and it works even when device is in sleep mode
//file with handler and firebase job dispatcher
public class GetterService extends JobService {
private HandlerThread handlerThread = new HandlerThread("HandlerThread");
private Handler threadHandler;
int delay = 30*1000;
Runnable runnable;
LocationManager locationManager;
#Override
public boolean onStartJob(#NonNull com.firebase.jobdispatcher.JobParameters job) {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
handlerThread.start();
threadHandler = new Handler();
threadHandler.postDelayed(runnable= () -> {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
Location repeatedLocations = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Log.i("location logger", "getting Longitude as "+ repeatedLocations.getLongitude() +" getting Latitide as "+repeatedLocations.getLatitude());
threadHandler.postDelayed(runnable,delay);
},delay);
return false;
}
#Override
public boolean onStopJob(#NonNull com.firebase.jobdispatcher.JobParameters job) {
return false;
}
}
//file with location getter
public class LocationGetter extends AppCompatActivity implements LocationListener {
LocationManager locationManager;
#BindView(R.id.latitudeVal)
TextView latitudeVal;
#BindView(R.id.longitudeVal)
TextView longitudeVal;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 2);
} else {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 1, this);
}
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher
(new GooglePlayDriver(getApplicationContext()));
Job notificationJob = dispatcher.newJobBuilder()
.setService(GetterService.class)
.setRecurring(true).setTag("jid")
.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
.setTrigger(Trigger.executionWindow(1, 10000))
.setReplaceCurrent(false)
.build();
dispatcher.mustSchedule(notificationJob);
}
#OnClick(R.id.ping)
public void onPingClick() {
}
#Override
public void onLocationChanged(Location location) {
latitudeVal.setText("" + location.getLatitude() + "");
longitudeVal.setText("" + location.getLongitude() + "");
Log.i("dfjh", "" + location.getLongitude() + " " + location.getLatitude());
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
I have an AlarmManager that triggers every 30 minutes to an IntentService, and this intent service is to get the user's location each time. I have 2 ways of getting location: first it checks the getLastKnownLocation(), and if it was within the last 2 minutes it uses that, this part works perfectly.
The second way is if the last location was old or returns null, in which I want to get the new location once. For some reason this never calls onLocationChanged(). This results in my IntentService not returning coordinates much of the time, it only returns them if the getLastKnownLocation() was recent.
Here is my code for how it is set up, why is it that if I want to get a new location, it is never called? Check the comments in the code to see what is called and what is never called.
LocationListener locationListener;
LocationManager locationManager;
private final double MIN_COORD_DIFF = 0.0006;
public CoordinateAlarmReceiver(){
super("CoordinateAlarmReceiver");
}
#Override
protected void onHandleIntent(Intent intent) {
//THIS IS CALLED CORRECTLY
MyLog.i("coordinate alarm received");
locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
//if last location was in past 2 minutes, use that
if(lastLocation != null && lastLocation.getTime() > Calendar.getInstance().getTimeInMillis() - 2 * 60 * 1000) {
//THIS IS CALLED CORRECTLY
storeLocation(lastLocation);
MyLog.i("Last location was recent, using that");
}
else { //otherwise get new location
//THIS IS CALLED CORRECTLY
MyLog.i("Last location was old, getting new location");
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
//THIS IS NEVER CALLED
MyLog.i("Got new coordinates");
storeLocation(location);
locationManager.removeUpdates(this);
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
};
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
}
}
You should use a Service instead of a IntentService. The IntentService finish when complete the task however, the service is running and can listen the location change events. Try to use a service.
Hope it helps you!!
I have a background service which should track the user movement with his car, and send the data to my server. I have two variables for sending the location, either 60 seconds have passed or the user has moved 100 meters.
On my service here is how I start listening to locations:
mLocationRequest = LocationRequest.create();
mLocationRequest.setSmallestDisplacement(100);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(60 * 1000);
mLocationClient = new LocationClient(context, connectionCallbacks, onConnectionFailedListener);
mLocationClient.connect();
and the listeners:
ConnectionCallbacks connectionCallbacks = new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
mLocationClient.requestLocationUpdates(mLocationRequest, locationListenerAPI);
//process the last location, if available
Location loc = mLocationClient.getLastLocation();
if (loc != null) {
prepareLocation(loc);
}
}
#Override
public void onDisconnected() {
}
};
LocationListener locationListenerAPI= new LocationListener() {
#Override
public void onLocationChanged(Location location) {
//A location has been read, process it
prepareLocation(location);
}
};
So, while the service is running and I driver around with the car, onLocationChanged only fires a few times, it does not take into consideration either the time or meters to fire a location read. I have data connection and the GPS icon is visible on notification bar the whole time the service is running.
Any ideas why is not working ?
In the end, I just did it like this:
I've setInterval(5000) and setFastestInterval(5000) and then, for each read location, if distance between them is bigger than 100m, I send the location to my server. I gave up on using smallestDisplacement. This seem to work reliably.
Details:
I have a service that needs to do the following:
- listen constantly for GPS locations, with 50 meters as parameter (works fine) and send it to a server
- each 60 seconds, if no GPS location was read, start listening for a Network location and send it to the server
This may sound weird, but this is the project requirement. So the user is constantly being tracked using GPS. When he stops, or GPS is not locked, or is inside a building, every 60 seconds start a quick Network location read, and send this location to the server. As the service uses the same thread as the main app, each server update is done in its own thread. And another important thing: each location read should be sent one after another, so for instance if the user is driving, and multiple reads are done, each should be send to the server, after the previous one has been sent. That's why I decided to use ScheduledExecutorService as I can submit threads and they will be execute one after another.
Here is the code:
private ScheduledExecutorService scheduleTaskExecutor;
Handler locationHandler = new Handler();
private Location lastNetworkLocation;
#Override
public void onStartCommand() {
scheduleTaskExecutor = Executors.newSingleThreadScheduledExecutor();
//prepare to execute Network reading every 60 seconds
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
initNetworkReadings();
//usually a network location read is done almost instantly
//however after 5 seconds I check if a location has been read by the Network listener inside the locationRunnable
locationHandler.postDelayed(locationRunnable, 5000);
}
}
}, 60, 60, TimeUnit.SECONDS);
locationRunnable = new Runnable() {
#Override
public void run() {
if (lastNetworkLocation !=null){
//send location to the server if valid
}
lastNetworkLocation = null;
}
}
}
private void initNetworkReadings() {
locationManager.removeUpdates(locationListenerNetwork);
try {
isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
if (isGpsEnabled) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);
}
}
LocationListener locationListenerNetwork = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
lastNetworkLocation = location;
// stop listening for Network locations
locationManager.removeUpdates(locationListenerNetwork);
}
...
}
Each time I read a GPS location I add it to the threads queue as:
scheduleTaskExecutor.submit(new Runnable() {
#Override
public void run() {
updateLocationOnServer(readLocation);
}
});
The problem I have is that the Network location listener never gets onLocationChanged() called,when I use it like in the code above, in the Runnable. BUT if I add on service start, the initNetworkReadings(), I get onLocationChanged() fired right away. So I believe it has something to do with being used in scheduleAtFixedRate.
What do you think it could be the problem ? Anything bad in the way I thought of the workflow ?
What if you try to set up your repitition with a Handler and a Runnable instead of scheduleAtFixedRate()?
Handler h = new Handler();
Runnable run = new Runnable(){
//Do your repeititive work here!
h.postDelayed(run, 60 * 1000);
};
h.post(run);
Same problem,
I have a background service that detect locations.
If network is avaible, it uses networks, otherwise it uses gps.
It works well with many smartphones
( nexus s 4.1, galaxy nexus 4.2, galaxy note) , but with Galaxy s3 jb (4.1) network location never rises any location.
Same code with gps locations, works just fine.
I'm using the code shown below to get an updated value for location every time a button is clicked. When my activity is resumed I get an update every second, so that when I call getLastKnownLocation I expect to have a location that have been updated in the last second.
Is that the correct way to do that?
I would expect the onLocationChanged event to be triggered every time I execute a 'geo fix' command (or max after 1s since I request update every 1s), but it's only triggered the first time. Why?
Any help/suggestion welcome!
Thanks
package org.digitalfarm.atable;
...
public class Atable extends Activity {
private Button mSearchButton;
private TextView mytext;
private LocationManager locationManager;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSearchButton = (Button)this.findViewById(R.id.button);
mytext = (TextView) findViewById(R.id.dude);
locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
final Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
mSearchButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
String provider = locationManager.getBestProvider(criteria, true);
Location location = locationManager.getLastKnownLocation(provider);
}
});
}
//Start a location listener
LocationListener onLocationChange=new LocationListener() {
public void onLocationChanged(Location loc) {
//sets and displays the lat/long when a location is provided
String latlong = "Lat: " + loc.getLatitude() + " Long: " + loc.getLongitude();
mytext.setText(latlong);
}
public void onProviderDisabled(String provider) {
// required for interface, not used
}
public void onProviderEnabled(String provider) {
// required for interface, not used
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
// required for interface, not used
}
};
//pauses listener while app is inactive
#Override
public void onPause() {
super.onPause();
locationManager.removeUpdates(onLocationChange);
}
//reactivates listener when app is resumed
#Override
public void onResume() {
super.onResume();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000,100.0f,onLocationChange);
}
}
The time, specified in requestLocationUpdates is the shortest possible time interval for which a maximum of one update will occur. So you have registered for at most one update every second, but the actual update may take more that that to occur, and, furthermore, no guarantee is given for that.
From the documentation:
minTime - the minimum time interval for notifications, in milliseconds. This field is only used as a hint to conserve power, and actual time between location updates may be greater or lesser than this value.
Try to set that time interval to 0 and if there are no other problems (although it all seems fine to me), you should start getting the updates "as frequently as possible".