Boa maneira de obter a localização do usuário no Android

211

O problema:

Obter a localização atual do usuário dentro de um limite o mais rápido possível e, ao mesmo tempo, economizar bateria.

Por que o problema é um problema:

Primeiro, o Android tem dois provedores; rede e GPS. Às vezes a rede é melhor e às vezes o GPS é melhor.

Por "melhor", quero dizer velocidade versus precisão.
Estou disposto a sacrificar alguns metros de precisão se conseguir obter a localização quase instantaneamente e sem ligar o GPS.

Em segundo lugar, se você solicitar atualizações para alterações no local, nada será enviado se o local atual estiver estável.

O Google tem um exemplo de como determinar a "melhor" localização aqui: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Mas acho que não é tão bom quanto deveria /poderia ser.

Estou meio confuso por que o google não possui uma API normalizada para localização, o desenvolvedor não precisa se preocupar com a localização, você deve especificar o que deseja e o telefone deve escolher.

Com o que preciso de ajuda:

Preciso encontrar uma boa maneira de determinar o "melhor" local, talvez com alguma heurística ou talvez através de uma biblioteca de terceiros.

Isso não significa determinar o melhor provedor!
Provavelmente vou usar todos os provedores e escolher o melhor deles.

Antecedentes da aplicação:

O aplicativo coletará a localização do usuário em um intervalo fixo (digamos a cada 10 minutos ou mais) e a enviará para um servidor.
O aplicativo deve economizar o máximo possível de bateria e o local deve ter uma precisão de X (50-100?) Metros.

O objetivo é mais tarde poder traçar o caminho do usuário durante o dia em um mapa, para que eu precise de precisão suficiente para isso.

Outros:

Na sua opinião, quais são os valores razoáveis ​​das precisões aceitas e desejadas?
Eu tenho usado 100m como aceito e 30m como desejado, isso é pedir muito?
Gostaria de poder traçar o caminho do usuário em um mapa posteriormente.
100m para o desejado e 500m para o aceito são melhores?

Além disso, no momento, eu tenho o GPS ligado por no máximo 60 segundos por atualização de local. Isso é muito curto para obter um local se você estiver em um ambiente fechado com uma precisão de talvez 200 m?


Este é o meu código atual, qualquer comentário é apreciado (além da falta de verificação de erros, que é o 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);
}
Nicklas A.
fonte
7
Chegar bem tarde, mas o "Provedor de localização fundida", anunciado recentemente no IO 2013, parece atender a muitas de suas necessidades - developer.android.com/google/play-services/location.html
Matt
a última linha de getBestLocation () não deve ser: return currentBestLocation; em vez de retornar bestLocation ;?
Gavriel

Respostas:

164

Parece que estamos codificando o mesmo aplicativo ;-)
Aqui está minha implementação atual. Ainda estou na fase de teste beta do meu aplicativo para upload de GPS, então pode haver muitas melhorias possíveis. mas parece funcionar muito bem até agora.

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

Editar: aqui está a parte que solicita as atualizações periódicas dos provedores de localização:

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
}

Coisas a considerar:

  • não solicite atualizações de GPS com muita frequência, ele consome a energia da bateria. Atualmente, uso 30 min como padrão para meu aplicativo.

  • adicione uma verificação de 'distância mínima ao último local conhecido'. sem isso, seus pontos irão "pular" quando o GPS não estiver disponível e a localização estiver sendo triangulada das torres de celular. ou você pode verificar se o novo local está fora do valor de precisão do último local conhecido.

