poniedziałek, 17 lutego 2014

GPS - sprawdzanie lokalizacji


Kod źródłowy do tej lekcji znajduje się tutaj. Rozpakuj go i zaimportuj. W katalogu z kodem źródłowym znajdą się podkatalogi oznaczone numerami. Są to po prostu kolejne wersje kodu, tworzonego w ramach przykładów. Możesz analizować kolejne kroki tworzenia aplikacji, lub od razu zabrać się za kod z katalogu o najwyższym numerku.


Jeśli nasz telefon / urządzenie z którego korzystamy posiada czujniki GPS,
możemy wykorzystać je do sprawdzania naszej pozycji programowo. Możemy również skorzystać z darmowych map projektu OpenStreetMap (http://openstreetmap.org) i np. stworzyć własną nawigację.
Zaczniemy od utworzenia nowego projektu i wyedytowania pliku AndroidManifest.xml tego projektu. Musimy dodać prośbę o uprawnienia do korzystania z czujników GPS. Bez takiej autoryzacji ze strony użytkownika, nasz program nie będzie działał. Dodajemy linie:


<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Tuż pod zamknięciem elementu <application>. Podglądzik:


Mamy już możliwość korzystania z czujników GPS. Czas przejść do właściwej wersji kodu. Zaczniemy jak zawsze od wersji najprostszej tj. po uruchomieniu programu ma się na ekranie wyświetlić nasze położenie tzn. długość i szerokość geograficzna.
Przechodzimy teraz do naszej głównej aktywności. Przyklejam na ekranie trzy elementy klasy TextView. Na długość i szerokość geograficzną, oraz na nazwę wybranego dostawcy (wyjaśni się to nieco później).



Od razu w metodzie onCreate podpinam do nich uchwyty. Będę się przecież do tych elementów odwoływał, więc będę też potrzebował elementów reprezentująćych te komponenty.
Do określania lokalizacji będzie nam służył obiekt klasy LocationManager. Definiuję go podobnie jak komponenty klasy TextView – jako pole klasy a nie jako zmienna prywatna w metodzie onCreate. Robię tak, bo być może będę się do tego obiektu owoływał z innych metod na dalszych etapach tworzenia programu.



Obiekt kr klasy Criteria (linia 29) służy do wyszukiwania najlepszego dostawcy informacji o położeniu GPS. Mogę np. poprzez te kryteria określić że interesują mnie wyłącznie bezpłatni dostawcy ( metoda setCostAllowed klasy Criteria), czy poziom zapotrzebowania energetycznego wymaganego dla danego sposobu określania lokalizacji (setPowerRequirement). Chcąc zastosować któryś z wymienionych warunków, po utworzeniu obiektu klasy Criteria, uruchamiamy dla tego obiektu wybrane metody. Sam obiekt klasy Criteria jako element warunkujący wybór dostawcy stosujemy później przy pobieraniu nazwy najlepszego dostawcy. W linii 30 inicjalizuję obiekt przy uzyciu usługi zwracanej przez metodę getSystemService (metoda dziedziczona po klasie Activity). Z użyciem tej metody możemy podpinać się do najprzeróżniejszych usług systemowych : np. korzystać z różnych czujników (temperatury, ciśnienia etc), korzystać z usług lokalizacyjnych, pobierać dane z internetu, drukować, korzystać z portu USB i wiele innych. Ponieważ będziemy korzystać z usług lokalizacyjnych, jako parametr metody getSystemService podaję LOCATION_SERVICE. Linia 31 to pobranie nazwy najlepszego dostawcy informacji o położeniu (może to być np. GPS, położenie określone wg triangulacji, lub sieć WIFI). Zwrócony może być tylko dostawca dla którego wywołująca aktywność ma niezbędne pozwolenia (chodzi o wpisy w AndroidManifest.xml). Jeśli kilku dostawców spełnia założone kryteria, to zwrócona zostanie nazwa tego który charakteryzuje się najwyższą dokładnością położenia. Przykładowo, jeśli możemy określić nasze położenie na podstawie GPS, lub na podstawie triangulacji, przy czym GPS określi to z dokładnością do 10 metrów a triangulacja z dokładnością do 1 kilometra, to zostanie zwrócona nazwa dostawcy gps. Pierwszy parametr metody getBestProvider to nasze kryteria, drugi określa czy zwracani mają być tylko aktywni dostawcy ( w tym przypadku oczywiście tylko tacy nas interesują).



Obiekt klasy Location będzie reprezentował naszą lokalizację w przestrzeni, będziemy z niego później pobierać np. naszą długość i szerokość geograficzną. Najpierw jednak musimy określić nasze położenie :) Robimy to w linii 32 przy użyciu metody getLastKnownLocation obiektu klasy LocationManager. Jako parametr podajemy nazwę wybranego dostawcy informacji o położeniu. Linie 34-36 to po prostu wyświetlenie uzyskanych danych. Warte uwagi mogą tutaj być metody getLongitude i getLatitude zwracające długość i szerokość geograficzną. Zwracają liczby typu double, z dosyć dużą dokładnością. Będąc na obrzeżach Warszawy dostałem swoje położenie z dokładnością do 7 miejsca po przecinku stopnia geograficznego. Do tego prostego programu warto byłoby ewentualnie dorobić obsługę sytuacji w której nie byłoby w danym położeniu żadnego dostawcy, lub nie można byłoby określić położenia. Nie będziemy się tym tutaj jednak zajmować ponieważ obsługa nulla zwracanego z metody (tutaj z metod getBestProvider i getLastKnownLocation) to podstawy Javy. Chcąc testować rzeczy związane z GPS, najlepiej robić to na realnym urządzeniu a nie żadnych emulatorach. Niby na emulatorach jest możliwość ustawienia fikcyjnego położenia GPS, ale też nie zawsze to działa. U mnie po uruchomieniu programu na telefonie wyświetliło się:

najlepszy dostawca: network
szerokość geograficzna: 52.2996591
długość geograficzna: 20.9930961

Program działa, jednak jeśli zmienimy położenie, nasz program tego nie odnotuje. Warto byłoby wzbogacić program o funkcjonalność która by uwzględniała zmiany naszego położenia. Zacznę od dodania do ekranu elementu na którym będzie pojawiała się historia lokalizacji. Przykleiłem komponent klasy SmallText.



Żeby historia troszkę się odróżniała, zmieniłem tekstu w tym nowym komponencie poprzez edycję pliku layoutu i dopisani linijki : android:textColor=”#300FD”


Kodowi źródłowemu naszej aktywności będziemy przyglądać się poczynając od góry. W definicji dopisałem „implements LocationListener”, dzięki czemu będę miał dostępną metodę onLocationChanged która jest wywoływana za każdym razem kiedy zostanie wykryta nowa lokalizacja. Ponieważ jest to interfejs, będę musiał zaimplementować cztery wymagane przez niego metody. Tym się zajmiemy za chwilę.



W linii 18 widzimy nowe pole – t4. To jest obiekt reprezentujący komponent do wyświetlania historii. W liniach 25-28 widzimy nową metodę „odswiez”. Ponieważ będę musiał odświeżać swoje położenie przynajmniej dwukrotnie tj. przy uruchomieniu aplikacji i przy zmianie lokalizacji, postanowiłem kod odpowiedzialny za odświeżanie oddelegować do osobnej metody.



W linii 37 widzimy podpięcie do obiektu t4 referencji do naszego nowego komponentu (tego napisu który będzie robił za historię). Nic nadzwyczajnego, ale musimy o tym pamiętać. W linii 41 zamiast wywoływać osobno szukanie najlepszego dostawcy i pobieranie położenia mamy wywołanie metody która właśnie to robi i aktualizuje nasze obiekty. Linia 42 zawiera ustawienie własności odświeżania. Konfigurujemy tutaj co jaki czas i przy jakiej zmianie położenia system ma odświeżać lokalizację (czyli jak często ma być automatycznie wywoływana metoda onLocationChanged którą będziemy za chwilę implementować - w liniach 50-57). Pierwszy parametr to nazwa dostawcy z którego korzystamy, drugi to czas w milisekundach co ile ma być odświeżana lokalizacja, trzeci to co jaki dystans (wyrażony w metrach), a czwarty to obiekt implementujący interfejs LocationListener (tutaj akurat dotyczy to obiektu w którym się znajdujemy). Linie 43-46 to ustawienie domyślnych napisów, czyli tego co ma się pojawić zaraz po uruchomieniu programu. Tutaj tak jak wcześniej wyświetlamy dostawcę i współrzędne. W linii 46 ustawiam pierwszą i na razie jedyną linię historii.


