首页 文章

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

提问于
浏览
207

问题:

尽快将用户的当前位置置于阈值内,同时节省电池电量 .

为什么问题出在问题上:

首先,android有两个提供商;网络和GPS . 有时网络更好,有时GPS更好 .

"better"我的意思是速度与准确度之比 .
我愿意牺牲几米精度,如果我能在几乎立即获得位置并且不打开GPS .

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

谷歌有一个确定"best"位置的例子:http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
但我认为它不应该接近它应该/可能的好 .

我有点困惑为什么谷歌没有标准化的位置API,开发者不应该关心位置来自哪里,你应该只指定你想要的东西,手机应该为你选择 .

我需要帮助的是:

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

This does not mean determine the best provider!
我可能会使用所有提供商并选择其中最好的 .

应用程序的背景:

该应用程序将以固定间隔收集用户的位置(假设每10分钟左右)并将其发送到服务器 .
该应用程序应尽可能节省电池,并且位置应具有X(50-100?)米的精度 .

目标是以后能够在 Map 上绘制白天用户的路径,因此我需要足够的准确性 .

其他:

您认为对于期望和接受的准确度的合理 Value 是什么?
我一直在使用100米接受,并且根据需要使用30米,这要问多少?
我以后在 Map 上的路径是_876669 .
期望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);
}

12 回答

  • 7

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

    使用谷歌开发人员开发的融合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

    嗨,这是一个链接,它将能够提供整个源代码以整合gps位置的链接,该位置将能够通过gps跟踪任何人,并且ti将通知:

    喜欢:http://code.google.com/p/mytracks/

  • 1

    看起来我们正在编写相同的应用程序;-)
    这是我目前的实施 . 我仍处于GPS上传应用程序的beta测试阶段,因此可能会有许多改进 . 但到目前为止似乎工作得很好 .

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

    Edit: 这是请求来自位置提供者的定期更新的部分:

    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
    }
    

    Things to consider:

    • 不要经常请求GPS更新,它会耗尽电池电量 . 我目前使用30分钟默认为我的应用程序 .

    • 添加“到最后已知位置的最小距离”检查 . 如果没有这个,当GPS不可用时,你的点将“跳转”,并且位置正从手机信号塔进行三角测量 . 或者您可以检查新位置是否超出上一个已知位置的准确度值 .

  • 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可能会在位置更新之间休息几分钟,以节省电量 . 如果minDistance大于0,则仅当设备移动minDistance米时才会广播位置 . 要尽可能频繁地获取通知,请将两个参数都设置为0 .

    更多想法

    • 您可以使用Location.getAccuracy()监视位置对象的精度,它以米为单位返回位置的估计精度 .

    • Criteria.ACCURACY_HIGH 标准应该给你100米以下的误差,这不如GPS可以,但符合你的需要 .

    • 您还需要监控位置提供程序的状态,并在用户不可用或禁用时切换到其他提供程序 .

    • passive provider也可能是这种应用程序的一个很好的匹配:想法是在其他应用程序请求并在系统范围内广播时使用位置更新 .

  • 9

    Answering the first two points

    • GPS将 always 为您提供更精确的位置,如果它已启用且周围没有厚墙 .

    • 如果位置没有改变,那么你可以调用getLastKnownLocation(String)并立即检索位置 .

    Using an alternative approach

    您可以尝试使用 cell 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() . 这避免了对位置的第三方数据库的需要 . 你也可以试试this approach .

  • 3

    这个是我的解决方案,运作得相当好:

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

    位置准确性主要取决于所使用的位置提供商:

    • GPS - 将为您提供几米精度(假设您有GPS接收)

    • Wifi - 精确到几百米

    • Cell Network - 会给你带来非常不准确的结果(我已经看到了4公里偏差......)

    如果您正在寻找它的准确性,那么GPS是您唯一的选择 .

    我已经阅读了一篇非常有益的文章here .

    至于GPS超时 - 60秒应该足够了,在大多数情况下甚至太多了 . 我认为30秒是可以的,有时甚至不到5秒......

    如果您只需要一个位置,我建议您在 onLocationChanged 方法中,一旦收到更新,您将取消注册听众并避免不必要地使用GPS .

  • 10

    Android-ReactiveLocation库是另一个处理Android位置的好库 .

    在精彩的RxJava Observable中包装Google Play Service API的小型库,将样板文件减少到最小 .

    https://github.com/mcharmas/Android-ReactiveLocation

  • 3

    我使用谷歌建议的最新位置拉动方法(使用FusedLocationProviderClient)在互联网上搜索更新(过去一年)的答案 . 我终于登陆了这个:

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

    我创建了一个新项目并复制了大部分代码 . 繁荣 . 有用 . 我认为没有任何弃用的行 .

    此外,我知道,模拟器似乎没有获得GPS位置 . 它确实在日志中报告了这一点:“满足所有位置设置 . ”

    最后,如果你想知道(我做过),你不需要谷歌开发者控制台的谷歌 Map api密钥,如果你想要的只是GPS位置 .

    他们的教程也很有用 . 但是我想要一个完整的单页教程/代码示例 . 他们的教程堆栈,但是当你刚接触到它时会感到困惑,因为你不知道从早期页面需要哪些部分 .

    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模拟器似乎确实获得了位置和位置更改事件(当我更改了sim的设置中的值) . 但我最好的和最初的结果是在一个实际的设备上 . 因此,在实际设备上进行测试可能最容易 .

  • 0

    根据我的经验,我发现最好使用GPS定位,除非它不可用 . 我不太了解其他位置提供商,但我知道对于GPS来说,有一些技巧可以用来给出一些贫民窟精确度量 . 海拔通常是一个标志,所以你可以检查荒谬的 Value 观 . Android位置修复程序有准确度衡量标准 . 此外,如果您可以看到使用的卫星数量,这也可以表明精度 .

    一个有趣的方法来更好地了解准确性可能是非常迅速地要求一组修复,例如~1 /秒持续10秒然后再睡一两分钟 . 我去过的一次谈话导致相信一些Android设备无论如何都会这样做 . 然后你会淘汰异常值(我听过这里提到的卡尔曼滤波器)并使用某种中心策略来获得单一修复 .

    显然,你到达这里的深度取决于你的要求有多难 . 如果您对获得最佳位置有特别严格的要求,我想您会发现GPS和网络位置与苹果和橙子相似 . GPS也可能因设备而异 .

  • 162

    最近重构了获取代码的位置,学习了一些好的想法,最后实现了一个比较完善的库和Demo .

    @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

    Skyhook(http://www.skyhookwireless.com/)的位置提供商比Google提供的标准提供商快得多 . 这可能就是你要找的东西 . 我不隶属于他们 .

相关问题