I have some Android code that needs to get the best available location QUICKLY, from GPS, network or whatever is available. Accuracy is less important than speed.
Getting the best available location is surely a really standard task. Yet I can't find any code to demonstrate it. The Android location code expects you to specify criteria, register for updates, and wait - which is fine if you have detailed criteria and don't mind waiting around.
But my app needs to work a bit more like the Maps app does when it first locates you - work from any available provider, and just check the location isn't wildly out of date or null.
I've attempted to roll my own code to do this, but am having problems. (It's inside an IntentService where an upload happens, if that makes a开发者_StackOverflow社区ny difference. I've included all the code for info.) What's wrong with this code?
@Override
protected void onHandleIntent(Intent arg0) {
testProviders();
doUpload();
}
private boolean doUpload() {
int j = 0;
// check if we have accurate location data yet - wait up to 30 seconds
while (j < 30) {
if ((latString == "") || (lonString == "")) {
Log.d(LOG_TAG, "latlng null");
Thread.sleep(1000);
j++;
} else {
Log.d(LOG_TAG, "found lat " + latString + " and lon " + lonString);
break;
}
//do the upload here anyway, with or without location data
//[code removed for brevity]
}
public boolean testProviders() {
Log.e(LOG_TAG, "testProviders");
String location_context = Context.LOCATION_SERVICE;
locationmanager = (LocationManager) getSystemService(location_context);
List<String> providers = locationmanager.getProviders(true);
for (String provider : providers) {
Log.e(LOG_TAG, "registering provider " + provider);
listener = new LocationListener() {
public void onLocationChanged(Location location) {
// keep checking the location - until we have
// what we need
//if (!checkLoc(location)) {
Log.e(LOG_TAG, "onLocationChanged");
locationDetermined = checkLoc(location);
//}
}
public void onProviderDisabled(String provider) {
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
}
};
locationmanager.requestLocationUpdates(provider, 0,
0, listener);
}
Log.e(LOG_TAG, "getting updates");
return true;
}
private boolean checkLoc(Location location) {
float tempAccuracy = location.getAccuracy();
int locAccuracy = (int) tempAccuracy;
Log.d(LOG_TAG, "locAccuracy = " + locAccuracy);
if ((locAccuracy != 0) && (locAccuracy < LOCATION_ACCURACY)) {
latitude = location.getLatitude();
longitude = location.getLongitude();
latString = latitude.toString();
lonString = longitude.toString();
return true;
}
return false;
}
public void removeListeners() {
// Log.e(LOG_TAG, "removeListeners");
if ((locationmanager != null) && (listener != null)) {
locationmanager.removeUpdates(listener);
}
locationmanager = null;
// Log.d(LOG_TAG, "Removed " + listener.toString());
}
@Override
public void onDestroy() {
super.onDestroy();
removeListeners();
}
Unfortunately, this finds the network provider, but only ever outputs latlng null
30 times - it never seems to get a location at all. I never even get a log statement of locationChanged
.
It's funny, because from ddms I can see output like:
NetworkLocationProvider: onCellLocationChanged [305,8580]
NetworkLocationProvider: getNetworkLocation(): returning cache location with accuracy 75.0
seeming to suggest that the network provider does have some location info after all, I'm just not getting at it.
Can anyone help? I think working example code would be a useful resource for the Android/StackOverflow community.
You are definitely trying to do this the hard way. Here are some snippets from a new app I am working on. It uses Criteria to get all providers capable of returning a fine level of accuracy without a cost.
If no providers are enabled a dialog is displayed that prompts the user to turn on their location settings. If the user hits ok an Intent is actually fired that sends them to the settings on their phone. If there are providers enabled the app takes the most recent last known location from any of the enabled providers. For my app I just need to know what general area the user is in and it's likely that the last known location is from their home area.
If providers are enabled the loop also requests location updates as quickly as possible. This is ideal for my app but you can change this to conserve battery my modifying the arguments to the requestLocationUpdates method.
The optimization that this code has that the examples on the Android app don't really show is that all of the enabled providers are started simultaneously. All of the providers will return separate updates on to the onLocationChanged method. In my app I remove the location listener after one of the providers returns a location with a good enough accuracy.
Start Location Updates:
void getCurrentLocation() {
List<String> providers = locationManager.getProviders(criteria, true);
if (providers != null) {
Location newestLocation = null;
for (String provider : providers) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
if (newestLocation == null) {
newestLocation = location;
} else {
if (location.getTime() > newestLocation.getTime()) {
newestLocation = location;
}
}
locationManager.requestLocationUpdates(provider, 0, 0, this);
}
}
} else {
LocationDialogFragment dialog = new LocationDialogFragment();
dialog.show(getSupportFragmentManager(),
LocationDialogFragment.class.getName());
}
}
Receive Location Update:
@Override
public void onLocationChanged(Location location) {
float bestAccuracy = -1f;
if (location.getAccuracy() != 0.0f
&& (location.getAccuracy() < bestAccuracy) || bestAccuracy == -1f) {
if (location.getAccuracy() < Const.MIN_ACCURACY) {
locationManager.removeUpdates(this);
}
}
bestAccuracy = location.getAccuracy();
}
Location Settings Dialog:
public class LocationDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.location_dialog_message)
.setPositiveButton(R.string.location_dialog_positive_button,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent settingsIntent = new Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(settingsIntent);
}
})
.setNegativeButton(R.string.location_dialog_negative_button,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getActivity(),
R.string.no_location_message, Toast.LENGTH_LONG)
.show();
}
});
return builder.create();
}
}
Thread.sleep()
in production code is a serious code smell IMHO. If you find you're having to do that, you're probably doing something that's not supposed to work that way. In this case, I think it's the source of your problem -- you're not letting Android go back to process this thread's message queue to dispatch any location updates it finds. I suspect an IntentService
is just not going to work for your scenario.
精彩评论