안드로이드에서 사용자의 위치를 ​​얻는 좋은 방법


211

문제 :

최대한 빨리 임계 값 내에 사용자의 현재 위치를 확보하고 배터리를 절약합니다.

문제가 문제인 이유 :

우선, 안드로이드에는 두 개의 제공자가 있습니다. 네트워크 및 GPS. 때로는 네트워크가 더 좋고 때로는 GPS가 더 좋습니다.

"더 나은"이란 속도 대 정확도 비율을 의미합니다.
GPS를 켜지 않고도 위치를 거의 즉각적으로 확보 할 수 있다면 몇 미터의 정확도를 기꺼이 희생하려고합니다.

둘째, 위치 변경에 대한 업데이트를 요청하면 현재 위치가 안정적인 경우 아무것도 전송되지 않습니다.

구글은 여기에 "최고"의 위치를 결정하는 예를 가지고 : http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
하지만 예상대로이 좋은대로 아무 곳 근처 생각하지 /있을 수 있습니다.

Google이 위치에 대해 표준화 된 API를 사용하지 않은 이유는 혼란 스럽습니다. 개발자가 위치를 신경 쓰지 않아도됩니다. 원하는 것을 지정하고 전화를 선택해야합니다.

내가 도움이 필요한 것 :

휴리스틱이나 타사 라이브러리를 통해 "최상의"위치를 결정하는 좋은 방법을 찾아야합니다.

이것은 최고의 공급자를 결정한다는 의미는 아닙니다!
아마 모든 제공자를 사용하고 최선을 다할 것입니다.

앱의 배경 :

앱은 고정 된 간격으로 (10 분 정도 말하자) 사용자의 위치를 ​​수집하여 서버로 보냅니다.
앱은 가능한 한 많은 배터리를 보존해야하며 위치는 X (50-100?) 미터 정확도를 가져야합니다.

목표는 나중에 낮에 사용자의 경로를지도에 표시하여 충분한 정확도가 필요하다는 것입니다.

기타 :

원하는 정확도와 허용 정확도에 대한 합리적인 가치는 무엇이라고 생각하십니까?
나는 100m을 허용하고 30m을 원하는대로 사용하고 있습니다.
나중에지도에서 사용자의 경로를 그릴 수 있기를 원합니다.
원하는 경우 100m, 허용되는 경우 500m가 더 좋습니까?

또한 현재 위치 업데이트 당 최대 60 초 동안 GPS를 켰습니다. 200m 정도의 정확도로 실내에있을 경우 위치를 찾기에는 너무 짧습니까?


