Хороший способ получить местоположение пользователя в Android

211

Эта проблема:

Получение текущего местоположения пользователя в пределах порога как можно скорее и в то же время экономить заряд батареи.

Почему проблема является проблемой:

Во-первых, у Android есть два провайдера; сеть и GPS. Иногда сеть лучше, а иногда лучше GPS.

Под «лучшим» я подразумеваю соотношение скорости и точности.
Я готов пожертвовать точностью в несколько метров, если смогу определить местоположение практически мгновенно и без включения GPS.

Во-вторых, если вы запрашиваете обновления для изменений местоположения, ничего не отправляется, если текущее местоположение стабильно.

У Google есть пример определения «лучшего» местоположения здесь: http://developer.android.com/guide/topics/location/obtainment-user-location.html#BestEstimate
Но я думаю, что это не так хорошо, как следовало бы /может быть.

Я немного сбит с толку, почему у Google нет нормализованного API для определения местоположения, разработчику не нужно заботиться о том, откуда оно, вы просто должны указать, что вы хотите, и телефон должен выбрать для вас.

Что мне нужно помочь с:

Мне нужно найти хороший способ определить «лучшее» местоположение, возможно, через какое-то эвристическое или, возможно, через какую-нибудь стороннюю библиотеку.

Это не значит определить лучшего поставщика!
Я, наверное, собираюсь использовать всех провайдеров и выбирать лучших из них.

Фон приложения:

Приложение будет собирать местоположение пользователя с фиксированным интервалом (скажем, каждые 10 минут или около того) и отправлять его на сервер.
Приложение должно сохранять как можно больше батареи, а местоположение должно иметь точность X (50-100?) Метров.

Цель состоит в том, чтобы позже иметь возможность рисовать путь пользователя в течение дня на карте, поэтому мне нужна достаточная точность для этого.

Разное:

Каковы, на ваш взгляд, разумные значения желаемой и принятой точности?
Я использовал 100 м как принято и 30 м как хотелось бы, это много, чтобы спросить?
Я хотел бы иметь возможность нанести путь пользователя на карту позже.
100 м для желаемого и 500 м для принятого лучше?

Кроме того, прямо сейчас у меня включен GPS максимум на 60 секунд на обновление местоположения, это слишком мало, чтобы получить местоположение, если вы находитесь в помещении с точностью, может быть, 200 м?


Это мой текущий код, любая обратная связь приветствуется (за исключением отсутствия проверки ошибок, которая является 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);
}
Никлас А.
источник
7
Включение происходит очень поздно, но недавно объявленный на IO 2013 провайдер слияния местоположения выглядит так, как будто он отвечает многим вашим потребностям - developer.android.com/google/play-services/location.html
Мэтт
не должна быть последняя строка getBestLocation (): return currentBestLocation; вместо возврата bestLocation ;?
Гавриил

Ответы:

164

Похоже, мы кодируем одно и то же приложение ;-)
Вот моя текущая реализация. Я все еще на стадии бета-тестирования моего приложения для загрузки GPS, поэтому возможны многие улучшения. но, похоже, пока работает довольно хорошо.