Gryphius
fonte
2
Na verdade, você nunca obtém um novo local, apenas usa locais existentes nas atualizações anteriores. Eu acho que esse código se beneficiaria muito ao adicionar um ouvinte que atualiza o local ativando o GPS de tempos em tempos.
precisa
2
desculpe, pensei que você estava interessado apenas na parte que seleciona o melhor de todos os locais disponíveis. Eu adicionei o código acima que solicita esses também. se um novo local GPS for recebido, ele será armazenado / enviado imediatamente. se eu receber uma atualização de local de rede, eu a armazeno para referência e 'espero' que eu receba uma atualização de GPS também até a próxima verificação de local.
Gryphius
2
Eu também tive um método stopRecording () que cancelou o timer. Eu finalmente mudou de um temporizador para um ScheduledThreadPoolExecutor, então stopRecording agora basicamente chama executor.shutdown () e de-registra todos os localização ouvintes de atualização
Gryphius
1
de acordo com o meu scm, stopRecording chamado apenas gpsTimer.cancel () e defina gps_recorder_running = false, assim como no seu caso, nenhuma limpeza dos ouvintes naquela época. o código atual mantém o controle de todos os ouvintes ativos em um vetor, eu não tinha isso quando escrevi essa resposta 1,5 anos atrás.
Gryphius
1
ele já está no github , mas não tenho certeza se ainda é a melhor maneira de fazer coisas sobre GPS hoje em dia. Afaik, eles fizeram muitas melhorias na API de localização desde que escrevi este código.
Gryphius 18/03
33

Para selecionar o provedor de local certo para o seu aplicativo, você pode usar os objetos Critérios :

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

Leia a documentação para requestLocationUpdates para obter mais detalhes sobre como os argumentos são levados em consideração:

A frequência da notificação pode ser controlada usando os parâmetros minTime e minDistance. Se minTime for maior que 0, o LocationManager poderá descansar por milésimos de segundo minTime entre as atualizações de local para economizar energia. Se minDistance for maior que 0, um local será transmitido apenas se o dispositivo se mover por medidores de minDistância. Para obter notificações com a maior frequência possível, defina os dois parâmetros como 0.

Mais pensamentos

  • Você pode monitorar a precisão dos objetos Location com Location.getAccuracy () , que retorna a precisão estimada da posição em metros.
  • o Criteria.ACCURACY_HIGHcritério deve fornecer erros abaixo de 100 m, o que não é tão bom quanto o GPS, mas atende às suas necessidades.
  • Você também precisa monitorar o status do seu provedor de localização e mudar para outro provedor, se ele ficar indisponível ou desabilitado pelo usuário.
  • O provedor passivo também pode ser uma boa opção para esse tipo de aplicativo: a idéia é usar as atualizações de local sempre que solicitadas por outro aplicativo e transmitidas por todo o sistema.
Stéphane
fonte
Eu examinei, Criteriamas e se a localização de rede mais recente for incrível (ela pode saber através de wifi) e não demorar tempo ou bateria para obtê-la (getLastKnown), então os critérios provavelmente desconsiderarão isso e retornarão o GPS. Não acredito que o Google tornou isso difícil para os desenvolvedores.
precisa
Além de usar os Critérios, você pode, em cada atualização de local enviada pelo provedor selecionado, verificar o lastKnowLocation do provedor de GPS e compará-lo (precisão e data) à sua localização atual. Mas isso me parece um requisito interessante e não um requisito para suas especificações; se, às vezes, for alcançada uma precisão um pouco melhor, será realmente útil para seus usuários?
Stéphane
É isso que estou fazendo agora, o problema é que tenho dificuldade em descobrir se o último conhecimento é bom o suficiente. Também posso acrescentar que não preciso me limitar a um provedor, quanto mais eu usar, mais rápido conseguiria um bloqueio.
Nicklas A.
Lembre-se de que PASSIVE_PROVIDER requer API nível 8 ou superior.
Eduardo
@ Stéphane desculpe pela edição. Não cuide disso. Sua postagem está correta. Eu fiz essa edição por erro. Desculpe. Saudações.
Gaúcho
10

Respondendo aos dois primeiros pontos :

  • O GPS sempre fornecerá uma localização mais precisa, se estiver ativado e se não houver paredes espessas ao redor .

  • Se o local não mudou, você pode chamar getLastKnownLocation (String) e recuperar o local imediatamente.