이것은 현재 코드이며, 피드백은 감사합니다 (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에서 최근 발표 된 "Fused Location Provider"는 많은 요구를 충족시키는 것처럼 보입니다 ( developer.android.com/google/play-services/location.html
Matt

getBestLocation ()의 마지막 행이 아니어야합니다. return currentBestLocation; bestLocation;을 반환하는 대신?
Gavriel

답변:


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를 수시로 켜서 위치를 업데이트하는 리스너를 실제로 추가하면이 코드가 크게 도움이 될 것이라고 생각합니다.
Nicklas A.

2
죄송합니다. 사용 가능한 모든 위치에서 가장 적합한 제품에만 관심이 있다고 생각했습니다. 위의 코드도 추가했습니다. 새로운 GPS 위치가 수신되면 즉시 저장 / 업로드됩니다. 네트워크 위치 업데이트를 받으면 참조 용으로 저장하고 다음 위치 확인이 발생할 때까지 GPS 업데이트를받을 수 있도록 '희망'합니다.
Gryphius

2
타이머를 취소 한 stopRecording () 메서드도 있습니다. 결국 타이머에서 ScheduledThreadPoolExecutor로 전환했기 때문에 stopRecording은 기본적으로 executor.shutdown ()을 호출하고 모든 위치 업데이트 리스너를 등록 해제합니다.
Gryphius

1
내 scm에 따르면 gpsTimer.cancel () 만 중지하고 gps_recorder_running = false 만 설정 했으므로 귀하의 경우와 같이 리스너를 정리하지 않습니다. 현재 코드는 벡터의 모든 활성 리스너를 추적하므로 1.5 년 전에이 답변을 썼을 때이 없었습니다.
Gryphius

1
이미 github 에 있지만 이것이 오늘날 GPS 작업을 수행하는 가장 좋은 방법인지 확실하지 않습니다. Afaik 은이 코드를 작성한 이후 위치 API를 많이 개선했습니다.
Gryphius

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는 위치 업데이트 사이에 minTime 밀리 초 동안 전력을 보존 할 수 있습니다. minDistance가 0보다 큰 경우 장치가 minDistance 미터로 이동 한 경우에만 위치가 브로드 캐스트됩니다. 가능한 한 자주 알림을 받으려면 두 매개 변수를 모두 0으로 설정하십시오.

더 많은 생각

  • Location.getAccuracy () 를 사용하여 Location 객체의 정확도를 모니터링 할 수 있습니다. 이는 위치 의 예상 정확도를 미터 단위로 반환합니다.
  • Criteria.ACCURACY_HIGH기준은 100m 미만의 오류를 발생시켜 GPS만큼 좋지는 않지만 사용자의 요구와 일치합니다.
  • 또한 위치 제공자의 상태를 모니터하고 사용자가 사용할 수 없거나 비활성화 된 경우 다른 제공자로 전환해야합니다.
  • 수동 제공자는 또한 응용 프로그램의 이런 종류의 좋은 일치 될 수있다 : 아이디어는 다른 응용 프로그램 및 방송 시스템 전체에 의해 요청 될 때마다 위치 업데이트를 사용하는 것입니다.

나는 살펴 보았지만 Criteria최신 네트워크 위치가 훌륭하고 (wifi를 통해 알 수 있음) 그것을 얻는 데 시간이나 배터리가 필요하지 않은 경우 (getLastKnown), 기준은 아마도 그것을 무시하고 대신 GPS를 반환합니다. 나는 구글이 개발자들에게 이것을 어렵게 만들었다 고 믿을 수 없다.
Nicklas A.

기준을 사용하는 것 외에도 선택한 제공자가 보낸 각 위치 업데이트에서 GPS 제공자의 lastKnowLocation을 확인하고 현재 위치와 정확도 (날짜 및 날짜)를 비교할 수 있습니다. 그러나 이것은 나에게 당신의 사양의 요구 사항보다는 좋은 것 같습니다. 때로는 더 나은 정확도가 달성되면 사용자에게 실제로 유용합니까?
Stéphane

그것이 내가 지금하고있는 일입니다. 문제는 마지막 지식이 충분히 좋은지 알아내는 데 어려움을 겪고 있다는 것입니다. 또한 하나의 공급자로 나 자신을 제한 할 필요가 없다고 덧붙일 수 있습니다. 더 많이 사용할수록 더 빨리 자물쇠를 얻을 수 있습니다.
Nicklas A.

PASSIVE_PROVIDER에는 API 레벨 8 이상이 필요합니다.
Eduardo

@ Stéphane 편집을해서 죄송합니다. 조심하지 마십시오. 게시물이 정확합니다. 오류로 편집했습니다. 죄송합니다. 문안 인사.
가우초

10

처음 두 지점에 응답 :

  • GPS 가 활성화되어 있고 주변에 두꺼운 벽이없는 경우 GPS는 항상 보다 정확한 위치를 제공합니다 .

  • 위치가 변경되지 않은 경우 getLastKnownLocation (String)을 호출 하고 위치를 즉시 검색 할 수 있습니다 .

다른 방법을 사용하여 :

사용중인 셀 ID 또는 모든 인접 셀을 가져 오려고 시도 할 수 있습니다.

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


전략은 위치를 읽을 때 타워 ID 목록을 읽는 것입니다. 그런 다음 다음 쿼리 (앱에서 10 분)에서 다시 읽으십시오. 적어도 일부 타워가 동일한 경우을 사용하는 것이 안전합니다 getLastKnownLocation(String). 그렇지 않은 경우 기다립니다 onLocationChanged(). 따라서 위치에 타사 데이터베이스가 필요하지 않습니다. 이 방법을 시도해 볼 수도 있습니다 .


그래, 그러나 lastKnownLocation이 실제로 나쁜 경우 문제가 발생합니다. 두 곳 중 가장 좋은 곳을 결정하는 좋은 방법이 필요합니다.
Nicklas A.

타워 정보를 저장하고 타워가 변경되었는지 확인할 수 있습니다. 그럴 경우 새 위치를 기다리거나 기다리지 않은 경우 (또는 일부만 변경된 경우) 재사용하십시오. 이렇게하면 타워 위치를 데이터베이스와 비교하지 않아도됩니다.
Aleadam

타워를 사용하는 것은 나에게 큰 과잉 인 것처럼 보이지만 좋은 생각입니다.
Nicklas A.

@Nicklas 코드는 그보다 더 복잡하지 않습니다. 그래도 android.Manifest.permission # ACCESS_COARSE_UPDATES가 필요합니다.
Aleadam

예, 그러나 여전히 타사 서비스를 사용해야하며 위치 데이터를 통해 타워 정보를 언제 사용할지 결정하는 방법이 필요합니다. 이로 인해 복잡성이 추가됩니다.
Nicklas A.

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

안녕하세요 Nicklas 나는 같은 장비를 가지고 있기 때문에 어떤 방법 으로든 당신에게 연락 할 수 있습니다 .. 당신이 우리를 도울 수 있다면 나는 당신에게 전적으로 감사 할 것입니다 ..
School Boy

전체 코드를 게시 할 수 있습니까? 감사합니다, 정말 감사합니다
rodi

이것이 모든 코드입니다. 더 이상 프로젝트에 액세스 할 수 없습니다.
Nicklas A.

1
이 프로젝트의 코드를 "android-protips-location"으로 보았으며 여전히 살아 있습니다. 사람들은 여기에서 어떻게 작동하는지 확인할 수 있습니다. code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77

7

위치 정확도는 주로 사용 된 위치 공급자에 따라 다릅니다.

  1. GPS-몇 미터의 정확도를 제공합니다 (GPS 수신이 있다고 가정).
  2. Wi-Fi-몇 백 미터의 정확도를 제공합니다
  3. Cell Network-매우 부정확 한 결과를 얻습니다 (최대 4km 편차를 보았습니다 ...)

찾고자하는 정확성이라면 GPS가 유일한 옵션입니다.

나는 그것에 대해 매우 유익한 기사를 읽었습니다 여기에 .

GPS 타임 아웃은 60 초면 충분하고 대부분의 경우 너무 큽니다. 나는 30 초는 괜찮고 때로는 5 초 미만이라고 생각합니다 ...

단일 위치 만 필요한 경우 onLocationChanged업데이트 된 메소드를 수신하면 리스너의 등록을 취소하고 GPS의 불필요한 사용을 피할 것을 제안합니다 .


나는 내 위치를 어디에서 보든 상관하지 않으며, 한 명의 제공자로 제한하고 싶지 않습니다
Nicklas A.

장치에서 사용할 수있는 모든 위치 공급자를 등록 할 수 있습니다 (LocationManager.getProviders ()에서 모든 공급자 목록을 얻을 수 있음). 정확한 수정을 원할 경우 대부분의 경우 네트워크 공급자는 유용하지 않습니다.
Muzikant

네, 그러나 이것은 공급자들 사이의 선택에 관한 질문이 아닙니다. 이것은 일반적으로 최상의 위치를 ​​얻는 것에 관한 질문입니다 (여러 제공자가 관련되어있는 경우에도)
Nicklas A.

4

현재 위치를 얻고 응용 프로그램의 거리를 계산하는 것이 신뢰할 수 있기 때문에 현재 사용하고 있습니다 .... 택시 응용 프로그램에 이것을 사용하고 있습니다.

Google 개발자가 Wi-Fi 또는 셀 위치를 사용하여 GPS 센서, 마그네토 미터, 가속도계의 융합으로 개발 한 퓨전 API를 사용하여 위치를 계산하거나 추정합니다. 또한 건물 내부의 위치를 ​​정확하게 업데이트 할 수도 있습니다. 자세한 내용은 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

인터넷에서 FusedLocationProviderClient를 사용하기 위해 Google에서 제안한 최신 위치 추출 방법을 사용하여 업데이트 된 (지난 해) 답변을 얻었습니다. 나는 마침내 이것에 착륙했다.

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

새 프로젝트를 만들고이 코드의 대부분을 복사했습니다. 팔. 효과가있다. 그리고 더 이상 사용되지 않는 줄이 없다고 생각합니다.

또한 시뮬레이터는 내가 알고있는 GPS 위치를 얻지 못하는 것 같습니다. "모든 위치 설정이 만족되었습니다."

그리고 마지막으로, 알고 싶을 경우 (내가 했음) GPS 위치 만 있으면 Google 개발자 콘솔에서 Google 맵 API 키가 필요하지 않습니다.

또한 튜토리얼도 유용합니다. 그러나 전체 한 페이지 자습서 / 코드 예제를 원했습니다. 튜토리얼은 쌓이지 만 이전 페이지에서 어떤 조각이 필요한지 모르기 때문에 처음 접할 때 혼란 스럽습니다.

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'과 같은 종속성을 추가하고 Google Play 서비스를 포함하도록 내 Android Studio SDK의 설정을 업데이트해야했습니다. (파일 설정 모양 시스템 설정 안드로이드 SDK SDK 도구는 구글 플레이 서비스를 확인).

업데이트 : 안드로이드 시뮬레이터는 위치 및 위치 변경 이벤트를 얻는 것처럼 보입니다 (심의 설정에서 값을 변경했을 때). 그러나 내 최고의 첫 번째 결과는 실제 장치였습니다. 따라서 실제 장치에서 테스트하는 것이 가장 쉽습니다.


1

최근 코드의 위치를 ​​얻기 위해 리팩터링하고 좋은 아이디어를 배우고 마침내 비교적 완벽한 라이브러리와 데모를 달성했습니다.

@Gryphius의 대답은 좋습니다

    //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 수정과 함께 사용하는 것이 가장 좋습니다. 다른 위치 제공 업체에 대해서는 잘 모르지만 GPS에는 약간의 빈민가 정밀 측정을 제공하는 데 사용할 수있는 몇 가지 트릭이 있다는 것을 알고 있습니다. 고도는 종종 부호이므로 우스운 값을 확인할 수 있습니다. Android 위치 수정에 대한 정확도 측정이 있습니다. 또한 사용 된 위성의 수를 볼 수 있으면 정확도를 나타낼 수도 있습니다.

정확도에 대한 더 나은 아이디어를 얻는 흥미로운 방법은 ~ 1 / sec와 같이 10 초 동안 매우 빠르게 수정을 요청한 다음 1 ~ 2 분 동안 잠자기하는 것입니다. 내가 한 이야기는 일부 안드로이드 장치가 어쨌든 이것을 할 것이라고 믿게했습니다. 그런 다음 특이 치를 제거하고 (여기서는 칼만 필터를 들었습니다) 일종의 센터링 전략을 사용하여 단일 수정을 얻습니다.

분명히 여기에 도달하는 깊이는 요구 사항이 얼마나 어려운지에 달려 있습니다. 최고의 위치를 ​​확보하기 위해 특별히 엄격한 요구 사항이있는 경우 GPS 및 네트워크 위치가 사과 및 오렌지와 비슷하다는 것을 알 수 있습니다. 또한 GPS는 기기마다 크게 다를 수 있습니다.


글쎄, 그것이 최고라는 것이 중요하지 않습니다.지도에 표시하기에 충분하고 배경 작업이므로 배터리를 소모하지 않는다는 것입니다.
Nicklas A.

-3

Skyhook (http://www.skyhookwireless.com/)에는 Google이 제공하는 표준보다 훨씬 빠른 위치 제공 업체가 있습니다. 당신이 찾고있는 것일 수도 있습니다. 나는 그들과 제휴하지 않습니다.


흥미롭게도 WiFi 만 사용하는 것 같지만 매우 훌륭하지만 Wi-Fi 또는 3G / 2G 연결이 없을 때 작동해야 다른 추상화 계층이 추가됩니다. 그래도 잘 잡는다.
Nicklas A.

1
Skyhook은 WiFi, GPS 및 셀 타워의 조합을 사용하는 것으로 보입니다. 자세한 기술 내용 은 skyhookwireless.com/howitworks 를 참조 하십시오. 최근 Mapquest, Twydroid, ShopSavvy 및 Sony NGP와 같은 몇 가지 디자인 상을 수상했습니다. SDK를 다운로드하여 사용해 보는 것은 무료 인 것으로 보이지만 앱에 배포하기위한 라이센스에 대해서는 SDK에 문의해야합니다. 불행히도 그들은 웹 사이트에 가격을 표시하지 않습니다.
Ed Burnette 2016 년

아, 알겠습니다 글쎄, 상업적으로 무료로 사용할 수 없다면 사용할 수 없을 것입니다.
Nicklas A.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.