/**
 * 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;
}

Изменить: вот часть, которая запрашивает периодические обновления от поставщиков местоположения:

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
}

Что нужно учитывать:

  • не запрашивайте обновления GPS слишком часто, это разряжает батарею. В настоящее время я использую 30 минут по умолчанию для своего приложения.

  • добавить проверку «минимальное расстояние до последнего известного местоположения». без этого ваши точки будут «прыгать», когда GPS недоступен, а местоположение триангулируется от вышек сотовой связи. или вы можете проверить, находится ли новое местоположение за пределами значения точности из последнего известного местоположения.

Грифиус
источник
2
Вы никогда не получите новое местоположение, вы используете только те места, которые были там из предыдущих обновлений. Я думаю, что этот код принесет большую пользу, добавив прослушиватель, который обновляет местоположение, время от времени включая GPS.
Никлас А.
2
извините, я думал, что вы заинтересованы только в той части, которая выбирает лучшее из всех доступных мест. Я добавил код выше, который запрашивает их также. Если получено новое местоположение GPS, оно сразу же сохраняется / загружается. если я получаю обновление местоположения в сети, я сохраняю его для справки и «надеюсь», что получу также обновление gps до следующей проверки местоположения.
Грифус
2
У меня также был метод stopRecording (), который отменял таймер. В конце концов я переключился с таймера на ScheduledThreadPoolExecutor, поэтому stopRecording теперь в основном вызывает executor.shutdown () и отменяет регистрацию всех слушателей обновления местоположения
Gryphius
1
согласно моей scm, stopRecording вызывал только gpsTimer.cancel () и устанавливал gps_recorder_running = false, так что, как в вашем случае, никакой очистки слушателей тогда не было. текущий код отслеживает всех активных слушателей в векторе, у меня не было этого, когда я писал этот ответ 1,5 года назад.
Gryphius
1
это уже на github , но я не уверен, что это все еще лучший способ делать вещи GPS в настоящее время. Afaik, они сделали много улучшений в API определения местоположения, так как я написал этот код.
Грифиус
33

Чтобы выбрать подходящего поставщика местоположения для вашего приложения, вы можете использовать объекты Criteria :

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); 

Прочитайте документацию для requestLocationUpdates для более подробной информации о том, как аргументы принимаются во внимание:

Частота уведомления может контролироваться с помощью параметров minTime и minDistance. Если minTime больше 0, LocationManager потенциально может отдыхать в течение миллисекунд minTime между обновлениями местоположения для экономии энергии. Если minDistance больше 0, местоположение будет транслироваться только в том случае, если устройство перемещается на счетчики minDistance. Чтобы получать уведомления как можно чаще, установите оба параметра на 0.

Больше мыслей

  • Вы можете отслеживать точность объектов Location с помощью Location.getAccuracy () , который возвращает приблизительную точность положения в метрах.
  • Criteria.ACCURACY_HIGHкритерий должен дать вам ошибку ниже 100 м, что не так хорошо , как GPS может быть, но соответствуют вашим потребностям.
  • Вам также необходимо отслеживать состояние вашего провайдера местоположения и переключаться на другого провайдера, если он становится недоступным или отключенным пользователем.
  • Пассивный поставщик может быть также хорошо подходит для такого применения: идея заключается в том , чтобы использовать данные о местоположении , когда они запрашиваются другим приложением и вещания Systemwide.
Stéphane
источник
Я изучил, Criteriaно что, если последнее местоположение в сети является отличным (оно может узнать через Wi-Fi), и для его получения не требуется время или батарея (getLastKnown), тогда критерии, вероятно, будут игнорировать это и возвращать вместо этого GPS. Я не могу поверить, что Google сделал это трудным для разработчиков.
Никлас А.
Помимо использования критериев, вы можете при каждом обновлении местоположения, отправляемом выбранным поставщиком, проверять lastKnowLocation для поставщика GPS и сравнивать его (точность и дату) с вашим текущим местоположением. Но это кажется мне приятным, а не требованием из ваших спецификаций; если иногда достигается лучшая точность, будет ли она полезна для ваших пользователей?
Стефан
Это то, что я делаю сейчас, проблема в том, что мне трудно понять, достаточно ли хорошо известно последнее знание. Я также могу добавить, что мне не нужно ограничивать себя одним провайдером, чем больше я использую, тем быстрее я могу получить блокировку.
Никлас А.
Имейте в виду, что PASSIVE_PROVIDER требует API уровня 8 или выше.
Эдуардо
@ Стефан, прости за редактирование. Не заботься об этом. Ваш пост правильный. Я сделал это редактировать по ошибке. Сожалею. С уважением.
Гаучо
10

Отвечая на первые два пункта :

  • GPS всегда даст вам более точное местоположение, если оно включено и если вокруг нет толстых стен .

  • Если местоположение не изменилось, вы можете вызвать getLastKnownLocation (String) и немедленно получить местоположение.

Используя альтернативный подход :

Вы можете попробовать использовать идентификатор ячейки или все соседние ячейки

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()));
}

Затем вы можете ссылаться на местоположение ячейки через несколько открытых баз данных (например, http://www.location-api.com/ или http://opencellid.org/ ).


Стратегия будет заключаться в чтении списка идентификаторов башен при чтении местоположения. Затем, в следующем запросе (10 минут в вашем приложении), прочитайте их снова. Если хотя бы несколько башен одинаковы, то их можно использовать безопасно getLastKnownLocation(String). Если нет, тогда ждите onLocationChanged(). Это исключает необходимость использования сторонней базы данных для этого местоположения. Вы также можете попробовать этот подход .

Aleadam
источник
Да, но я проблема возникает, если lastKnownLocation действительно плохо. Мне нужен хороший способ выбрать лучшее из двух мест.
Никлас А.
Вы можете сохранить информацию о башнях и проверить, не изменились ли эти башни. Если они это сделали, то дождитесь нового места, если нет (или если только некоторые изменились), то используйте его снова. Таким образом, вы не будете сравнивать расположение башен с базой данных.
Алеадам
Использование башен кажется мне большим излишним, хорошая идея.
Никлас А.
@ Никлас код не становится более сложным, чем это. Вам понадобится android.Manifest.permission # ACCESS_COARSE_UPDATES.
Алеадам
Да, но мне все еще нужно использовать сторонний сервис, а также мне нужен способ решить, когда использовать информацию о вышке поверх данных о местоположении, это просто добавляет дополнительный уровень сложности.
Никлас А.
9

Это мое решение, которое работает довольно хорошо:

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);
}
Никлас А.
источник
Привет, Никлас, у меня такое же уравнение, так что я могу общаться с вами любыми способами ... Я был бы вам благодарен, если бы вы могли помочь нам ...
Школьник
Не могли бы вы опубликовать весь код? Спасибо, очень
понравилось
Это весь код. У меня больше нет доступа к проекту.
Никлас А.
1
Вы, кажется, взяли код этого проекта "android-protips-location", и он все еще жив. Люди могут увидеть, как это работает здесь code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77
7

Точность определения местоположения зависит в основном от используемого поставщика местоположения:

  1. GPS - даст вам точность нескольких метров (при условии, что у вас есть прием GPS)
  2. Wi-Fi - даст вам точность в несколько сотен метров
  3. Сотовая сеть - даст вам очень неточные результаты (я видел отклонение до 4 км ...)

Если вы ищете точность, то GPS - ваш единственный выбор.

Я прочитал очень информативную статью об этом здесь .

Что касается времени ожидания GPS - 60 секунд должно быть достаточно, а в большинстве случаев даже слишком много. Я думаю, что 30 секунд это нормально, а иногда даже меньше, чем 5 секунд ...

если вам нужно только одно местоположение, я рекомендую, чтобы в вашем onLocationChangedметоде после получения обновления вы отменяли регистрацию слушателя и избегали ненужного использования GPS.

Muzikant
источник
Мне все равно, откуда я получаю свое местоположение, я не хочу ограничивать меня одним провайдером
Никлас А.
Вы можете зарегистрировать всех провайдеров определения местоположения, доступных на устройстве (список всех провайдеров можно получить из LocationManager.getProviders ()), но если вы ищете точное исправление, в большинстве случаев сетевой провайдер вам не пригодится.
Музикант
Да, но это не вопрос о выборе между провайдерами, это вопрос о том, как получить лучшее местоположение в целом (даже если задействовано несколько провайдеров)
Никлас А.
4

В настоящее время я использую это, поскольку это надежно для получения местоположения и расчета расстояния для моего приложения ...... я использую это для моего приложения такси.

используйте API-интерфейс fusion, разработанный Google-разработчиком с использованием GPS-датчика, магнитометра, акселерометра, а также Wi-Fi или местоположения ячейки для расчета или оценки местоположения. Он также может точно определять местоположение и внутри здания. для получения подробной информации перейдите по ссылке 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;
        }
    }
}
AshisParajuli
источник
2

Я искал в Интернете обновленный (в прошлом году) ответ, используя последние методы определения местоположения, предложенные Google (для использования FusedLocationProviderClient). Я наконец-то приземлился на это:

https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates

Я создал новый проект и скопировал большую часть этого кода. Boom. Оно работает. И я думаю без каких-либо устаревших строк.

Кроме того, симулятор, кажется, не получает местоположение GPS, о котором я знаю. Это действительно так далеко, как сообщать об этом в журнале: «Все настройки местоположения удовлетворены».

И, наконец, если вы хотите знать (я это сделал), вам НЕ нужен ключ API Google Maps из консоли разработчика Google, если вам нужно только местоположение GPS.

Также полезным является их учебник. Но я хотел полный одностраничный учебник / пример кода, и это. Их учебник сложен, но сбивает с толку, когда вы новичок в этом, потому что вы не знаете, какие части вам нужны из предыдущих страниц.

https://developer.android.com/training/location/index.html

И наконец, запомните такие вещи:

Я не только должен был изменить mainActivity.Java. Мне также пришлось изменить Strings.xml, androidmanifest.xml и правильный build.gradle. А также ваш activity_Main.xml (но эта часть была легкой для меня).

Мне нужно было добавить зависимости, подобные этой: реализация 'com.google.android.gms: play-services-location: 11.8.0', и обновить настройки моей SDK для Android Studio, чтобы включить сервисы Google Play. (настройки файлов внешнего вида, системные настройки Android SDK SDK Tools, проверьте сервисы Google Play).

обновление: симулятор андроида, похоже, получал события локации и смены локации (когда я менял значение в настройках сима). Но мои лучшие и первые результаты были на реальном устройстве. Так что, вероятно, проще всего протестировать на реальных устройствах.

Neo42
источник
1

Недавно проведен рефакторинг для получения местоположения кода, изучения некоторых хороших идей и, наконец, достижения относительно совершенной библиотеки и демоверсии.

@ Ответ Грифия хорош

    //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();
            }
        }
    }
}

Полная реализация: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

1.Спасибо идеи решения @Gryphius, я также делюсь полным кодом.

2.Каждый запрос на заполнение местоположения лучше удалить с помощью обновлений, в противном случае в строке состояния телефона всегда будет отображаться значок позиционирования.

Bingerz
источник
0

По своему опыту, я нашел, что лучше всего использовать исправление GPS, если оно недоступно. Я не знаю много о других провайдерах определения местоположения, но я знаю, что для GPS есть несколько уловок, которые можно использовать, чтобы дать немного гетто точности измерения. Высота над уровнем моря часто является признаком, поэтому вы можете проверить смешные значения. На исправлениях местоположения Android есть мера точности. Также, если вы видите количество используемых спутников, это также может указывать на точность.

Интересный способ получить лучшее представление о точности может заключаться в том, чтобы очень быстро запросить набор исправлений, например, ~ 1 / сек в течение 10 секунд, а затем поспать минуту или две. Один разговор, на котором я был, привел к убеждению, что некоторые Android-устройства будут делать это в любом случае. Затем вы должны отсеять выбросы (я слышал, что фильтр Калмана упоминался здесь) и использовать какую-то стратегию центрирования, чтобы получить одно исправление.

Очевидно, глубина, к которой вы попадаете, зависит от того, насколько сложны ваши требования. Если вы предъявляете особенно строгие требования к получению ЛУЧШЕГО местоположения, я думаю, вы обнаружите, что GPS и сетевое местоположение такие же, как яблоки и апельсины. Также GPS может сильно отличаться от устройства к устройству.

буксиры
источник
Ну, это не важно, что это самое лучшее, просто достаточно хорошо наносить на карту и что я не разряжаю батарею, так как это фоновая задача.
Никлас А.
-3

У Skyhook (http://www.skyhookwireless.com/) есть провайдер местоположения, который намного быстрее, чем стандартный, предоставляемый Google. Это может быть то, что вы ищете. Я не связан с ними.

Эд Бернетт
источник
Интересно, что они, кажется, используют только Wi-Fi, но это очень хорошо, но мне все еще нужно, чтобы он работал, когда вокруг нет соединения Wi-Fi или 3G / 2G, так что это добавило бы еще один уровень абстракции. Хороший улов, хотя.
Никлас А.
1
Skyhook использует комбинацию WiFi, GPS и сотовых вышек. Смотрите skyhookwireless.com/howitworks для технических деталей. В последнее время они получили несколько побед в дизайне, например, Mapquest, Twydroid, ShopSavvy и Sony NGP. Обратите внимание, что загрузка и проверка их SDK, по-видимому, бесплатны, но вам необходимо связаться с ними по поводу лицензии для распространения в вашем приложении. К сожалению, они не указывают цену на своем веб-сайте.
Эд Бернетт
О, я вижу. Ну, если это не бесплатно для коммерческого использования, то, боюсь, я не смогу это использовать.
Никлас А.