Cara yang baik untuk mendapatkan lokasi pengguna di Android

211

Masalah:

Mendapatkan lokasi pengguna saat ini dalam ambang ASAP dan pada saat yang sama menghemat baterai.

Mengapa masalahnya adalah masalah:

Pertama, Android memiliki dua penyedia; jaringan dan GPS. Terkadang jaringan lebih baik dan terkadang GPS lebih baik.

Dengan "lebih baik" Maksudku rasio kecepatan vs akurasi.
Saya bersedia mengorbankan akurasi beberapa meter jika saya bisa mendapatkan lokasi hampir instan dan tanpa menyalakan GPS.

Kedua, jika Anda meminta pembaruan untuk perubahan lokasi tidak dikirim jika lokasi saat ini stabil.

Google memiliki contoh untuk menentukan lokasi "terbaik" di sini: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
Tapi saya pikir tidak ada yang sedekat yang seharusnya. /bisa jadi.

Saya agak bingung mengapa google belum memiliki API yang dinormalisasi untuk lokasi, pengembang tidak perlu peduli dari mana lokasi itu berasal, Anda hanya perlu menentukan apa yang Anda inginkan dan telepon harus memilih untuk Anda.

Apa yang saya perlu bantuan:

Saya perlu menemukan cara yang baik untuk menentukan lokasi "terbaik", mungkin melalui beberapa heuristik atau mungkin melalui beberapa perpustakaan pihak ke-3.

Ini tidak berarti menentukan penyedia terbaik!
Saya mungkin akan menggunakan semua penyedia dan memilih yang terbaik dari mereka.

Latar belakang aplikasi:

Aplikasi ini akan mengumpulkan lokasi pengguna pada interval yang tetap (misalkan setiap 10 menit atau lebih) dan mengirimkannya ke server.
Aplikasi ini harus menghemat baterai sebanyak mungkin dan lokasi harus memiliki akurasi X (50-100?) Meter.

Tujuannya adalah untuk nantinya dapat memplot jalur pengguna pada siang hari di peta jadi saya perlu akurasi yang cukup untuk itu.

Lain-lain:

Menurut Anda apa nilai yang masuk akal pada akurasi yang diinginkan dan diterima?
Saya telah menggunakan 100m sebagai diterima dan 30m seperti yang diinginkan, apakah ini banyak bertanya?
Saya ingin dapat memplot jalur pengguna di peta nanti.
Apakah 100m untuk yang diinginkan dan 500m untuk diterima lebih baik?

Juga, saat ini saya memiliki GPS aktif selama maksimum 60 detik per pembaruan lokasi, apakah ini terlalu pendek untuk mendapatkan lokasi jika Anda berada di dalam ruangan dengan akurasi mungkin 200m?


Ini adalah kode saya saat ini, ada umpan balik yang dihargai (terlepas dari kurangnya pengecekan kesalahan yaitu 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.
sumber
7
Menyela benar-benar terlambat, tetapi "Penyedia Lokasi Bersatu" yang baru-baru ini diumumkan di IO 2013 sepertinya memenuhi banyak kebutuhan Anda - developer.android.com/google/play-services/location.html
Matt
seharusnya baris terakhir getBestLocation () menjadi: return currentBestLocation; bukannya mengembalikan bestLocation ;?
Gavriel

Jawaban:

164

Sepertinya kami sedang mengkode aplikasi yang sama ;-)
Ini adalah implementasi saya saat ini. Saya masih dalam tahap pengujian beta aplikasi pengunggah GPS saya, jadi mungkin ada banyak kemungkinan perbaikan. tetapi sejauh ini tampaknya bekerja dengan cukup baik.

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

Sunting: di sini adalah bagian yang meminta pembaruan berkala dari penyedia lokasi:

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
}

Hal yang perlu dipertimbangkan:

  • jangan terlalu sering meminta pembaruan GPS, itu menghabiskan daya baterai. Saat ini saya menggunakan 30 menit sebagai default untuk aplikasi saya.

  • tambahkan cek 'jarak minimum ke lokasi terakhir yang diketahui'. tanpa ini, poin Anda akan "melompat-lompat" ketika GPS tidak tersedia dan lokasi sedang triangulasi dari menara sel. atau Anda dapat memeriksa apakah lokasi baru di luar nilai akurasi dari lokasi terakhir yang diketahui.

