在Android中获取用户位置的好方法

212

问题:

尽快获取用户的当前位置,并在同时节省电池。

为什么这是个问题:

首先,Android有两种提供者:网络和GPS。有时网络更好,有时GPS更好。

所谓“更好”,我是指速度与准确性之间的比率。
如果我可以立即获得位置并且不需要打开GPS,我愿意牺牲准确性的几米。

其次,如果您请求更新位置更改,则当前位置稳定时不会发送任何内容。

这里Google提供了确定“最佳”位置的示例:http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
但我认为它还远远没有达到应该/可以的水平。

我有点困惑为什么google没有规范化的位置API,开发人员不应该关心位置来自哪里,您应该只需指定您想要的位置并且手机应该为您选择。

我需要帮助:

我需要找到一个好的方法来确定“最佳”位置,也许是通过一些启发式或通过某些第三方库。

这不意味着确定最佳提供商!
我可能会使用所有提供者并从中选择最好的。

应用背景:

该应用程序将按固定间隔(比如每10分钟)收集用户的位置并将其发送到服务器。
应用程序应尽可能节省电池,并且位置应具有X(50-100?)米的准确性。

目标是稍后能够在地图上绘制用户的路径,因此我需要足够的准确度。

其他信息:

您认为期望准确性和接受准确性的合理值是多少?
我一直在使用100m作为接受的准确度,30m作为期望的准确度,这是否太过分了?
我想能够稍后在地图上绘制用户的路径。
100m作为期望值和500m作为接受值是否更好?

此外,现在我将GPS设置为每个位置更新的最长60秒,如果您在室内并且精度可能达到200m,则这是否太短了?


这是我的当前代码,欢迎任何反馈(除了缺乏错误检查外,这是要做的事情):

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

8
虽然我来晚了,但最近在2013年IO大会上宣布的“融合位置提供程序”似乎能够满足你的许多需求--https://developer.android.com/google/play-services/location.html - Matthew
getBestLocation() 的最后一行应该是 return currentBestLocation; 而非 return bestLocation;,对吗? - Gavriel
10个回答

167

看起来我们正在编写同一个应用程序 ;-)
这是我的当前实现。我仍然处于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,stopRecording仅调用了gpsTimer.cancel()并设置gps_recorder_running=false,因此就像在您的情况下一样,当时没有清理侦听器。当前代码在向量中跟踪所有活动侦听器,我在1.5年前写这个答案时没有这个功能。 - Gryphius
1
它已经在Github上了,但我不确定这是否仍然是现今处理GPS的最佳方式。据我所知,自从我编写此代码以来,他们对位置API进行了许多改进。 - Gryphius
显示剩余16条评论

33
为了选择适合您应用程序的位置提供者,您可以使用 Criteria 对象:
Criteria myCriteria = new Criteria();
myCriteria.setAccuracy(Criteria.ACCURACY_HIGH);
myCriteria.setPowerRequirement(Criteria.POWER_LOW);
// let Android select the right location provider for you
String myProvider = locationManager.getBestProvider(myCriteria, true); 

// finally require updates at -at least- the desired rate
long minTimeMillis = 600000; // 600,000 milliseconds make 10 minutes
locationManager.requestLocationUpdates(myProvider,minTimeMillis,0,locationListener); 

阅读requestLocationUpdates文档以了解如何考虑参数的详细信息:

可以使用minTime和minDistance参数控制通知的频率。 如果minTime大于0,则为了节省电力,LocationManager在位置更新之间可能会休眠minTime毫秒。 如果minDistance大于0,则仅在设备移动minDistance米时才广播位置。 要尽可能频繁地接收通知,请将两个参数都设置为0。

更多想法

  • 您可以使用Location.getAccuracy()来监视位置对象的准确性,该函数返回位置的估计准确度(以米为单位)。
  • Criteria.ACCURACY_HIGH标准应该可以给出低于100m的误差,虽然不是GPS的最佳精度,但足够满足您的需求。
  • 您还需要监视位置提供程序的状态,并在其不可用或被用户禁用时切换到另一个提供程序。
  • 被动提供程序也可能非常适合这种应用程序:其思想是在其他应用程序请求位置更新并广播系统范围时使用位置更新。

我已经研究了“Criteria”但是如果最新的网络位置很棒(可能通过wifi知道),而且获取它不需要时间和电池(getLastKnown),那么标准很可能会忽略它并返回GPS。 我无法相信Google为开发人员做到了这一点。 - Nicklas A.
除了使用标准,您还可以在所选提供程序发送的每个位置更新时检查 GPS 提供程序的 lastKnowLocation,并将其(精度和日期)与当前位置进行比较。但是,这对于您的规格说明来说似乎是一个不错的附加功能,如果有时能够实现更好的精度,那么它真的对您的用户有用吗? - Stéphane
这就是我现在正在做的事情,问题是我很难确定最后一个知道的是否足够好。我还可以补充说,我不必局限于一个提供者,使用的越多,我可能会更快地获得锁定。 - Nicklas A.
@Gaucho,我实际上没有看到编辑,我猜你已经撤销了它...无论如何不用担心,感谢您的审阅。 - Stéphane
我该如何获取纬度/经度? - user3402040
显示剩余2条评论

10

回答前两个问题:

  • 如果启用GPS并且周围没有厚厚的墙壁,那么GPS将始终为您提供更精确的位置。

  • 如果位置未发生变化,则可以调用getLastKnownLocation(String)并立即检索位置。