Usando uma abordagem alternativa :

Você pode tentar colocar o ID da célula em uso ou todas as células vizinhas

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

Você pode consultar a localização da célula através de vários bancos de dados abertos (por exemplo, http://www.location-api.com/ ou http://opencellid.org/ )


A estratégia seria ler a lista de IDs da torre ao ler o local. Em seguida, na próxima consulta (10 minutos no seu aplicativo), leia-as novamente. Se pelo menos algumas torres são iguais, é seguro usá-lo getLastKnownLocation(String). Se não estiverem, aguarde onLocationChanged(). Isso evita a necessidade de um banco de dados de terceiros para o local. Você também pode tentar essa abordagem .

Aleadam
fonte
Sim, mas eu o problema surge se o lastKnownLocation for muito ruim. Preciso de uma boa maneira de decidir o melhor dos dois locais.
Nicklas A.
Você pode armazenar as informações das torres e verificar se essas torres foram alteradas. Se o fizeram, aguarde um novo local, caso contrário (ou se apenas alguns foram alterados), reutilize-o. Dessa forma, você evita comparar os locais da torre com um banco de dados.
Aleadam 31/05
Usar torres me parece um grande exagero, mas é uma boa ideia.
Nicklas A.
@ Nicklas, o código não é mais complicado que isso. Você precisará de android.Manifest.permission # ACCESS_COARSE_UPDATES.
Aleadam
Sim, mas ainda preciso usar um serviço de terceiros e também preciso de uma maneira de decidir quando usar as informações da torre sobre os dados de localização, isso apenas adiciona uma camada extra de complexidade.
Nicklas A.
9

Esta é a minha solução que funciona bastante bem:

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 A.
fonte
Oi Nicklas eu tenho mesmo equirement para que eu pudesse comunicar a você por qualquer meio .. i seria agradecer completo para você se você pudesse nos ajuda ..
menino de escola
Você poderia postar o código inteiro? Obrigado, muito apreciado
Rodi
Esse é todo o código. Não tenho mais acesso ao projeto.
precisa
1
Você parece ter pegado o código deste projeto "android-protips-location" e ele ainda está vivo. As pessoas podem ver como isso funciona aqui code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77 4/14/14
7

A precisão da localização depende principalmente do provedor de localização usado:

  1. GPS - você obtém vários metros de precisão (supondo que você tenha recepção GPS)
  2. Wifi - você terá algumas centenas de metros de precisão
  3. Rede celular - Você obterá resultados muito imprecisos (já vi um desvio de até 4 km ...)

Se você procura a precisão, o GPS é sua única opção.

Eu li um artigo muito informativo sobre isso aqui .

Quanto ao tempo limite do GPS - 60 segundos devem ser suficientes e, na maioria dos casos, até demais. Eu acho que 30 segundos é bom e, às vezes, menos de 5 segundos ...

se você precisar apenas de um único local, sugiro que, no seu onLocationChangedmétodo, depois de receber uma atualização, cancele o registro do ouvinte e evite o uso desnecessário do GPS.

Muzikant
fonte
Eu realmente não me importo de onde eu começar a minha localização, eu não quero me limitar a um provedor
Nicklas A.
Você pode registrar todos os provedores de localização disponíveis no dispositivo (você pode obter a lista de todos os provedores em LocationManager.getProviders ()), mas se estiver procurando uma correção precisa, na maioria dos casos o provedor de rede não será útil para você.
Muzikant
Sim, mas isso não é uma questão sobre escolher entre fornecedores, esta é uma pergunta sobre como obter o melhor localização em geral (mesmo quando há múltiplos fornecedores envolvidos)
Nicklas A.
4

Atualmente estou usando, já que isso é confiável para obter a localização e calcular a distância para meu aplicativo ...... estou usando isso para meu aplicativo de táxi.

use a API de fusão que o desenvolvedor do Google desenvolveu com a fusão de sensor GPS, magnetômetro e acelerômetro, que também usa Wi-Fi ou localização da célula para calcular ou estimar a localização. Também é capaz de fornecer atualizações de localização também dentro do edifício com precisão. para obter detalhes, acesse o link https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi

import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class MainActivity extends Activity implements LocationListener,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private static final long ONE_MIN = 500;
    private static final long TWO_MIN = 500;
    private static final long FIVE_MIN = 500;
    private static final long POLLING_FREQ = 1000 * 20;
    private static final long FASTEST_UPDATE_FREQ = 1000 * 5;
    private static final float MIN_ACCURACY = 1.0f;
    private static final float MIN_LAST_READ_ACCURACY = 1;

    private LocationRequest mLocationRequest;
    private Location mBestReading;
TextView tv;
    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!servicesAvailable()) {
            finish();
        }

        setContentView(R.layout.activity_main);