Gryphius
sumber
2
Anda tidak pernah benar-benar mendapatkan lokasi baru, Anda hanya menggunakan lokasi yang kebetulan ada dari pembaruan sebelumnya. Saya pikir kode ini akan sangat bermanfaat dengan menambahkan pendengar yang memperbarui lokasi dengan menyalakan GPS dari waktu ke waktu.
Nicklas A.
2
maaf, saya pikir Anda hanya tertarik pada bagian yang memilih yang terbaik dari semua lokasi yang tersedia. Saya menambahkan kode di atas yang meminta ini juga. jika lokasi gps baru diterima, segera disimpan / diunggah. jika saya menerima pembaruan lokasi jaringan saya menyimpannya untuk referensi dan 'berharap' bahwa saya akan menerima pembaruan gps juga sampai pemeriksaan lokasi berikutnya terjadi.
Gryphius
2
Saya juga punya metode stopRecording () yang membatalkan timer. Saya akhirnya beralih dari timer ke ScheduledThreadPoolExecutor, jadi stopRecording sekarang pada dasarnya memanggil executor.shutdown () dan membatalkan registrasi semua pendengar pembaruan lokasi
Gryphius
1
menurut scm saya, stopRecording hanya bernama gpsTimer.cancel () dan mengatur gps_recorder_running = false, jadi seperti dalam kasus Anda, tidak ada pembersihan pendengar saat itu. kode saat ini melacak semua pendengar aktif dalam vektor, saya tidak memiliki ini ketika saya menulis jawaban ini 1,5 tahun yang lalu.
Gryphius
1
ini sudah ada di github , tapi saya tidak yakin ini masih cara terbaik untuk melakukan hal-hal GPS saat ini. Afaik mereka telah membuat banyak perbaikan pada API lokasi sejak saya menulis kode ini.
Gryphius
33

Untuk memilih penyedia lokasi yang tepat untuk aplikasi Anda, Anda dapat menggunakan objek Kriteria :

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

Baca dokumentasi untuk requestLocationUpdates untuk detail lebih lanjut tentang bagaimana argumen diperhitungkan:

Frekuensi pemberitahuan dapat dikontrol menggunakan parameter minTime dan minDistance. Jika minTime lebih besar dari 0, LocationManager berpotensi beristirahat selama minTime milidetik di antara pembaruan lokasi untuk menghemat daya. Jika minDistance lebih besar dari 0, lokasi hanya akan disiarkan jika perangkat bergerak dengan minDistance meter. Untuk mendapatkan pemberitahuan sesering mungkin, atur kedua parameter ke 0.

Lebih banyak pemikiran

  • Anda dapat memantau keakuratan objek Lokasi dengan Location.getAccuracy () , yang mengembalikan perkiraan akurasi posisi dalam meter.
  • yang Criteria.ACCURACY_HIGHkriteria harus memberikan kesalahan di bawah 100m, yang tidak sebaik GPS dapat, tetapi sesuai dengan kebutuhan Anda.
  • Anda juga perlu memantau status penyedia lokasi Anda, dan beralih ke penyedia lain jika tidak tersedia atau dinonaktifkan oleh pengguna.
  • The penyedia pasif juga mungkin cocok untuk jenis aplikasi: idenya adalah untuk menggunakan update lokasi setiap kali mereka diminta oleh aplikasi dan siaran lain systemwide.
