Reputation: 10016
Android 4.3 on Moto G, Android 4.4.2 on Nexus 7 2012, Android 4.4.2 on Nexus 5. Android Studio 0.4.
I don't want to receive regular location updates, I just want an accurate location when the user presses a button.
I have followed this example: https://developer.android.com/training/location/retrieve-current.html
In manifest file:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
I check that Play Services are available using GooglePlayServicesUtil.isGooglePlayServicesAvailable
In main activity:
//in activity onCreate method
mLocationClient = new LocationClient(this, this, this);
@Override
protected void onStart() {
mLocationClient.connect();
super.onStart();
}
@Override
protected void onStop() {
mLocationClient.disconnect();
super.onStop();
}
//in button onclick method
mCurrentLocation = mLocationClient.getLastLocation();
I have no SIM card. If I enable Wifi then sometimes I get an accurate location. Other times mCurrentLocation is null.
If I disable Wifi then mCurrentLocation is always null.
I am testing outside in several locations always with a clear view of the sky. I waited three minutes in each location.
I never see the GPS icon appear on the Android notification bar at the top of the screen.
I have these location settings:
A GPS Test app manages to use GPS successfully indoors on the same device with Wi-Fi disabled so GPS is working:
Registering for location updates, as at https://developer.android.com/training/location/receive-location-updates.html, doesn't work either. Registered method never called.
What am I doing wrong?
Upvotes: 39
Views: 64770
Reputation: 311
What's in the OnConnected
method?
In this method you should create the LocationRequest
object with PRIORITY_HIGH_ACCURACY
priority.
@Override
public void onConnected(Bundle dataBundle) {
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationClient.requestLocationUpdates(mLocationRequest, fusedListener);
}
Upvotes: 1
Reputation: 1776
Location provider won't wake up GPS until some of clients ask (subscribe) for high precision location (as described in examples given by other users). GPS test app doesn't use location provider but uses old "direct" way of obtaining location.
Also there is expiry mechanism, which removes information about last location after some time if it is believed to be stale.
Summing up all above it is really possible that LP(Location Provider) has nothing to give you.
Upvotes: 5
Reputation: 3563
Without a sim card the coarse location provider
has no way to know the coarse position, unless it is able to find a WiFi network that has been mapped by Google.
Requesting the last known location may result in a outdated location, and as such is rather useless. I guess this is the position that was recorded the last time some app requested a location update.
I use the following code to get a recent location:
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
criteria.setAltitudeRequired(false);
criteria.setSpeedRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(false);
final LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
....
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location lastKnownLocation) {
....
}
// rest of interface
}
manager.requestSingleUpdate(criteria, listener, null);
The last call ensures that we request the current location, not the location it was able to find an unknown amount of time before.
You might try to change it to Criteria.ACCURACY_FINE
in order to get the GPS fired up. Be aware that if the GPS didn't have a fix for quite some while it may take more than several minutes before it is actually capable of getting a fix. I'd expect in the mean time that you'd see the GPS icon indicating it is waiting for a fix.
Upvotes: 1
Reputation: 3885
The problem is with getLastLocation()
because it uses a cached location. I had the same problem as I also tried to use this simple approach. Since, I have switched to listening to updates (and stopping after 1st successfull update automatically).
This is my code that works.
First, the check for availability in Application (not essential, can be in Activity and without keeping of result):
public class MainApp extends Application {
public static enum PlayServices {
NOT_CHECKED, AVAILABLE, UNAVAILABLE
};
public static PlayServices mPlayServices = PlayServices.NOT_CHECKED;
@Override
public void onCreate() {
super.onCreate();
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;
}
}
}
Then, on to the Activity:
public class MainActivity extends SherlockFragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {
In its onCreate()
:
if (MainApp.mPlayServices != MainApp.PlayServices.UNAVAILABLE) {
mLocationClient = new LocationClient(this, this, this);
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(5000);
mLocationRequest.setNumUpdates(1);
mLocationRequest.setFastestInterval(1000);
mUpdatesRequested = false;
MainApp.prefs.edit().putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested)
.commit();
}
The rest of the MainActivity
class:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(this.getClass().getSimpleName(), "onActivityResult(" + requestCode + ", " + resultCode
+ ")");
// Decide what to do based on the original request code
switch (requestCode) {
case MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST:
/*
* If the result code is Activity.RESULT_OK, try
* to connect again
*/
switch (resultCode) {
case Activity.RESULT_OK:
// here we want to initiate location requests!
mLocationClient = new LocationClient(this, this, this);
break;
}
break;
}
}
@Override
public void onConnected(Bundle dataBundle) {
Log.d(this.getClass().getSimpleName(), "onConnected()");
Log.d(this.getClass().getSimpleName(), "Google Play Services are available.");
MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;
if (!mUpdatesRequested) {
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
boolean gps_enabled = false;
try {
gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
} catch (Exception ex) {
}
boolean network_enabled = false;
try {
network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
}
// don't start listeners if no provider is enabled
MainApp.locEnabled = gps_enabled || network_enabled;
if (!MainApp.locEnabled) {
// we have access to PlayServices, but user has disabled location visibility --> alert him
alertLocationOff();
} else {
mLocationClient.requestLocationUpdates(mLocationRequest, this);
mUpdatesRequested = true;
}
}
}
@Override
public void onDisconnected() {
Log.d(this.getClass().getSimpleName(), "onDisconnected()");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.d(this.getClass().getSimpleName(), "onConnectionFailed()");
Log.d(this.getClass().getSimpleName(), "Google Play Services not available.");
MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;
/*
* 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()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(this,
MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (IntentSender.SendIntentException e) {
// Log the error
e.printStackTrace();
}
} else {
/*
* If no resolution is available, display a dialog to the
* user with the error.
*/
GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), this, 0).show();
}
}
@SuppressLint("NewApi")
@Override
public void onLocationChanged(Location location) {
Log.d(this.getClass().getSimpleName(), "onLocationChanged(), location=" + location);
if (location != null) {
boolean present = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
present = Geocoder.isPresent();
}
if (present) {
(new ExtractLocationTask(this)).execute(location);
} else {
Log.e(this.getClass().getSimpleName(), "Geocoder not present");
MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;
}
}
}
private class ExtractLocationTask extends AsyncTask<Location, Void, Boolean> {
Context mContext;
public ExtractLocationTask(Context context) {
super();
mContext = context;
}
@Override
protected Boolean doInBackground(Location... params) {
Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPreExecute()");
boolean found = false;
try {
Geocoder geoCoder_local = new Geocoder(mContext, Locale.getDefault());
Geocoder geoCoder_en = new Geocoder(mContext, Locale.ENGLISH);
List<Address> addresses_local = geoCoder_local.getFromLocation(params[0].getLatitude(),
params[0].getLongitude(), 10);
List<Address> addresses_en = geoCoder_en.getFromLocation(params[0].getLatitude(),
params[0].getLongitude(), 10);
if (addresses_local != null && addresses_local.size() > 0) {
// do what you want with location info here
// based on mLocationRequest.setNumUpdates(1), no need to call
// removeLocationUpdates()
MainApp.locEnabled = true;
mUpdatesRequested = false;
MainApp.prefs.edit()
.putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested).commit();
found = true;
}
} catch (IOException e) {
Log.e(this.getClass().getSimpleName(), "Exception: ", e);
}
return found;
}
@Override
protected void onPostExecute(Boolean found) {
Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPostExecute()");
if (found) {
// update UI etc.
} else if (!mUpdatesReRequested) {
mLocationClient.requestLocationUpdates(mLocationRequest, (LocationListener) mContext);
mUpdatesRequested = true;
mUpdatesReRequested = true;
}
}
}
I hope this will help you get it to work!
Upvotes: 15
Reputation: 1039
All you need to do is to add a priority property to the request object like this.
public void onConnected(Bundle arg0)
{
locationrequest = LocationRequest.create();
locationrequest.setInterval(1000);
locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, this);
}
Maybe use a boolean variable to let the user have options to force GPS like
boolean forceGPS=false;
.
.
.//make the user choose to change the value of the boolean
.
.
public void onConnected(Bundle arg0)
{
locationrequest = LocationRequest.create();
locationrequest.setInterval(1000);
if(forceGPS==true)
{
locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
locationclient.requestLocationUpdates(locationrequest, this);
}
Upvotes: 1
Reputation: 10016
I solved it. The problem was that "Let Google apps access your location" was turned off:
When I turn it on I get GPS readings and when it's off I don't.
I had left it off for two reasons:
I'm developing an app to be used to lots of devices at a company and I want minimum manual configuration to be necessary
The screen says clearly "This setting affects Google apps only." I know that Play Services is Google software but I didn't think Google would expect an end user to understand that.
Then I got the Android 4.4.2 update and the location settings page has changed. It appears that I can have Google Location Reporting turned off and still get GPS readings from the fused location provider:
So maybe Google realised that the setting was confusing and improved it. Either way, I'd have saved a lot of time if I'd got 4.4.2 a few days ago.
Upvotes: 16
Reputation: 73753
According to the docs
This method provides a simplified way to get location. It is particularly well suited for applications that do not require an accurate location and that do not want to maintain extra logic for location updates.
so it may or may not return a highly accurate location.
GPS can take a while to lock on so calling getLastLocation
may or may not return a location
you are better off requesting location updates then after you get a location just stop requesting location updates.
Also looking at you code you provided are you waiting for the LocationClient
to connect before trying to get a location? That will certainly give you a null location since it is not connected to get the location yet.
what you should be doing is in your onConnected
get the last location there, example
public void onConnected(Bundle connectionHint) {
Location location = mLocationClient.getLastLocation();
}
as it says in that example onConnected is 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
Upvotes: 1
Reputation: 107
I prefer to use Service that implements LocationListener
to get current near-accurate location. I normally take following steps:
LocationManager
and call requestLocationUpdates
for both GPS_PROVIDER
and NETWORK_PROVIDER
in onCreate()
getLastKnownLocation
for both GPS_PROVIDER
and NETWORK_PROVIDER
and use getTime()
to know last newer location.onLocationChanged
is called I compare newly updated location with last best location (if any) and check accuracy level. locationManager.removeUpdates(this);
onDestroy
)EDIT:
OK...Above method was using Location API, below I have coded using Fused Provider (GooglePlayServices). I have tested on my Nexus 5 (Android 4.4.2), NO SIM, NO WIFI...and I'm getting results.
Edit 2: I have also tested on Android 2.3.3 LG Optimus (No SIM & Wifi) and it took about 5 min to get lock (using Fused Provided), but I was getting location icon instantly.
public class HomeActivity extends FragmentActivity implements
ActionBar.OnNavigationListener,
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {
private LocationClient locationClient;
private LocationRequest locationRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// ...
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 onConnected(Bundle connectionHint) {
if (locationClient != null && locationClient.isConnected()) {
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(100);
locationClient.requestLocationUpdates(locationRequest, this);
}
}
@Override
public void onLocationChanged(Location location) {
try {
if (location != null) {
LatLng gps = new LatLng(location.getLatitude(), location.getLongitude());
Log.v("JD", "My Location: " + gps.toString());
}
} catch (Exception e) {
Log.d("JD",
"onLocationChanged", e);
}
}
}
Upvotes: 0
Reputation: 6410
Since no one has shared this link, I found this to be the most helpful documentation http://developer.android.com/guide/topics/location/strategies.html
"You might expect that the most recent location fix is the most accurate. However, because the accuracy of a location fix varies, the most recent fix is not always the best. You should include logic for choosing location fixes based on several criteria. The criteria also varies depending on the use-cases of the application and field testing."
Your best bet is to keep a location listener going as long as the activity is in the foreground and select the most accurate cached location when the button is pressed. You may need to show a spinner or something and disable the button while it waits for an accurate measurement to show up.
Upvotes: 0
Reputation: 11756
There are two things going on here that are causing you to sometimes get null
, and sometimes get a location.
First, you're creating a new instance of the LocationClient
in the onClick
method, which is not the same instance you're calling connect()
on in onStart()
. This will create unpredictable behavior where sometimes the client will have enough time to connect before returning a result from LocationClient.getLastLocation()
, and sometimes it won't.
Second, you should guard the call to LocationClient.getLastLocation()
with a call to LocationClient.isConnected()
. It says the following in the LocationClient.isConnected()
docs:
https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#isConnected()
Checks if the client is currently connected to the service, so that requests to other methods will succeed. Applications should guard client actions caused by the user with a call to this method.
Since the user is triggering the onClick()
code by tapping on the button, you should call this method before trying to get the last location.
So, your code should look like this:
LocationClient mLocationClient;
Location mCurrentLocation;
@Override
protected void onCreate() {
...
mLocationClient = new LocationClient(this, this, this);
...
}
@Override
protected void onStart() {
mLocationClient.connect();
super.onStart();
}
@Override
protected void onStop() {
mLocationClient.disconnect();
super.onStop();
}
public void onClick(View v) {
...
if (mLocationClient.isConnected()) {
// You should get a valid location here
mCurrentLocation = mLocationClient.getLastLocation();
}
}
This should give the LocationClient
a long enough time to connect and give you a valid location, which should hopefully include GPS data.
Upvotes: 0
Reputation: 98
Try this:
public final static int MINACCURACY = 50;
private LocationManager lm;
private LocationListener listener;
private void foundLoc(double x, double y) {
// do something with x,y
Log.v("DEBUG", x + "," + y);
}
public void findMe(View v) {
lm = (LocationManager) getSystemService(LOCATION_SERVICE);
listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
if(location.getAccuracy() < MINACCURACY) {
foundLoc(location.getLatitude(), location.getLongitude());
lm.removeUpdates(listener);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
};
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 100, 0, listener);
}
MINACCURACY
is in meters. This way, on button press (which calls the findMe method), the GPS is enabled, your location is found with a minimum accuracy, the method foundLoc gets the data and the listener terminates (which, in turn, disables GPS).
Upvotes: 0
Reputation: 1451
I think your are testing your application in Indoor , it doesn't works..
and code flow..
public void setUpLocationClientIfNeeded() {
createLocReq();
if (mLocationClient == null) {
mLocationClient = new LocationClient(getApplicationContext(), this, // ConnectionCallbacks
this); // OnConnectionFailedListener
}
}
private void createLocReq() {
if (mLocationRequest == null) {
// Create a new global location parameters object
mLocationRequest = LocationRequest.create();
// Set the update interval
mLocationRequest.setInterval(LocationServices.UPDATE_INTERVAL_IN_MILLISECONDS);
// Use high accuracy
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the interval ceiling to one minute
mLocationRequest.setFastestInterval(LocationServices.FAST_INTERVAL_CEILING_IN_MILLISECONDS);
mLocationClient = new LocationClient(getApplicationContext(), this, this);
mLocationClient.connect();
}
}
public void updateLocation(Location location) {
if (lastTrackedLat == 0.0) {
lastTrackedLat = location.getLatitude();
}
if (lastTrackedLng == 0.0) {
lastTrackedLng = location.getLongitude();
}
currentLat = location.getLatitude();
currentLng = location.getLongitude();
}
@Override
public void onLocationChanged(Location location) {
if (location != null) {
this.location = location;
updateLocation(location);
}
}
public Location getLocation() {
// mLocationClient.getLastLocation();
return location;
}
Upvotes: 2