使用另一种方法:

您可以尝试获取正在使用的基站ID或所有相邻单元的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()。这避免了需要第三方数据库来获取位置。您还可以尝试这种方法


使用塔似乎对我来说有点过度杀伐,不过是个好主意。 - Nicklas A.
@Nicklas,代码不会比那更复杂。但你需要 android.Manifest.permission#ACCESS_COARSE_UPDATES。 - Aleadam
是的,但我仍然需要使用第三方服务,并且我需要一种方法来决定何时使用塔信息而不是位置数据,这只会增加额外的复杂性。 - Nicklas A.
啊,我明白了。嗯,位置可能每隔10分钟获取一次,那么你需要等待10分钟才能获取位置,这似乎有点奇怪。 - Nicklas A.
@Nicklas,你不需要。我是根据你之前说的内容来计算时间的。如果你继续在后台监听,你可以使用requestLocationUpdates(...)来设置你想要的时间间隔(甚至是1秒),但这会让手机保持唤醒状态。 - Aleadam
显示剩余3条评论

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
你能贴出整个代码吗?谢谢,非常感激。 - M4rk
这就是所有的代码了。我不再能够访问该项目。 - Nicklas A.
2
你似乎已经获取了这个项目“android-protips-location”的代码,并且它仍然存在。人们可以在此处查看其工作原理:https://code.google.com/p/android-protips-location/source/browse/trunk/src/com/radioactiveyak/location_best_practices/UI/PlaceActivity.java - 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

目前我正在使用这种方法,因为它是可靠的用于获取位置和计算我的出租车应用程序的距离......我正在使用这种方法。

使用谷歌开发者开发的融合API,将GPS传感器、磁力计、加速度计与Wifi或移动位置相结合,来计算或估算位置。它还能够准确地在建筑物内更新位置。 详情请参见链接 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)。 我最终找到了这个:

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

我创建了一个新项目,并复制了大部分代码。嘭,它可以工作了。我认为没有任何过时的代码。
此外,仿真器似乎无法获取GPS位置,至少我不知道。它只在日志中报告了这一点:“所有位置设置都已满足”。
最后,如果您想知道(我想知道),如果您只想要GPS位置,则不需要从Google开发者控制台获得Google Maps API密钥。
他们的教程也很有用。但是我想要一个完整的一页教程/代码示例,他们的教程很混乱,因为当您刚开始时,您不知道需要从早期页面中获取哪些部分。

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

最后,请记住这些事情:
我不仅必须修改mainActivity.java,还必须修改Strings.xml、androidmanifest.xml和正确的build.gradle。同时,也需要修改activity_Main.xml(但对我来说这部分很容易)。
我需要添加类似于这样的依赖项:implementation 'com.google.android.gms:play-services-location:11.8.0',并更新我的Android Studio SDK设置以包括Google Play服务(文件设置外观系统设置Android SDK SDK工具勾选Google Play服务)。
更新:Android模拟器似乎能够获取位置和位置变更事件(当我在模拟器的设置中更改了值)。但是我获得的最好和第一个结果是在实际设备上。所以最好在实际设备上测试。

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.每次请求完成定位后,最好使用removeUpdates移除位置更新,否则手机状态栏将一直显示定位图标。


0
在我的经验中,我发现最好使用GPS定位,除非它不可用。我不太了解其他位置提供者,但是我知道对于GPS来说,有一些技巧可以用来给出一些粗略的精度测量。海拔高度通常是一个指标,因此您可以检查荒谬的值。Android位置修复程序上也有精度测量。另外,如果您可以看到使用的卫星数量,这也可以指示出精度。
获取更好的精度的一种有趣方式可能是快速请求一组修复,例如每秒约1次,持续10秒,然后休眠一两分钟。我参加过的一个演讲使我相信,一些android设备会自动执行此操作。然后,您将淘汰掉异常值(我听说过卡尔曼滤波器),并使用某种居中策略来获得单个修复程序。
显然,您在这里达到的深度取决于您的要求有多严格。如果您有特别严格的要求以获得最佳定位,则我认为您会发现GPS和网络位置与苹果和橙子一样不同。此外,不同设备的GPS可能大相径庭。

好的,它不需要是最好的,只要足够好以在地图上绘制,并且我不会耗尽电池,因为这是一个后台任务。 - Nicklas A.

-3
Skyhook(http://www.skyhookwireless.com/)有一个定位提供程序,比Google提供的标准提供程序快得多。这可能是你正在寻找的东西。我与他们没有关联。

确实很有趣,它们似乎只使用WiFi,这非常好,但是当周围没有WiFi或3G/2G连接时,我仍然需要它能够工作,因此这将增加另一层抽象。不过,你的发现很好。 - Nicklas A.
1
Skyhook似乎使用WiFi、GPS和蜂窝塔的组合。有关技术细节,请参见http://www.skyhookwireless.com/howitworks/。他们最近赢得了几个设计奖项,例如Mapquest、Twydroid、ShopSavvy和Sony NGP。请注意,下载和尝试他们的SDK似乎是免费的,但您必须与他们联系以获取在您的应用程序中分发它的许可证。不幸的是,他们的网站上没有列出价格。 - Ed Burnette
哦,我明白了。如果它不是免费商用的话,恐怕我不能使用它。 - Nicklas A.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接