Stéphane
sumber
Saya telah melihat ke dalam Criteriatetapi bagaimana jika lokasi jaringan terbaru luar biasa (mungkin tahu melalui wifi) dan tidak membutuhkan waktu atau baterai untuk mendapatkannya (getLastKnown), maka kriteria mungkin akan mengabaikan itu dan mengembalikan GPS sebagai gantinya. Saya tidak percaya Google telah mempersulit pengembang.
Nicklas A.
Selain menggunakan Kriteria, Anda dapat, di setiap pembaruan lokasi yang dikirim oleh penyedia yang Anda pilih, periksa lastKnowLocation untuk penyedia GPS dan membandingkannya (keakuratan dan tanggal) dengan lokasi Anda saat ini. Tapi menurut saya ini adalah persyaratan yang bagus untuk dimiliki daripada persyaratan dari spesifikasi Anda; jika akurasi yang agak lebih baik kadang-kadang dicapai, apakah itu benar-benar bermanfaat bagi pengguna Anda?
Stéphane
Itulah yang saya lakukan sekarang, masalahnya adalah saya kesulitan mencari tahu apakah yang terakhir cukup baik. Saya juga dapat menambahkan bahwa saya tidak harus membatasi diri pada satu penyedia, semakin banyak saya menggunakan semakin cepat saya mendapatkan kunci.
Nicklas A.
Perlu diingat bahwa PASSIVE_PROVIDER memerlukan API Level 8 atau lebih tinggi.
Eduardo
@ Stéphane maaf untuk hasil edit. Jangan pedulikan itu. Pos Anda benar. Saya melakukan itu untuk mengedit kesalahan. Maaf. Salam.
Gaucho
10

Menjawab dua poin pertama :

  • GPS akan selalu memberi Anda lokasi yang lebih tepat, jika diaktifkan dan tidak ada dinding tebal di sekitarnya .

  • Jika lokasi tidak berubah, maka Anda dapat memanggil getLastKnownLocation (String) dan mengambil lokasi segera.

Menggunakan pendekatan alternatif :

Anda dapat mencoba menggunakan id sel atau semua sel tetangga

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

