I am creating a simple location app that stores the user's location upon command. I am attempting to use both the GPS provider and Network provider to establish the user's location.
The process is achieved using Asynctask and works likes this:
Checks last known GPS location and Network location. If locations are within acceptable parameters the thread finishes.
If last know locations are unacceptable, updates are requested of both GPS and Network.
Once 4 network locations have been found or either 3 gps locations have been found the thread finishes.
A dialog box is displayed when waiting for a location fix.
PROBLEM: GPS seems to be updating whereas Network does not. I have tried solely updating NETWORK_PROVIDER, waited for over an hour with no results. I have tried with both WIFI on and off.
The code for the GetGPS class:
public class GetGPS extends AsyncTask<String, Void, String> implements LocationListener {
private ProgressDialog progDialog;
public double[] DataArray = {0.0,0.0,0.0};
private Integer NetworkCount = 0;
private Integer GPSCount = 0;
private LocationManager mlocManager;
private Context mContext;
private Activity mActivity;
final LocationListener locationListener = (LocationListener) this;
private boolean HasCompleted = false;
public GetGPS(Context context, Activity activity) {
mContext = context;
mActivity = activity;
}
#Override
public void onPreExecute() {
Log.v(getClass().getName(),"PreExecute");
mlocManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
//Stop any current GPS updates
mlocManager.removeUpdates((LocationListener) this);
//Get current system time in millis
long currentTime = System.currentTimeMillis();
//Check last known GPS location and corresponding accuracy and time
String GPSlocationProvider = LocationManager.GPS_PROVIDER;
Location lastKnownGPSLocation = mlocManager.getLastKnownLocation(GPSlocationProvider);
if (lastKnownGPSLocation != null) {
long lastGPStime = lastKnownGPSLocation.getTime();
long timeDifference = currentTime - lastGPStime;
float lastGPSaccuracy = lastKnownGPSLocation.getAccuracy();
Log.v(getClass().getName(),"Found last known GPS! GPS time diff:"+timeDifference+", Accuracy:"+lastGPSaccuracy);
if ((lastGPSaccuracy < 50) && (timeDifference < 180000)) {
Log.v(getClass().getName(),"Acceptable last known GPS location!");
DataArray[0] = (double) lastKnownGPSLocation.getLatitude();
DataArray[1] = (double) lastKnownGPSLocation.getLongitude();
DataArray[2] = (double) lastKnownGPSLocation.getAccuracy();
HasCompleted = true;
}
else {
Log.v(getClass().getName(),"Last known location GPS too inaccurate!");
}
}
else {
Log.v(getClass().getName(),"No previous GPS locations!");
}
//Check last known network location and corresponding accuracy and time
String NetworklocationProvider = LocationManager.NETWORK_PROVIDER;
Location lastKnownNetworkLocation = mlocManager.getLastKnownLocation(NetworklocationProvider);
if (lastKnownNetworkLocation != null) {
long lastNetworktime = lastKnownNetworkLocation.getTime();
long timeDifference = currentTime - lastNetworktime;
float lastNetworkaccuracy = lastKnownNetworkLocation.getAccuracy();
Log.v(getClass().getName(),"Found last known Network location! Time diff:"+timeDifference+", Accuracy:"+lastNetworkaccuracy);
if ((lastNetworkaccuracy < 50) && (timeDifference < 180000)) {
Log.v(getClass().getName(),"Acceptable last known Network location!");
DataArray[0] = (double) lastKnownNetworkLocation.getLatitude();
DataArray[1] = (double) lastKnownNetworkLocation.getLongitude();
DataArray[2] = (double) lastKnownNetworkLocation.getAccuracy();
HasCompleted = true;
}
else {
Log.v(getClass().getName(),"Last known Network location too inaccurate!");
}
}
else {
Log.v(getClass().getName(),"No previous Network locations!");
}
// Request both providers to update
mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,(LocationListener) this);
mlocManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,(LocationListener) this);
//Display dialog box
progDialog = new ProgressDialog(mActivity);
progDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Stop", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Log.v(getClass().getName(),"ProgDialog cancelled");
mlocManager.removeUpdates(locationListener);
GetGPS.this.cancel(true);
}
});
progDialog.setMessage("Acquiring location..");
progDialog.setIndeterminate(true);
progDialog.setCancelable(false);
progDialog.show();
Log.v(getClass().getName(),"Show progdialog");
}
#Override
public String doInBackground(String... params) {
while (HasCompleted == false) {
}
Log.v(getClass().getName(),"doInBackground completed");
return "Success";
}
#Override
public void onPostExecute(String result) {
Log.v(getClass().getName(),"onPostExecute");
//Destroy Dialog box
progDialog.dismiss();
//TODO: Deal with data
}
public void onLocationChanged(Location location) {
Log.v(getClass().getName(),"onLocationChanged, Provider:'"+location.getProvider()+"'");
// Check if from Network
if (location.getProvider().equals("network")){
Log.v(getClass().getName(),"Network location Changed");
NetworkCount = NetworkCount + 1;
if (NetworkCount > 3) {
DataArray[0] = (double) location.getLatitude();
DataArray[1] = (double) location.getLongitude();
DataArray[2] = (double) location.getAccuracy();
Log.v(getClass().getName(),"Location stored from Network!");
Log.v("data array 0","" + DataArray[0]);
mlocManager.removeUpdates((LocationListener) this);
HasCompleted = true;
}
else {
Log.v(getClass().getName(),"Network location count:"+NetworkCount);
}
}
//Check if from GPS
if (location.getProvider().equals("gps")){
Log.v(getClass().getName(),"GPS location Changed");
GPSCount = GPSCount + 1;
if (GPSCount > 2) {
DataArray[0] = (double) location.getLatitude();
DataArray[1] = (double) location.getLongitude();
DataArray[2] = (double) location.getAccuracy();
Log.v(getClass().getName(),"Location stored from GPS!");
//Log.v("data array 0","" + DataArray[0]);
mlocManager.removeUpdates((LocationListener) this);
HasCompleted = true;
}
else {
Log.v(getClass().getName(),"GPS location count:"+GPSCount);
}
}
}
#Override
public void onProviderDisabled(String provider) {
Log.v("OnProviderDisabled", "OnProviderDisabled:"+provider);
}
#Override
public void onProviderEnabled(String provider) {
Log.v("onProviderEnabled", "onProviderEnabled:"+provider);
}
#Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
Log.v("onStatusChanged", "onStatusChanged, provider:"+provider+", Status:"+status);
}
}
The GetGPS class is called in the following manner:
Context mContext = getActivity().getApplicationContext();
Activity mActivity = getActivity();
GetGPS getgps = new GetGPS(mContext, mActivity);
getgps.execute();
Any help as to where i'm going wrong? Thank you in advance forany help.
Also: Is there a simple way to implement a timeout for the Asynctask? I.e. If no location fix is found after a set time period, the thread stops.
About your first question, please check that both GPS and Network providers are enabled at your phone:
I'm also not sure if implementing LocationListener inside AsyncTask is the proper way, I tried to read about it and came across this. Try to read it and implement the solution he propose. (What I would do is create an independent location Singelton class which will have nethods such as stop and start and will be controlled from the AsyncTask).
At your doInBackground method, I would change the While loop, and just put a Thread.sleep(50) inside. to prevent busy waiting.
About your second question, the simplest thing to do here is check the time that has passed in the while loop of doInBackground and stop the thread there is necessary.
I am developing an application which lists restaurants closest to the user. When the refresh button is clicked, it lists the restaurants for the user's current location. I am using the Location Manager and requesting updates only when the activity comes to foreground(onResume) to avoid the constant usage of the battery. When the app goes in onPause(), the location updates are stopped. It works fine on the emulator when I pass the updates through the terminal.
Problem:
When I physically change my location (say drive 5 miles away) and I open my app and the activity to show the nearest restaurants, it takes long time(4-5 minutes) for the location to refresh and till then the app continues to show the restaurants for the previous location. But if I physically change my location and access Google Maps and then open my restaurant application, then it works instantaneously. I have ensured that the GPS is switched on. What can I do to get the Location Manager to kick in and refresh the location as soon as I open my activity?
Thank you in advance!
package com.mortley.android.restaurantsaver;
public class NearbyRestaurantActivity extends ListActivity implements OnClickListener, LocationListener{
private Button refreshButton, searchRestaurants;
ImageButton goToSearch;
private double[] lastKnownLocation;
private EditText locationEditText;
private LocationManager locManager;
private LocationListener locListener;
private boolean gps_enabled = false;
private boolean network_enabled = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nearbyrestaurants);
refreshButton = (Button)findViewById(R.id.reloadButton);
refreshButton.setOnClickListener(this);
searchRestaurants = (Button)findViewById(R.id.searchButton);
searchRestaurants.setOnClickListener(this);
goToSearch = (ImageButton)findViewById(R.id.goLocationButton);
goToSearch.setOnClickListener(this);
locationEditText = (EditText)findViewById(R.id.addressTextBox);
locationEditText.setVisibility(View.GONE);
goToSearch.setVisibility(View.GONE);
locManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);//??
locManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 1000, 100, this);
//checks network connectivity
boolean checkConnection = isNetworkAvailable();
if(!checkConnection){
Toast.makeText(getApplicationContext(), "Check your Network Connectivity", Toast.LENGTH_LONG).show();
}
if(checkConnection){
//sets current location parameters for the user
lastKnownLocation = RestaurantHelper.getLastKnownLocation(this);
//Log.v("NearbyRestaurantActivity", "This"+this);
RestaurantApplication application = (RestaurantApplication) this.getApplication();
RestaurantAdapter restaurantAdapter = new RestaurantAdapter(this, R.layout.restaurantrow, R.id.label,new ArrayList<RestaurantReference>());
restaurantAdapter.setLastKnownLocation(lastKnownLocation);
//set a global variable for the RestaurantAdapter in the RestaurantApplication class.
application.setRestaurantAdapter(restaurantAdapter);
//Set the adapter first and then update it when the RestaurantHttpAsyncTask makes a web service call.
setListAdapter(restaurantAdapter);
//Make a webservice call in a different thread passing Keyword for URL as a string array.
RestaurantHttpAsyncTask m_progressTask;
String[] keywords = {"", "american", "asian", "italian","mexican"};
//String[] keywords = {"indian"};
m_progressTask = new RestaurantHttpAsyncTask(NearbyRestaurantActivity.this, keywords);
m_progressTask.setRestaurantAdapter(restaurantAdapter);
m_progressTask.execute();
}
}
#Override
public void onClick(View v) {
//Refresh button helps to refresh the restaurant list on location change. Again it makes a call to the webservice using Async Task
if(v.getId() == refreshButton.getId() ){
try {
gps_enabled = locManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
network_enabled = locManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch (Exception ex) {
ex.printStackTrace();
}
// don't start listeners if no provider is enabled
if (!gps_enabled && !network_enabled) {
Toast.makeText(getApplicationContext(), "Sorry, Location is not determined. Please enable your Network Providers.", Toast.LENGTH_LONG).show();
}
//check network connectivity before refresh
boolean checkConnection = isNetworkAvailable();
if(!checkConnection){
Toast.makeText(getApplicationContext(), "Check your Network Connectivity", Toast.LENGTH_LONG).show();
}
if(checkConnection){
RestaurantApplication application = (RestaurantApplication) this.getApplication();
RestaurantAdapter restaurantAdapter = new RestaurantAdapter(this, R.layout.restaurantrow, R.id.label, new ArrayList<RestaurantReference>());
restaurantAdapter.setLastKnownLocation(lastKnownLocation);
//set a global variable for the RestaurantAdapter in the RestaurantApplication class.
application.setRestaurantAdapter(restaurantAdapter);
//Set the adapter first and then update it when the RestaurantHttpAsyncTask makes a web service call.
setListAdapter(restaurantAdapter);
//Make a webservice call in a different thread passing Keyword for URL as a string array.
RestaurantHttpAsyncTask m_progressTask, m_progressTask1;
String[] keywords = {"", "american", "asian", "italian","mexican", "chinese", "indian"};
//String[] keywords = {"Chinese"};
m_progressTask = new RestaurantHttpAsyncTask(NearbyRestaurantActivity.this, keywords);
m_progressTask.setRestaurantAdapter(restaurantAdapter);
m_progressTask.execute();
}
}
if(v.getId() == goToSearch.getId() ){
Activity child = this;
while(child.getParent() != null){
child = child.getParent();
}
TabGroup1Activity parent = (TabGroup1Activity)getParent();
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(locationEditText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
//changes ** restaurantAdapter to RestaurantAdapter1 to test & application to application1
RestaurantApplication application1 = (RestaurantApplication) this.getApplication();
RestaurantAdapter restaurantAdapter1 = new RestaurantAdapter(this, R.layout.restaurantrow, R.id.label, new ArrayList<RestaurantReference>());
restaurantAdapter1.setLastKnownLocation(lastKnownLocation);
//set a global variable for the RestaurantAdapter in the RestaurantApplication class.
application1.setRestaurantAdapter(restaurantAdapter1);
//Set the adapter first and then update it when the RestaurantHttpAsyncTask makes a web service call.
setListAdapter(restaurantAdapter1);
//Make a webservice call in a different thread passing Keyword for URL as a string array.
RestaurantHttpAsyncTaskTextSearch m_progressTask, m_progressTask1;
String keywords = locationEditText.getText().toString();
if(keywords.equals("")){
keywords = "Pizza in Palo Alto";
}
keywords = keywords.replaceAll(" ", "%20");
keywords = keywords.replaceAll(",", "%20");
m_progressTask = new RestaurantHttpAsyncTaskTextSearch (NearbyRestaurantActivity.this, keywords);
m_progressTask.setRestaurantAdapter(restaurantAdapter1);
m_progressTask.execute();
locationEditText.setVisibility(View.GONE);
goToSearch.setVisibility(View.GONE);
}
if(v.getId() == searchRestaurants.getId() ){
if(goToSearch.isShown() == true){
goToSearch.setVisibility(View.GONE);
locationEditText.setVisibility(View.GONE);
}
else if(goToSearch.isShown() == false){
//check network connectivity before refresh
boolean checkConnection = isNetworkAvailable();
if(!checkConnection){
Toast.makeText(getApplicationContext(), "Check your Network Connectivity", Toast.LENGTH_LONG).show();
}
if(checkConnection){
goToSearch.setVisibility(View.VISIBLE);
locationEditText.setVisibility(View.VISIBLE);
}
}
}
}
//Method to check network connectivity
public boolean isNetworkAvailable() {
ConnectivityManager connectivityManager
= (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting()) {
//Log.d("network", "Network available:true");
return true;
} else {
//Log.d("network", "Network available:false");
return false;
}
}
#Override
protected void onResume() {
super.onResume();
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 50, 100, this);
//Log.v("NearbyRestaurantActivity", "In OnResume()");
}
#Override
protected void onPause() {
super.onPause();
locManager.removeUpdates(this);
//Log.v("NearbyRestaurantActivity", "In onPause()");
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
}
#Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
}
public class RestaurantHelper {
public static double[] getLastKnownLocation(Activity activity){
double lat = 0.0;
double lon = 0.0;
LocationManager lm = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
Location location = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(location == null){
lat = 0.0;
lon = 0.0;
}
else{
//Log.v("Latitude", Double.toString(location.getLatitude()));
//Log.v("Longitude", Double.toString(location.getLongitude()));
lat = location.getLatitude();
lon = location.getLongitude();
}
return new double[]{lat,lon};
}
}
The primary reason why you aren't getting updated location information quickly is that you're relying on the NETWORK_PROVIDER in the RestaurantHelper.getLastKnownLocation() method, but registering a LocationListener for the GPS_PROVIDER in onCreate().
So, this code in RestaurantHelper.getLastKnownLocation():
Location location = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
...should be changed to:
Location location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
In theory, this should then give you the latest GPS location, which should have been refreshed when you register the listener. Conversely, you could also change to listening to the NETWORK_PROVIDER in onCreate() and leave RestaurantHelper.getLastKnownLocation() as is. It depends on your accuracy requirement - if you want high accuracy locations to return the nearest location to the nearest building level (e.g., 5-30m), you should use the GPS_PROVIDER. But, if you can live with coarser accuracy, the NETWORK_PROVIDER typically returns a new location much faster than GPS, especially if you're indoors, and sometimes this can be fairly accurate if derived from WiFi.
Another approach would be to listen to both GPS_PROVIDER and NETWORK_PROVIDER by registering both via two requestLocationUpdates() lines in onCreate(), and then checking to see most recent timestamp on the Location from lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); and lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);, and using the one that was updated more recently.
I would also recommend the following changes to make your code reliable on a large number of Android devices:
Specify the requestLocationUpdates() minDistance parameter as 0 when listening for GPS or NETWORK location updates - the minDistance parameter has a history of being unreliable and unpredictable in the way its interpreted by OEMs, until Android 4.1.
Switch to the new Fused Location Provider - this should be much more reliable when calling the getLastKnownLocation() method than the Android framework location APIs, and more consistent across different devices. Note that this relies on Google Play Services SDK, which is only available on Android 2.2 and higher.
I have 2 advice for you
LocationClient video, is the new way of doing location stuff. It has improvements over the LocationManager that can be a pain to manage and develop.
If you need to use LocationManager, you must know that requestLocationUpdates is buggy (very buggy). Since all its implementations on custom hardware differ. There is a hack/workaround that works. Before you call requestLocationUpdates, just kick start it with the following
Code :
HomeScreen.getLocationManager().requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 0, 0, new LocationListener() {
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
}
#Override
public void onLocationChanged(final Location location) {
}
});
requestLocationUpdates is buggy. Use network provider always to trigger onLocationChanged(...)
locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 100, this)
only after using network provider use gps provider back to back:
locManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 1000, 100, this)
do not forget to check if gps is enabled or not before requesting location update by gps.
The problem:
Getting the user's current location within a threshold ASAP and at the same time conserve battery.
Why the problem is a problem:
First off, android has two providers; network and GPS. Sometimes network is better and sometimes the GPS is better.
By "better" I mean speed vs. accuracy ratio.
I'm willing to sacrifice a few meters in accuracy if I can get the location almost instant and without turning on the GPS.
Secondly, if you request updates for location changes nothing is sent if the current location is stable.
Google has an example of determining the "best" location here: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
But I think it's no where near as good as it should/could be.
I'm kind of confused why google hasn't a normalized API for location, the developer shouldn't have to care where the location is from, you should just specify what you want and the phone should choose for you.
What I need help with:
I need to find a good way to determine the "best" location, maybe though some heuristic or maybe through some 3rd party library.
This does not mean determine the best provider!
I'm probably gonna use all providers and picking the best of them.
Background of the app:
The app will collect the user's location at a fixed interval (let say every 10 minutes or so) and send it to a server.
The app should conserve as much battery as possible and the location should have X (50-100?) meters accuracy.
The goal is to later be able to plot the user's path during the day on a map so I need sufficient accuracy for that.
Misc:
What do you think are reasonable values on desired and accepted accuracies?
I've been using 100m as accepted and 30m as desired, is this to much to ask?
I'd like to be able to plot the user's path on a map later.
Is 100m for desired and 500m for accepted better?
Also, right now I have the GPS on for a maximum of 60 seconds per location update, is this too short to get a location if you're indoors with an accuracy of maybe 200m?
This is my current code, any feedback is appreciated (apart from the lack of error checking which is TODO):
protected void runTask() {
final LocationManager locationManager = (LocationManager) context
.getSystemService(Context.LOCATION_SERVICE);
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER));
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
if (getLocationQuality(bestLocation) != LocationQuality.GOOD) {
Looper.prepare();
setLooper(Looper.myLooper());
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateBestLocation(location);
if (getLocationQuality(bestLocation) != LocationQuality.GOOD)
return;
// We're done
Looper l = getLooper();
if (l != null) l.quit();
}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
public void onStatusChanged(String provider, int status,
Bundle extras) {
// TODO Auto-generated method stub
Log.i("LocationCollector", "Fail");
Looper l = getLooper();
if (l != null) l.quit();
}
};
// Register the listener with the Location Manager to receive
// location updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 1, locationListener,
Looper.myLooper());
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
Timer t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
Looper l = getLooper();
if (l != null) l.quit();
// Log.i("LocationCollector",
// "Stopping collector due to timeout");
}
}, MAX_POLLING_TIME);
Looper.loop();
t.cancel();
locationManager.removeUpdates(locationListener);
setLooper(null);
}
if (getLocationQuality(bestLocation) != LocationQuality.BAD)
sendUpdate(locationToString(bestLocation));
else Log.w("LocationCollector", "Failed to get a location");
}
private enum LocationQuality {
BAD, ACCEPTED, GOOD;
public String toString() {
if (this == GOOD) return "Good";
else if (this == ACCEPTED) return "Accepted";
else return "Bad";
}
}
private LocationQuality getLocationQuality(Location location) {
if (location == null) return LocationQuality.BAD;
if (!location.hasAccuracy()) return LocationQuality.BAD;
long currentTime = System.currentTimeMillis();
if (currentTime - location.getTime() < MAX_AGE
&& location.getAccuracy() <= GOOD_ACCURACY)
return LocationQuality.GOOD;
if (location.getAccuracy() <= ACCEPTED_ACCURACY)
return LocationQuality.ACCEPTED;
return LocationQuality.BAD;
}
private synchronized void updateBestLocation(Location location) {
bestLocation = getBestLocation(location, bestLocation);
}
// Pretty much an unmodified version of googles example
protected Location getBestLocation(Location location,
Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return location;
}
if (location == null) return currentBestLocation;
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return location;
// If the new location is more than two minutes older, it must be
// worse
} else if (isSignificantlyOlder) {
return currentBestLocation;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate) {
return location;
} else if (isNewer && !isLessAccurate) {
return location;
} else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider) {
return location;
}
return bestLocation;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
Looks like we're coding the same application ;-)
Here is my current implementation. I'm still in the beta testing phase of my GPS uploader app, so there might be many possible improvements. but it seems to work pretty well so far.
/**
* try to get the 'best' location selected from all providers
*/
private Location getBestLocation() {
Location gpslocation = getLocationByProvider(LocationManager.GPS_PROVIDER);
Location networkLocation =
getLocationByProvider(LocationManager.NETWORK_PROVIDER);
// if we have only one location available, the choice is easy
if (gpslocation == null) {
Log.d(TAG, "No GPS Location available.");
return networkLocation;
}
if (networkLocation == null) {
Log.d(TAG, "No Network Location available");
return gpslocation;
}
// a locationupdate is considered 'old' if its older than the configured
// update interval. this means, we didn't get a
// update from this provider since the last check
long old = System.currentTimeMillis() - getGPSCheckMilliSecsFromPrefs();
boolean gpsIsOld = (gpslocation.getTime() < old);
boolean networkIsOld = (networkLocation.getTime() < old);
// gps is current and available, gps is better than network
if (!gpsIsOld) {
Log.d(TAG, "Returning current GPS Location");
return gpslocation;
}
// gps is old, we can't trust it. use network location
if (!networkIsOld) {
Log.d(TAG, "GPS is old, Network is current, returning network");
return networkLocation;
}
// both are old return the newer of those two
if (gpslocation.getTime() > networkLocation.getTime()) {
Log.d(TAG, "Both are old, returning gps(newer)");
return gpslocation;
} else {
Log.d(TAG, "Both are old, returning network(newer)");
return networkLocation;
}
}
/**
* get the last known location from a specific provider (network/gps)
*/
private Location getLocationByProvider(String provider) {
Location location = null;
if (!isProviderSupported(provider)) {
return null;
}
LocationManager locationManager = (LocationManager) getApplicationContext()
.getSystemService(Context.LOCATION_SERVICE);
try {
if (locationManager.isProviderEnabled(provider)) {
location = locationManager.getLastKnownLocation(provider);
}
} catch (IllegalArgumentException e) {
Log.d(TAG, "Cannot acces Provider " + provider);
}
return location;
}
Edit: here is the part that requests the periodic updates from the location providers:
public void startRecording() {
gpsTimer.cancel();
gpsTimer = new Timer();
long checkInterval = getGPSCheckMilliSecsFromPrefs();
long minDistance = getMinDistanceFromPrefs();
// receive updates
LocationManager locationManager = (LocationManager) getApplicationContext()
.getSystemService(Context.LOCATION_SERVICE);
for (String s : locationManager.getAllProviders()) {
locationManager.requestLocationUpdates(s, checkInterval,
minDistance, new LocationListener() {
#Override
public void onStatusChanged(String provider,
int status, Bundle extras) {}
#Override
public void onProviderEnabled(String provider) {}
#Override
public void onProviderDisabled(String provider) {}
#Override
public void onLocationChanged(Location location) {
// if this is a gps location, we can use it
if (location.getProvider().equals(
LocationManager.GPS_PROVIDER)) {
doLocationUpdate(location, true);
}
}
});
// //Toast.makeText(this, "GPS Service STARTED",
// Toast.LENGTH_LONG).show();
gps_recorder_running = true;
}
// start the gps receiver thread
gpsTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Location location = getBestLocation();
doLocationUpdate(location, false);
}
}, 0, checkInterval);
}
public void doLocationUpdate(Location l, boolean force) {
long minDistance = getMinDistanceFromPrefs();
Log.d(TAG, "update received:" + l);
if (l == null) {
Log.d(TAG, "Empty location");
if (force)
Toast.makeText(this, "Current location not available",
Toast.LENGTH_SHORT).show();
return;
}
if (lastLocation != null) {
float distance = l.distanceTo(lastLocation);
Log.d(TAG, "Distance to last: " + distance);
if (l.distanceTo(lastLocation) < minDistance && !force) {
Log.d(TAG, "Position didn't change");
return;
}
if (l.getAccuracy() >= lastLocation.getAccuracy()
&& l.distanceTo(lastLocation) < l.getAccuracy() && !force) {
Log.d(TAG,
"Accuracy got worse and we are still "
+ "within the accuracy range.. Not updating");
return;
}
if (l.getTime() <= lastprovidertimestamp && !force) {
Log.d(TAG, "Timestamp not never than last");
return;
}
}
// upload/store your location here
}
Things to consider:
do not request GPS updates too often, it drains battery power. I currently
use 30 min as default for my application.
add a 'minimum distance to last known location' check. without this, your points
will "jump around" when GPS is not available and the location is being triangulated
from the cell towers. or you can check if the new location is outside of the accuracy
value from the last known location.
To select the right location provider for your app, you can use Criteria objects:
Criteria myCriteria = new Criteria();
myCriteria.setAccuracy(Criteria.ACCURACY_HIGH);
myCriteria.setPowerRequirement(Criteria.POWER_LOW);
// let Android select the right location provider for you
String myProvider = locationManager.getBestProvider(myCriteria, true);
// finally require updates at -at least- the desired rate
long minTimeMillis = 600000; // 600,000 milliseconds make 10 minutes
locationManager.requestLocationUpdates(myProvider,minTimeMillis,0,locationListener);
Read the documentation for requestLocationUpdates for more details on how the arguments are taken into account:
The frequency of notification may be controlled using the minTime and
minDistance parameters. If minTime is greater than 0, the LocationManager
could potentially rest for minTime milliseconds between location updates
to conserve power. If minDistance is greater than 0, a location will only
be broadcasted if the device moves by minDistance meters. To obtain
notifications as frequently as possible, set both parameters to 0.
More thoughts
You can monitor the accuracy of the Location objects with Location.getAccuracy(), which returns the estimated accuracy of the position in meters.
the Criteria.ACCURACY_HIGH criterion should give you errors below 100m, which is not as good as GPS can be, but matches your needs.
You also need to monitor the status of your location provider, and switch to another provider if it gets unavailable or disabled by the user.
The passive provider may also be a good match for this kind of application: the idea is to use location updates whenever they are requested by another app and broadcast systemwide.
Answering the first two points:
GPS will always give you a more precise location, if it is enabled and if there are no thick walls around.
If location did not change, then you can call getLastKnownLocation(String) and retrieve the location immediately.
Using an alternative approach:
You can try getting the cell id in use or all the neighboring cells
TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
GsmCellLocation loc = (GsmCellLocation) mTelephonyManager.getCellLocation();
Log.d ("CID", Integer.toString(loc.getCid()));
Log.d ("LAC", Integer.toString(loc.getLac()));
// or
List<NeighboringCellInfo> list = mTelephonyManager.getNeighboringCellInfo ();
for (NeighboringCellInfo cell : list) {
Log.d ("CID", Integer.toString(cell.getCid()));
Log.d ("LAC", Integer.toString(cell.getLac()));
}
You can refer then to cell location through several open databases (e.g., http://www.location-api.com/ or http://opencellid.org/ )
The strategy would be to read the list of tower IDs when reading the location. Then, in next query (10 minutes in your app), read them again. If at least some towers are the same, then it's safe to use getLastKnownLocation(String). If they're not, then wait for onLocationChanged(). This avoids the need of a third party database for the location. You can also try this approach.
This is my solution which works fairly well:
private Location bestLocation = null;
private Looper looper;
private boolean networkEnabled = false, gpsEnabled = false;
private synchronized void setLooper(Looper looper) {
this.looper = looper;
}
private synchronized void stopLooper() {
if (looper == null) return;
looper.quit();
}
#Override
protected void runTask() {
final LocationManager locationManager = (LocationManager) service
.getSystemService(Context.LOCATION_SERVICE);
final SharedPreferences prefs = getPreferences();
final int maxPollingTime = Integer.parseInt(prefs.getString(
POLLING_KEY, "0"));
final int desiredAccuracy = Integer.parseInt(prefs.getString(
DESIRED_KEY, "0"));
final int acceptedAccuracy = Integer.parseInt(prefs.getString(
ACCEPTED_KEY, "0"));
final int maxAge = Integer.parseInt(prefs.getString(AGE_KEY, "0"));
final String whichProvider = prefs.getString(PROVIDER_KEY, "any");
final boolean canUseGps = whichProvider.equals("gps")
|| whichProvider.equals("any");
final boolean canUseNetwork = whichProvider.equals("network")
|| whichProvider.equals("any");
if (canUseNetwork)
networkEnabled = locationManager
.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (canUseGps)
gpsEnabled = locationManager
.isProviderEnabled(LocationManager.GPS_PROVIDER);
// If any provider is enabled now and we displayed a notification clear it.
if (gpsEnabled || networkEnabled) removeErrorNotification();
if (gpsEnabled)
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER));
if (networkEnabled)
updateBestLocation(locationManager
.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
if (desiredAccuracy == 0
|| getLocationQuality(desiredAccuracy, acceptedAccuracy,
maxAge, bestLocation) != LocationQuality.GOOD) {
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateBestLocation(location);
if (desiredAccuracy != 0
&& getLocationQuality(desiredAccuracy,
acceptedAccuracy, maxAge, bestLocation)
== LocationQuality.GOOD)
stopLooper();
}
public void onProviderEnabled(String provider) {
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER))networkEnabled =true;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER)) gpsEnabled = true;
// The user has enabled a location, remove any error
// notification
if (canUseGps && gpsEnabled || canUseNetwork
&& networkEnabled) removeErrorNotification();
}
public void onProviderDisabled(String provider) {
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER))networkEnabled=false;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER)) gpsEnabled = false;
if (!gpsEnabled && !networkEnabled) {
showErrorNotification();
stopLooper();
}
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
Log.i(LOG_TAG, "Provider " + provider + " statusChanged");
if (isSameProvider(provider,
LocationManager.NETWORK_PROVIDER)) networkEnabled =
status == LocationProvider.AVAILABLE
|| status == LocationProvider.TEMPORARILY_UNAVAILABLE;
else if (isSameProvider(provider,
LocationManager.GPS_PROVIDER))
gpsEnabled = status == LocationProvider.AVAILABLE
|| status == LocationProvider.TEMPORARILY_UNAVAILABLE;
// None of them are available, stop listening
if (!networkEnabled && !gpsEnabled) {
showErrorNotification();
stopLooper();
}
// The user has enabled a location, remove any error
// notification
else if (canUseGps && gpsEnabled || canUseNetwork
&& networkEnabled) removeErrorNotification();
}
};
if (networkEnabled || gpsEnabled) {
Looper.prepare();
setLooper(Looper.myLooper());
// Register the listener with the Location Manager to receive
// location updates
if (canUseGps)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
if (canUseNetwork)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000, 1,
locationListener, Looper.myLooper());
Timer t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
stopLooper();
}
}, maxPollingTime * 1000);
Looper.loop();
t.cancel();
setLooper(null);
locationManager.removeUpdates(locationListener);
} else // No provider is enabled, show a notification
showErrorNotification();
}
if (getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
bestLocation) != LocationQuality.BAD) {
sendUpdate(new Event(EVENT_TYPE, locationToString(desiredAccuracy,
acceptedAccuracy, maxAge, bestLocation)));
} else Log.w(LOG_TAG, "LocationCollector failed to get a location");
}
private synchronized void showErrorNotification() {
if (notifId != 0) return;
ServiceHandler handler = service.getHandler();
NotificationInfo ni = NotificationInfo.createSingleNotification(
R.string.locationcollector_notif_ticker,
R.string.locationcollector_notif_title,
R.string.locationcollector_notif_text,
android.R.drawable.stat_notify_error);
Intent intent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
ni.pendingIntent = PendingIntent.getActivity(service, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Message msg = handler.obtainMessage(ServiceHandler.SHOW_NOTIFICATION);
msg.obj = ni;
handler.sendMessage(msg);
notifId = ni.id;
}
private void removeErrorNotification() {
if (notifId == 0) return;
ServiceHandler handler = service.getHandler();
if (handler != null) {
Message msg = handler.obtainMessage(
ServiceHandler.CLEAR_NOTIFICATION, notifId, 0);
handler.sendMessage(msg);
notifId = 0;
}
}
#Override
public void interrupt() {
stopLooper();
super.interrupt();
}
private String locationToString(int desiredAccuracy, int acceptedAccuracy,
int maxAge, Location location) {
StringBuilder sb = new StringBuilder();
sb.append(String.format(
"qual=%s time=%d prov=%s acc=%.1f lat=%f long=%f",
getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
location), location.getTime() / 1000, // Millis to
// seconds
location.getProvider(), location.getAccuracy(), location
.getLatitude(), location.getLongitude()));
if (location.hasAltitude())
sb.append(String.format(" alt=%.1f", location.getAltitude()));
if (location.hasBearing())
sb.append(String.format(" bearing=%.2f", location.getBearing()));
return sb.toString();
}
private enum LocationQuality {
BAD, ACCEPTED, GOOD;
public String toString() {
if (this == GOOD) return "Good";
else if (this == ACCEPTED) return "Accepted";
else return "Bad";
}
}
private LocationQuality getLocationQuality(int desiredAccuracy,
int acceptedAccuracy, int maxAge, Location location) {
if (location == null) return LocationQuality.BAD;
if (!location.hasAccuracy()) return LocationQuality.BAD;
long currentTime = System.currentTimeMillis();
if (currentTime - location.getTime() < maxAge * 1000
&& location.getAccuracy() <= desiredAccuracy)
return LocationQuality.GOOD;
if (acceptedAccuracy == -1
|| location.getAccuracy() <= acceptedAccuracy)
return LocationQuality.ACCEPTED;
return LocationQuality.BAD;
}
private synchronized void updateBestLocation(Location location) {
bestLocation = getBestLocation(location, bestLocation);
}
protected Location getBestLocation(Location location,
Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return location;
}
if (location == null) return currentBestLocation;
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return location;
// If the new location is more than two minutes older, it must be
// worse
} else if (isSignificantlyOlder) {
return currentBestLocation;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate) {
return location;
} else if (isNewer && !isLessAccurate) {
return location;
} else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider) {
return location;
}
return bestLocation;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) return provider2 == null;
return provider1.equals(provider2);
}
Location accuracy depends mostly on the location provider used:
GPS - will get you several meters accuracy (assuming you have GPS reception)
Wifi - Will get you few hundred meters accuracy
Cell Network - Will get you very inaccurate results (I've seen up to 4km deviation...)
If it's accuracy you are looking for, then GPS is your only option.
I've read a very informative article about it here.
As for the GPS timeout - 60 seconds should be sufficient, and in most cases even too much. I think 30 seconds is OK and sometimes even less than 5 sec...
if you only need a single location, I'd suggest that in your onLocationChanged method, once you receive an update you'll unregister the listener and avoid unnecessary usage of the GPS.
Currently i am using since this is trustable for getting location and calculating distance for my application...... i am using this for my taxi application.
use the fusion API that google developer have developed with fusion of GPS Sensor,Magnetometer,Accelerometer also using Wifi or cell location to calculate or estimate the location. It is also able to give location updates also inside the building accurately.
for detail get to link
https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi
import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity implements LocationListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private static final long ONE_MIN = 500;
private static final long TWO_MIN = 500;
private static final long FIVE_MIN = 500;
private static final long POLLING_FREQ = 1000 * 20;
private static final long FASTEST_UPDATE_FREQ = 1000 * 5;
private static final float MIN_ACCURACY = 1.0f;
private static final float MIN_LAST_READ_ACCURACY = 1;
private LocationRequest mLocationRequest;
private Location mBestReading;
TextView tv;
private GoogleApiClient mGoogleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!servicesAvailable()) {
finish();
}
setContentView(R.layout.activity_main);
tv= (TextView) findViewById(R.id.tv1);
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(POLLING_FREQ);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_FREQ);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
if (mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
}
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
}
#Override
protected void onPause() {d
super.onPause();
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
tv.setText(location + "");
// Determine whether new location is better than current best
// estimate
if (null == mBestReading || location.getAccuracy() < mBestReading.getAccuracy()) {
mBestReading = location;
if (mBestReading.getAccuracy() < MIN_ACCURACY) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}
}
#Override
public void onConnected(Bundle dataBundle) {
// Get first reading. Get additional location updates if necessary
if (servicesAvailable()) {
// Get best last location measurement meeting criteria
mBestReading = bestLastKnownLocation(MIN_LAST_READ_ACCURACY, FIVE_MIN);
if (null == mBestReading
|| mBestReading.getAccuracy() > MIN_LAST_READ_ACCURACY
|| mBestReading.getTime() < System.currentTimeMillis() - TWO_MIN) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
//Schedule a runnable to unregister location listeners
#Override
public void run() {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, MainActivity.this);
}
}, ONE_MIN, TimeUnit.MILLISECONDS);
}
}
}
#Override
public void onConnectionSuspended(int i) {
}
private Location bestLastKnownLocation(float minAccuracy, long minTime) {
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;
// Get the best most recent location currently available
Location mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
//tv.setText(mCurrentLocation+"");
if (mCurrentLocation != null) {
float accuracy = mCurrentLocation.getAccuracy();
long time = mCurrentLocation.getTime();
if (accuracy < bestAccuracy) {
bestResult = mCurrentLocation;
bestAccuracy = accuracy;
bestTime = time;
}
}
// Return best reading or null
if (bestAccuracy > minAccuracy || bestTime < minTime) {
return null;
}
else {
return bestResult;
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
private boolean servicesAvailable() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (ConnectionResult.SUCCESS == resultCode) {
return true;
}
else {
GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
return false;
}
}
}
I scoured the internet for an updated (past year) answer using the latest location pulling methods suggested by google (to use FusedLocationProviderClient). I finally landed on this:
https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates
I created a new project and copied in most of this code. Boom. It works. And I think without any deprecated lines.
Also, the simulator doesn't seem to get a GPS location, that I know of. It did get as far as reporting this in the log: "All location settings are satisfied."
And finally, in case you wanted to know (I did), you DO NOT need a google maps api key from the google developer console, if all you want is the GPS location.
Also useful is their tutorial. But I wanted a full one page tutorial/code example, and that. Their tutorial stacks but is confusing when you're new to this because you don't know what pieces you need from earlier pages.
https://developer.android.com/training/location/index.html
And finally, remember things like this:
I not only had to modify the mainActivity.Java. I also had to modify Strings.xml, androidmanifest.xml, AND the correct build.gradle. And also your activity_Main.xml (but that part was easy for me).
I needed to add dependencies like this one: implementation 'com.google.android.gms:play-services-location:11.8.0', and update the settings of my android studio SDK to include google play services. (file settings appearance system settings android SDK SDK Tools check google play services).
update: the android simulator did seem to get a location and location change events (when I changed the value in the settings of the sim). But my best and first results were on an actual device. So it's probably easiest to test on actual devices.
Recently refactored to obtain the location of the code, learn some good ideas, and finally achieved a relatively perfect library and Demo.
#Gryphius's answer is good
//request all valid provider(network/gps)
private boolean requestAllProviderUpdates() {
checkRuntimeEnvironment();
checkPermission();
if (isRequesting) {
EasyLog.d("Request location update is busy");
return false;
}
long minTime = getCheckTimeInterval();
float minDistance = getCheckMinDistance();
if (mMapLocationListeners == null) {
mMapLocationListeners = new HashMap<>();
}
mValidProviders = getValidProviders();
if (mValidProviders == null || mValidProviders.isEmpty()) {
throw new IllegalArgumentException("Not available provider.");
}
for (String provider : mValidProviders) {
LocationListener locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
if (location == null) {
EasyLog.e("LocationListener callback location is null.");
return;
}
printf(location);
mLastProviderTimestamp = location.getTime();
if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
finishResult(location);
} else {
doLocationResult(location);
}
removeProvider(location.getProvider());
if (isEmptyValidProviders()) {
requestTimeoutMsgInit();
removeUpdates();
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
}
};
getLocationManager().requestLocationUpdates(provider, minTime, minDistance, locationListener);
mMapLocationListeners.put(provider, locationListener);
EasyLog.d("Location request %s provider update.", provider);
}
isRequesting = true;
return true;
}
//remove request update
public void removeUpdates() {
checkRuntimeEnvironment();
LocationManager locationManager = getLocationManager();
if (mMapLocationListeners != null) {
Set<String> keys = mMapLocationListeners.keySet();
for (String key : keys) {
LocationListener locationListener = mMapLocationListeners.get(key);
if (locationListener != null) {
locationManager.removeUpdates(locationListener);
EasyLog.d("Remove location update, provider is " + key);
}
}
mMapLocationListeners.clear();
isRequesting = false;
}
}
//Compared with the last successful position, to determine whether you need to filter
private boolean isNeedFilter(Location location) {
checkLocation(location);
if (mLastLocation != null) {
float distance = location.distanceTo(mLastLocation);
if (distance < getCheckMinDistance()) {
return true;
}
if (location.getAccuracy() >= mLastLocation.getAccuracy()
&& distance < location.getAccuracy()) {
return true;
}
if (location.getTime() <= mLastProviderTimestamp) {
return true;
}
}
return false;
}
private void doLocationResult(Location location) {
checkLocation(location);
if (isNeedFilter(location)) {
EasyLog.d("location need to filtered out, timestamp is " + location.getTime());
finishResult(mLastLocation);
} else {
finishResult(location);
}
}
//Return to the finished position
private void finishResult(Location location) {
checkLocation(location);
double latitude = location.getLatitude();
double longitude = location.getLongitude();
float accuracy = location.getAccuracy();
long time = location.getTime();
String provider = location.getProvider();
if (mLocationResultListeners != null && !mLocationResultListeners.isEmpty()) {
String format = "Location result:<%f, %f> Accuracy:%f Time:%d Provider:%s";
EasyLog.i(String.format(format, latitude, longitude, accuracy, time, provider));
mLastLocation = location;
synchronized (this) {
Iterator<LocationResultListener> iterator = mLocationResultListeners.iterator();
while (iterator.hasNext()) {
LocationResultListener listener = iterator.next();
if (listener != null) {
listener.onResult(location);
}
iterator.remove();
}
}
}
}
Complete implementation:
https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java
1.Thanks #Gryphius solution ideas, I also share the complete code.
2.Each request to complete the location, it is best to removeUpdates, otherwise the phone status bar will always display the positioning icon
In my experience, I've found it best to go with the GPS fix unless it's not available. I don't know much about other location providers, but I know that for GPS there are a few tricks that can be used to give a bit of a ghetto precision measure. The altitude is often a sign, so you could check for ridiculous values. There is the accuracy measure on Android location fixes. Also if you can see the number of satellites used, this can also indicate the precision.
An interesting way of getting a better idea of the accuracy could be to ask for a set of fixes very rapidly, like ~1/sec for 10 seconds and then sleep for a minute or two. One talk I've been to has led to believe that some android devices will do this anyway. You would then weed out the outliers (I've heard Kalman filter mentioned here) and use some kind of centering strategy to get a single fix.
Obviously the depth you get to here depends on how hard your requirements are. If you have particularly strict requirement to get THE BEST location possible, I think you'll find that GPS and network location are as similar as apples and oranges. Also GPS can be wildly different from device to device.
Skyhook (http://www.skyhookwireless.com/) has a location provider that is much faster than the standard one Google provides. It might be what you're looking for. I'm not affiliated with them.