Poniżej mamy cztery metody które musiałem zaimplementować z racji implementacji interfejsu LocationListener. Trzy tymczasowo zostawiam w spokoju. Interesuje mnie teraz tylko metoda onLocationChanged. Jest ona wywoływana co interwał czasowy lub dystans określone (linia 42) w metodzie requestLocationUpdates. W ramach mojej implementacji metody onLocationChanged odświeżam położenie wywołując metodę odswiez, a następnie w liniach 52-54 wyświetlam na komponentach aktualne położenie. W linii 55 dodaję do historii kolejną linię.



Byłem ciekaw na ile sprawnie t o działa, więc wgrałem program na telefon i poszedłem sprawdzić czy nie ma mnie za rogiem. Oto wyniki:





10 komentarzy:

  1. Zamiast dawać:
    najlepszyDostawca=lm.getBestProvider(kr, true);

    możemy dać:
    najlepszyDostawca=lm.GPS_PROVIDER;

    wtedy będziemy zawsze korzystać z GPS.


    Równie dobrze możemy wpisać:

    najlepszyDostawca=lm.NETWORK_PROVIDER;

    wtedy będziemy korzystali tylko z lokalizacji po sieci komórkowej.

    OdpowiedzUsuń
    Odpowiedzi
    1. Możemy dać też metody onResume i onPause, po to aby zapobiegać aktualizowaniu pozycji gdy aplikacja jest zminimalizowana (jesli nie jest to nam potrzebne).

      @override
      protected void onResume(){
      lm.requestLocationUpdates(najlepszyDostawca, 400, 1, this);
      }

      @override
      protected void onPause(){
      lm.removeUpdates();
      }

      BTW: metoda onResume wykonywana jest także przy starcie aplikacji, zalecam dodać ten wpis do tej metody (albo onCreate), gdyż wtedy podczas uzycia GPS nasza pozycja będzie aktualizowana co 400ms.

      Usuń
    2. po wpisaniu najlepszyDostawca=lm.GPS_PROVIDER;
      zamiast tamtego zawiesza mi komórkę i wychodzi z programy (mam włączone GPS w komie)

      Usuń
  2. jeśli chcemy korzystać zarówno z lokalizacji po GPS jak i po sieci komórkowej wystarczy dodać samo uprawnienie:




    Nie potrzeba wtedy ACCESS_COARSE_LOCATION. Dodajemy je tylko wtedy gdy chcemy korzystać z samej sieci komórkowej a GPS nam nie potrzebny. Jednak wpis ACCESS_FINE_LOCATION zapewnia nam możliwość lokalizacji zarówno po GPS jak i po sieci komórkowej :)

    OdpowiedzUsuń
  3. Dopiero zaczynam swoją przygodę z androidem. Próbowałem uruchomić Pański kod, ale za każdym razem dostaję:

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.gps/com.example.gps.MainActivity}: java.lang.NullPointerException

    Czym to może być spowodowane?

    OdpowiedzUsuń
    Odpowiedzi
    1. Wróć do podstaw.

      Usuń
    2. Czy mógłby Pan wyjaśnić tą kwestię? też mam z tym problem...

      Usuń
  4. https://www.google.pl/maps/place/52%C2%B017%2757.6%22N+20%C2%B059%2736.5%22E/@52.299359,20.993404,3a,90y,123h,90t/data=!3m4!1e1!3m2!1sZv8c7m1YNMY0JmmcnunnOw!2e0!4m2!3m1!1s0x0:0x0!6m1!1e1

    OdpowiedzUsuń
  5. gps mi pokazuje cały czas tę samą lokalizację. dostawca network. czy to z powodu że mam internet w wifi routerze który jest w jednym stałym miejscu?

    OdpowiedzUsuń
  6. Na androidzie 4.2.2 (mam włączone usługi GPS) chwilę po włączeniu aplikacji pojawia się komunikat "Aplikacja GPS została zatrzymana", wie ktoś dlaczego mi nie działa?

    PS: na emulatorze też nie działa

    OdpowiedzUsuń