tv= (TextView) findViewById(R.id.tv1);
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setInterval(POLLING_FREQ);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_FREQ);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();


        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    protected void onPause() {d
        super.onPause();

        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }


        tv.setText(location + "");
        // Determine whether new location is better than current best
        // estimate
        if (null == mBestReading || location.getAccuracy() < mBestReading.getAccuracy()) {
            mBestReading = location;


            if (mBestReading.getAccuracy() < MIN_ACCURACY) {
                LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            }
        }
    }

    @Override
    public void onConnected(Bundle dataBundle) {
        // Get first reading. Get additional location updates if necessary
        if (servicesAvailable()) {

            // Get best last location measurement meeting criteria
            mBestReading = bestLastKnownLocation(MIN_LAST_READ_ACCURACY, FIVE_MIN);

            if (null == mBestReading
                    || mBestReading.getAccuracy() > MIN_LAST_READ_ACCURACY
                    || mBestReading.getTime() < System.currentTimeMillis() - TWO_MIN) {

                LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);

               //Schedule a runnable to unregister location listeners

                    @Override
                    public void run() {
                        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, MainActivity.this);

                    }

                }, ONE_MIN, TimeUnit.MILLISECONDS);

            }

        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }


    private Location bestLastKnownLocation(float minAccuracy, long minTime) {
        Location bestResult = null;
        float bestAccuracy = Float.MAX_VALUE;
        long bestTime = Long.MIN_VALUE;

        // Get the best most recent location currently available
        Location mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        //tv.setText(mCurrentLocation+"");
        if (mCurrentLocation != null) {
            float accuracy = mCurrentLocation.getAccuracy();
            long time = mCurrentLocation.getTime();

            if (accuracy < bestAccuracy) {
                bestResult = mCurrentLocation;
                bestAccuracy = accuracy;
                bestTime = time;
            }
        }

        // Return best reading or null
        if (bestAccuracy > minAccuracy || bestTime < minTime) {
            return null;
        }
        else {
            return bestResult;
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

    private boolean servicesAvailable() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        if (ConnectionResult.SUCCESS == resultCode) {
            return true;
        }
        else {
            GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
            return false;
        }
    }
}
AshisParajuli
fonte
2

Procurei na Internet uma resposta atualizada (no ano passado) usando os métodos mais recentes de localização sugeridos pelo google (para usar o FusedLocationProviderClient). Finalmente cheguei a isso:

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

Criei um novo projeto e copiei a maior parte desse código. Estrondo. Funciona. E acho que sem linhas obsoletas.

Além disso, o simulador não parece ter uma localização GPS, que eu saiba. Chegou ao ponto de relatar isso no log: "Todas as configurações de localização são satisfeitas".

E, finalmente, caso você queira saber (eu sabia), você NÃO precisa de uma chave API do google maps no console do desenvolvedor do google, se tudo o que você quer é a localização do GPS.

Também é útil o seu tutorial. Mas eu queria um tutorial completo de uma página / exemplo de código, e isso. O tutorial deles empilha, mas é confuso quando você é novo nisso, porque você não sabe quais são as peças necessárias nas páginas anteriores.

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

