Хороший спосіб отримати місцезнаходження користувача в Android


211

Проблема:

Отримання поточного місцезнаходження користувача в межах порогу якнайшвидшого і одночасно заощадження акумулятора.

Чому проблема - це проблема:

По-перше, Android має двох постачальників; мережа та GPS. Іноді мережа краща, а іноді GPS - краща.

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

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

У Google є приклад визначення "найкращого" місця тут: http://developer.android.com/guide/topics/location/obthing-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 () бути: повернути 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 роки тому.
Грифій

1
це вже на github , але я не впевнений, що це все-таки найкращий спосіб робити GPS-речі в наш час. Афаїк, вони зробили багато вдосконалень у API API з моменту написання цього коду.
Грифій

33

Щоб вибрати потрібного постачальника місцеположень для вашої програми, ви можете використовувати об’єкти Критерії :

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 може потенційно відпочити протягом декількох міліметрових секунд між оновленнями місць для збереження енергії. Якщо minDistance більше 0, то місце відображатиметься лише в тому випадку, якщо пристрій переміщається за допомогою вимірювачів minDistance. Щоб отримувати сповіщення якомога частіше, встановіть обидва параметри 0.

Більше думок

  • Ви можете відстежувати точність об'єктів Location за допомогою Location.getAccuracy () , який повертає оцінену точність положення в метрах.
  • Criteria.ACCURACY_HIGHкритерій повинен дати вам помилку нижче 100 м, що не так добре , як GPS може бути, але відповідають вашим потребам.
  • Вам також слід відстежувати стан свого постачальника послуг та перейти на іншого постачальника, якщо користувач стає недоступним або відключений.
  • Пасивний постачальник може бути також добре підходить для такого застосування: ідея полягає в тому , щоб використовувати дані про місцезнаходження , коли вони запитуються іншим додатком і мовлення Systemwide.

Я роздивився, Criteriaале що, якщо остання мережева адреса є приголомшливою (це може знати через Wi-Fi), і для її отримання не потрібно часу чи батареї (getLastKknown), то критерії, ймовірно, не зважають на це і повертають GPS замість цього. Не можу повірити, що Google ускладнив розробників це важко.
Ніклас А.

Крім використання критеріїв, ви зможете під час кожного оновлення місцеположення, надісланого вибраним провайдером, перевірити lastKnowLocation для постачальника GPS та порівняти його (точність та дату) з вашим поточним місцезнаходженням. Але це здається мені приємним, а не вимогою з ваших специфікацій; якщо іноді досягається дещо краща точність, чи справді вона буде корисна вашим користувачам?
Стефан

Це те, що я зараз роблю. Проблема полягає в тому, що я важко з'ясовую, чи є остання інформація достатньо хорошою. Я також можу додати, що мені не потрібно обмежуватися одним постачальником, чим більше я використовую, тим швидше я можу отримати замок.
Ніклас А.

Майте на увазі, що PASSIVE_PROVIDER вимагає рівня API 8 або вище.
Едуардо

@ Stéphane вибачте за редагування. Не дбайте про це. Ваше повідомлення правильне. Я зробив це редагування для помилки. Вибачте. З повагою
Гаучо

10

Відповідь на перші два пункти :

  • GPS завжди надасть точніше розташування, якщо воно ввімкнено і якщо навколо немає товстих стін .

  • Якщо місцезнаходження не змінилося, ви можете зателефонувати на getLastKknownLocation (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(). Це дозволяє уникнути необхідності використання сторонніх баз даних для місцезнаходження. Ви також можете спробувати такий підхід .


Так, але у мене проблема виникає, якщо lastKknownLocation дійсно поганий. Мені потрібен гарний спосіб вирішити найкраще з двох локацій.
Ніклас А.

Ви можете зберігати інформацію про вежі та перевіряти, чи змінилися ці вежі. Якщо вони це зробили, то зачекайте на нове місце розташування, якщо ні (або якщо лише деякі змінилися), то використовуйте його повторно. Таким чином ви уникаєте порівнювати місця розташування вежі з базою даних.
Алеадам

Використання башти здається мені великим зайвим, хоча гарна ідея.
Ніклас А.

@Nicklas код не стає складнішим за це. Вам знадобиться 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);
}

Привіт, Ніклас, у мене таке ж налагодження, щоб я міг спілкуватися з вами будь-якими способами .. Я був би вдячний вам, якщо ви могли б нам допомогти ..
Школярний хлопчик

Чи можете ви опублікувати весь код? Дякую, дуже вдячний
rodi

Ось і весь код. У мене вже немає доступу до проекту.
Ніклас А.

1
Ви, схоже, взяли код цього проекту "android-protips-location", і він все ще живий. Люди можуть побачити, як це працює тут code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77

7

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

  1. GPS - отримає точність на кілька метрів (якщо припустимо, що ви отримаєте GPS)
  2. Wifi - Ви отримаєте точність на кілька сотень метрів
  3. Мобільна мережа - отримає дуже неточні результати (я бачив відхилення до 4 км ...)

Якщо ви шукаєте точність, то GPS - це ваш єдиний варіант.

Я прочитав дуже інформативну статтю про це тут .

Щодо часу очікування GPS - 60 секунд повинно бути достатньо, а в більшості випадків навіть занадто багато. Я думаю, що 30 секунд добре, а іноді навіть менше 5 секунд ...

якщо вам потрібне лише одне місце, я б запропонував, що onLocationChangedколи ви отримаєте оновлення, ви скасуєте реєстрацію слухача та уникнете зайвого використання GPS.


Мені не цікаво, звідки я дістався свого місця, я не хочу обмежувати мене одним провайдером
Nicklas A.

Ви можете зареєструвати всіх доступних постачальників послуг на пристрої (список усіх провайдерів ви можете отримати від LocationManager.getProviders ()), але якщо ви шукаєте точне виправлення, у більшості випадків провайдер мережі не буде корисним для вас.
Muzikant

Так, але це не питання щодо вибору між провайдерами, це питання про те, щоб отримати найкраще місце взагалі (навіть коли задіяно кілька провайдерів)
Nicklas A.

4

В даний час я використовую, оскільки це надійно для отримання місця розташування та обчислення відстані для моєї програми ...... я використовую це для своєї програми таксі.

використовуйте Fusion API, який розробник 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;
        }
    }
}

2

Я обшукував Інтернет в оновленому (минулому році) відповіді, використовуючи останні методи витягування місцеположення, запропоновані google (для використання FusedLocationProviderClient). Я нарешті приземлився на цьому:

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

Я створив новий проект і скопіював більшу частину цього коду. Бум. Це працює. І я думаю без будь-яких застарілих ліній.

Крім того, тренажер, схоже, не має 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).

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


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.Кожен запит про завершення місцезнаходження, найкраще видалити оновлення, інакше рядок стану телефону завжди відображатиме піктограму позиціонування


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 видається безкоштовним, але вам потрібно зв’язатися з ними щодо ліцензії на розповсюдження його у вашому додатку. На жаль, вони не перераховують ціну на своєму веб-сайті.
Ед Бернетт

О Я бачу. Ну, якщо це не вільно використовувати комерційно, то, я боюся, я не можу ним користуватися.
Ніклас А.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.