Anda dapat merujuk lokasi sel melalui beberapa database terbuka (mis., Http://www.location-api.com/ atau http://opencellid.org/ )


Strateginya adalah membaca daftar ID menara saat membaca lokasi. Kemudian, dalam kueri berikutnya (10 menit di aplikasi Anda), baca lagi. Jika setidaknya beberapa menara sama, maka aman digunakan getLastKnownLocation(String). Jika tidak, maka tunggu onLocationChanged(). Ini menghindari kebutuhan database pihak ketiga untuk lokasi. Anda juga dapat mencoba pendekatan ini .

Aleadam
sumber
Ya, tapi saya masalahnya datang jika lastKnownLocation benar-benar buruk. Saya perlu cara yang baik untuk memutuskan yang terbaik dari dua lokasi.
Nicklas A.
Anda dapat menyimpan info menara dan memeriksa apakah menara itu berubah. Jika ya, maka tunggu lokasi baru, jika tidak (atau jika hanya beberapa yang berubah), lalu gunakan kembali. Dengan begitu Anda menghindari membandingkan lokasi menara dengan basis data.
Aleadam
Menggunakan menara sepertinya terlalu sulit bagiku, ide bagus.
Nicklas A.
@Nicklas kode tidak menjadi lebih rumit dari itu. Anda akan memerlukan android.Manifest.permission # ACCESS_COARSE_UPDATES.
Aleadam
Ya, tapi saya masih perlu menggunakan layanan pihak ketiga dan juga saya perlu cara untuk memutuskan kapan pengguna info menara atas data lokasi, ini hanya menambah lapisan kompleksitas tambahan.
Nicklas A.
9

Ini solusi saya yang bekerja cukup baik:

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.
sumber
Hai Nicklas, saya memiliki equirement yang sama sehingga saya bisa berkomunikasi dengan Anda dengan cara apa pun .. saya akan berterima kasih penuh kepada Anda jika Anda dapat membantu kami ..
School Boy
Bisakah Anda memposting seluruh kode? Terima kasih, sangat dihargai
rodi
Itu semua kodenya. Saya tidak memiliki akses ke proyek lagi.
Nicklas A.
1
Anda sepertinya telah mengambil kode proyek ini "android-protips-location" dan itu masih hidup. Orang-orang dapat melihat cara kerjanya di sini code.google.com/p/android-protips-location/source/browse/trunk/…
Gödel77
7

Akurasi lokasi sebagian besar tergantung pada penyedia lokasi yang digunakan:

  1. GPS - akan memberi Anda akurasi beberapa meter (dengan asumsi Anda memiliki penerimaan GPS)
  2. Wifi - Akan membuat Anda mendapatkan akurasi beberapa ratus meter
  3. Jaringan Sel - Akan memberi Anda hasil yang sangat tidak akurat (Saya telah melihat deviasi hingga 4 km ...)

Jika akurasi yang Anda cari, maka GPS adalah satu-satunya pilihan Anda.

Saya telah membaca artikel yang sangat informatif tentang hal ini di sini .

Adapun batas waktu GPS - 60 detik harus cukup, dan dalam kebanyakan kasus bahkan terlalu banyak. Saya pikir 30 detik tidak apa-apa dan kadang-kadang bahkan kurang dari 5 detik ...

jika Anda hanya memerlukan satu lokasi, saya sarankan dalam onLocationChangedmetode Anda , setelah Anda menerima pembaruan Anda akan membatalkan registrasi pendengar dan menghindari penggunaan GPS yang tidak perlu.

Muzikant
sumber
Saya tidak terlalu peduli dari mana saya mendapatkan lokasi saya, saya tidak ingin membatasi saya pada satu penyedia
Nicklas A.
Anda dapat mendaftarkan semua penyedia lokasi yang tersedia di perangkat (Anda bisa mendapatkan daftar semua penyedia dari LocationManager.getProviders ()), tetapi jika Anda mencari perbaikan yang akurat, dalam banyak kasus penyedia jaringan tidak akan berguna bagi Anda.
Muzikant
Ya, tetapi ini bukan pertanyaan tentang memilih di antara penyedia, ini adalah pertanyaan tentang mendapatkan lokasi terbaik secara umum (bahkan ketika ada banyak penyedia yang terlibat)
Nicklas A.
4

Saat ini saya menggunakan karena ini dapat dipercaya untuk mendapatkan lokasi dan menghitung jarak untuk aplikasi saya ...... saya menggunakan ini untuk aplikasi taksi saya.

menggunakan fusion API yang telah dikembangkan oleh pengembang google dengan fusi GPS Sensor, Magnetometer, Accelerometer juga menggunakan Wifi atau lokasi sel untuk menghitung atau memperkirakan lokasi. Itu juga dapat memberikan pembaruan lokasi juga di dalam gedung secara akurat. untuk detail dapatkan tautan 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
sumber
2

Saya menjelajahi internet untuk jawaban yang diperbarui (tahun lalu) menggunakan metode penarikan lokasi terbaru yang disarankan oleh google (untuk menggunakan FusedLocationProviderClient). Saya akhirnya mendarat di ini:

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

Saya membuat proyek baru dan menyalin sebagian besar kode ini. Ledakan. Berhasil. Dan saya pikir tanpa garis yang usang.

Selain itu, simulator tampaknya tidak mendapatkan lokasi GPS, yang saya tahu. Itu memang sejauh melaporkan ini di log: "Semua pengaturan lokasi puas."

Dan akhirnya, jika Anda ingin tahu (saya tahu), Anda TIDAK perlu kunci peta google api dari konsol pengembang google, jika semua yang Anda inginkan adalah lokasi GPS.

Juga bermanfaat adalah tutorial mereka. Tapi saya ingin contoh tutorial / kode satu halaman penuh, dan itu. Tumpukan tutorial mereka tetapi membingungkan ketika Anda baru dengan ini karena Anda tidak tahu apa yang Anda butuhkan dari halaman sebelumnya.

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

Dan akhirnya, ingat hal-hal seperti ini:

Saya tidak hanya harus memodifikasi mainActivity.Java. Saya juga harus memodifikasi Strings.xml, androidmanifest.xml, DAN build.gradle yang benar. Dan juga activity_Main.xml Anda (tetapi bagian itu mudah bagi saya).

Saya perlu menambahkan dependensi seperti ini: implementasi 'com.google.android.gms: play-services-location: 11.8.0', dan perbarui pengaturan SDK studio android saya untuk memasukkan layanan google play. (pengaturan file, pengaturan tampilan sistem, android SDK, SDK Tools, memeriksa layanan google play).

pembaruan: simulator android tampaknya mendapatkan lokasi dan perubahan lokasi acara (ketika saya mengubah nilai dalam pengaturan sim). Tetapi hasil terbaik dan pertama saya pada perangkat yang sebenarnya. Jadi mungkin paling mudah untuk menguji pada perangkat yang sebenarnya.

Neo42
sumber
1

Baru-baru ini refactored untuk mendapatkan lokasi kode, mempelajari beberapa ide bagus, dan akhirnya mencapai perpustakaan dan Demo yang relatif sempurna.

@Gryphius jawabannya bagus

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

Implementasi lengkap: https://github.com/bingerz/FastLocation/blob/master/fastlocationlib/src/main/java/cn/bingerz/fastlocation/FastLocation.java

1.Terima kasih solusi ide @Gryphius, saya juga berbagi kode lengkap.

2. Setiap permintaan untuk menyelesaikan lokasi, yang terbaik adalah menghapus Pembaruan, jika tidak, bilah status telepon akan selalu menampilkan ikon posisi

Bingerz
sumber
0

Dalam pengalaman saya, saya telah menemukan yang terbaik untuk memperbaiki GPS kecuali itu tidak tersedia. Saya tidak tahu banyak tentang penyedia lokasi lain, tetapi saya tahu bahwa untuk GPS ada beberapa trik yang dapat digunakan untuk memberikan sedikit ukuran presisi ghetto. Ketinggian sering merupakan tanda, sehingga Anda dapat memeriksa nilai-nilai konyol. Ada ukuran akurasi pada perbaikan lokasi Android. Juga jika Anda dapat melihat jumlah satelit yang digunakan, ini juga dapat menunjukkan ketepatan.

Cara yang menarik untuk mendapatkan ide yang lebih baik tentang keakuratan bisa dengan meminta satu set perbaikan dengan sangat cepat, seperti ~ 1 / detik selama 10 detik dan kemudian tidur selama satu atau dua menit. Satu pembicaraan saya telah menyebabkan percaya bahwa beberapa perangkat android akan melakukan ini. Anda kemudian akan menyingkirkan outlier (saya pernah mendengar filter Kalman disebutkan di sini) dan menggunakan semacam strategi pemusatan untuk mendapatkan satu perbaikan.

Jelas kedalaman Anda sampai di sini tergantung pada seberapa keras persyaratan Anda. Jika Anda memiliki persyaratan ketat untuk mendapatkan lokasi TERBAIK, saya pikir Anda akan menemukan bahwa lokasi GPS dan jaringan sama seperti apel dan jeruk. GPS juga bisa sangat berbeda dari satu perangkat ke perangkat lainnya.

kapal tunda
sumber
Yah, itu tidak penting bahwa itu yang terbaik, hanya saja itu cukup baik untuk plot pada peta dan bahwa saya tidak menguras baterai karena ini adalah tugas latar belakang.
Nicklas A.
-3

Skyhook (http://www.skyhookwireless.com/) memiliki penyedia lokasi yang jauh lebih cepat daripada standar yang disediakan Google. Mungkin itu yang Anda cari. Saya tidak berafiliasi dengan mereka.

Ed Burnette
sumber
Menarik memang, mereka tampaknya hanya menggunakan WiFi namun yang sangat bagus tapi saya masih membutuhkannya untuk bekerja ketika tidak ada wifi atau koneksi 3G / 2G di sekitar jadi ini akan menambah lapisan abstraksi lain. Tangkapan yang bagus.
Nicklas A.
1
Skyhook tampaknya menggunakan kombinasi WiFi, GPS, dan menara seluler. Lihat skyhookwireless.com/howit bekerja untuk rincian teknis. Mereka mendapatkan beberapa kemenangan desain akhir-akhir ini, misalnya Mapquest, Twydroid, ShopSavvy, dan Sony NGP. Perhatikan bahwa mengunduh dan mencoba SDK mereka tampaknya gratis tetapi Anda harus menghubungi mereka tentang lisensi untuk mendistribusikannya di aplikasi Anda. Sayangnya mereka tidak mencantumkan harga di situs web mereka.
Ed Burnette
Oh begitu. Nah, jika tidak bebas untuk digunakan secara komersial maka saya khawatir saya tidak dapat menggunakannya.
Nicklas A.