E, finalmente, lembre-se de coisas assim:

Eu não apenas tive que modificar o mainActivity.Java. Também tive que modificar Strings.xml, androidmanifest.xml E o build.gradle correto. E também o activity_Main.xml (mas essa parte foi fácil para mim).

Eu precisava adicionar dependências como esta: implementação 'com.google.android.gms: play-services-location: 11.8.0' e atualizar as configurações do meu SDK do Android Studio para incluir os serviços do Google Play. (configurações do arquivo aparência configurações do sistema Android SDK SDK Tools verifique os serviços do Google Play).

update: o simulador android parecia obter um local e eventos de alteração de local (quando alterei o valor nas configurações do sim). Mas meus melhores e primeiros resultados foram em um dispositivo real. Portanto, é provavelmente mais fácil testar em dispositivos reais.

Neo42
fonte
1

Recentemente refatorado para obter a localização do código, aprender algumas boas idéias e finalmente obter uma biblioteca e uma demonstração relativamente perfeitas.

@ A resposta de Gryphius é boa

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

Implementação completa: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

1.Obrigado pelas idéias da solução @Gryphius, também compartilho o código completo.

2.Cada solicitação para concluir o local, é melhor remover Atualizações, caso contrário, a barra de status do telefone sempre exibirá o ícone de posicionamento

Bingerz
fonte
0

Na minha experiência, achei melhor usar a correção GPS, a menos que ela não estivesse disponível. Não sei muito sobre outros provedores de localização, mas sei que para o GPS existem alguns truques que podem ser usados ​​para dar uma medida de precisão do gueto. A altitude geralmente é um sinal, para que você possa verificar valores ridículos. Existe a medida de precisão nas correções de localização do Android. Além disso, se você pode ver o número de satélites usados, isso também pode indicar a precisão.

Uma maneira interessante de obter uma idéia melhor da precisão pode ser pedir um conjunto de correções muito rapidamente, como ~ 1 / s por 10 segundos e depois dormir por um minuto ou dois. Uma conversa em que estive levou a acreditar que alguns dispositivos Android farão isso de qualquer maneira. Você eliminaria os outliers (ouvi o filtro Kalman mencionado aqui) e usaria algum tipo de estratégia de centralização para obter uma única correção.

Obviamente, a profundidade que você chega aqui depende de quão difíceis são seus requisitos. Se você tem um requisito particularmente rígido para obter a melhor localização possível, acho que você descobrirá que o GPS e a localização da rede são tão parecidos quanto as maçãs e laranjas. Além disso, o GPS pode ser totalmente diferente de dispositivo para dispositivo.

rebocadores
fonte
Bem, não é importante que seja o melhor, apenas que seja bom o suficiente para traçar em um mapa e que eu não gaste a bateria, pois essa é uma tarefa em segundo plano.
Nicklas A.
-3

A Skyhook (http://www.skyhookwireless.com/) possui um provedor de localização muito mais rápido que o padrão que o Google fornece. Pode ser o que você está procurando. Eu não sou afiliado a eles.

Ed Burnette
fonte
Interessante, de fato, eles só parecem usar WiFi, no entanto, o que é muito bom, mas eu ainda preciso que ele funcione quando não há conexão Wi-Fi ou 3G / 2G por isso, isso adicionaria outra camada de abstração. Boa captura embora.
precisa
1
O Skyhook parece usar uma combinação de WiFi, GPS e torres de celular. Consulte skyhookwireless.com/howitworks para obter detalhes técnicos. Eles obtiveram várias vitórias de design recentemente, por exemplo, Mapquest, Twydroid, ShopSavvy e Sony NGP. Observe que o download e a experimentação do SDK deles parecem gratuitos, mas é necessário entrar em contato com eles sobre uma licença para distribuí-lo no seu aplicativo. Infelizmente, eles não listam o preço em seu site.
Ed Burnette
Ah eu vejo. Bem, se não é livre para usar comercialmente, receio que não possa usá-lo.
precisa