Zbuduj własną aplikację Uber/Lyft z funkcją śledzenia geolokalizacji
Published: 2024-02-22 07:27:49

**W tym poście z naszego archiwum wyjaśniamy, jak zbudować własnego klona Ubera lub Lyfta, używając PubNub do śledzenia pozycji pojazdu. Podczas gdy niektóre fragmenty kodu mogą odnosić się do starszych wersji biblioteki, wszystko w tym artykule jest odpowiednie dla dzisiejszych odbiorców**  
  

Śledzenie lokalizacji innego urządzenia w czasie rzeczywistym jest podstawą wielu popularnych aplikacji mobilnych skoncentrowanych na lokalizacji, zwłaszcza jeśli chodzi o budowanie

takich jak Lyft i Uber. Budowanie tej funkcjonalności jest trudne i może być żmudne, ale uprościmy to dzięki łatwemu modelowi publikowania / subskrybowania między kierowcami a pasażerami, zasilanemu przez PubNub.

Przegląd samouczka
------------------

**[Pełny kod źródłowy](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo) tego samouczka jest dostępny tutaj.**

W tym samouczku pokażemy, jak zaimplementować ten powszechny przypadek użycia w aplikacji na Androida. Przykładowa aplikacja, którą zbudujemy, najpierw zapyta użytkownika, czy jest kierowcą, czy pasażerem. Jeśli użytkownik jest kierowcą, jego bieżąca lokalizacja zostanie opublikowana na kanale, aktualizowana co 5 sekund (w tym przykładzie) lub tyle razy, ile wymaga tego aplikacja.

Jeśli użytkownik jest pasażerem, zasubskrybuje ten sam kanał, aby otrzymywać aktualizacje dotyczące bieżącej lokalizacji swojego kierowcy. Ten model publikowania/subskrybowania jest przedstawiony na poniższej grafice.

[![Uber/Lyft App](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png "Uber/Lyft App")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4o/69ed800cea3e73d95979e9cad4cbfb3b/Screen-Shot-2018-06-06-at-11.58.58-AM.png)

Google Maps API i konfiguracja PubNub
-------------------------------------

Najpierw musimy napisać nasze zależności Gradle, aby korzystać z PubNub [Android SDK](https://pubnub.com/docs/sdks/java/android/) i usług Google Play dla funkcji mapy. Zaimplementujemy równieżbiblioteki

```js
com.fasterxml.jackson
```

, aby ukończyć podstawowe analizowanie JSON wiadomości Pub Nub.

W pliku build.gradle dla modułu aplikacji pod zależnościami wprowadź poniższy blok kodu, aby upewnić się, że mamy to, czego potrzebujemy. Upewnij się, że Usługi Google Play są zainstalowane z Menedżera Android SDK w części menu Narzędzia, abyśmy mogli zaimplementować niezbędne biblioteki.

```js
implementation group: 'com.pubnub', name: 'pubnub-gson', version: '4.12.0'
implementation 'com.google.android.gms:play-services-maps:15.0.1'
implementation "com.google.android.gms:play-services-location:15.0.1"
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.2'
```

Teraz, gdy mamy już niezbędne zależności, upewnijmy się, że mamy niezbędne uprawnienia w naszym pliku Android Manifest. Mając uprawnienia dostępu do stanu sieci i nawiązywania połączenia internetowego, użytkownik może teraz nawiązać połączenie z PubNub API. Uprawnienia do lokalizacji wykorzystamy później, gdy poprosimy o lokalizację kierowcy.

```js
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
```

Aby skonfigurować PubNub w aplikacji na Androida, należy utworzyć aplikację PubNub w [PubNub Admin Dashboard](https://dashboard.pubnub.com/signup/) **(jest to bezpłatne)**. Po utworzeniu aplikacji zostanie przypisany klucz subskrypcji i klucz publikacji. W dalszej części użyjemy tych poświadczeń w naszej klasie MainActivity do nawiązania połączenia.

Teraz musimy skonfigurować nasz klucz Google Maps API, abyśmy mogli go używać w naszej aplikacji. Aby to zrobić, przejdź do [konsoli Google Developer API](https://console.cloud.google.com/projectselector2/apis/dashboard?supportedpurview=project). Tutaj utworzymy nowy projekt, a następnie wygenerujemy klucz API. Teraz, gdy mamy już nasz klucz API, możemy napisać następujący kod, aby skonfigurować nasz interfejs API Map Google i skonfigurować go za pomocą naszego wygenerowanego klucza API.

```js
<meta-data
   android:name="com.google.android.gms.version"
   android:value="@integer/google_play_services_version" />
<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value=”ENTER_KEY_HERE" />
```

Główne działanie
----------------

Klasa MainActivity będzie miała trzy ważne funkcje:

1.  Kierowanie naszego użytkownika do jego odpowiedniego interfejsu, w zależności od tego, czy jest kierowcą, czy pasażerem.
2.  Ustanowienie połączenia z PubNub dla wszystkich użytkowników aplikacji
3.  Sprawdzanie uprawnień dostępu do lokalizacji

Po pierwsze, chcemy podzielić naszych użytkowników na kierowców i pasażerów i zapewnić im odpowiedni interfejs użytkownika. Zrobimy to, tworząc klasę DriverActivity i PassengerActivity, które omówimy szczegółowo w częściach 2 i 3.

W MainActivity stworzymy prosty interfejs oparty na przyciskach, który poprosi użytkownika o poinformowanie aplikacji o jego roli jako kierowcy lub pasażera. Korzystając z klasy Intent systemu Android, możemy przejść z klasy MainActivity do klasy DriverActivity lub z klasy MainActivity do klasy PassengerActivity, w zależności od tego, który przycisk zostanie naciśnięty.

Interfejs będzie wyglądał następująco:

[![screenshot](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png "screenshot")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1j4p/89ff1d0cceea0f257e1b66aacc1fa7a6/screenshot.png)

```js
driverButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, DriverActivity.class));
   }
});
passengerButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       startActivity(new Intent(MainActivity.this, PassengerActivity.class));
   }
});
```

Następnie, używając naszych poświadczeń PubNub z [panelu administracyjnego](https://dashboard.pubnub.com/signup), utworz ymyinstancję

```js
PNConfiguration
```

, która pozwoli nam utworzyć instancję

```js
PubNub
```

łączącą naszą aplikację z PubNub. Ta instancja PubNub w klasie MainActivity będzie publiczna i statyczna, więc będzie dostępna zarówno z DriverActivity, jak i PassengerActivity . Wywołamyponiższą metodę w naszej metodzie

```js
onCreate()
```

głównej aktywności, aby PubNub został poprawnie zainicjowany dla wszystkich użytkowników naszej aplikacji. Wszystko to jest pokazane w poniższym kodzie.

```js
private void initPubnub() {
        PNConfiguration pnConfiguration = new PNConfiguration();
        pnConfiguration.setSubscribeKey(Constants.PUBNUB_SUBSCRIBE_KEY);
        pnConfiguration.setPublishKey(Constants.PUBNUB_PUBLISH_KEY);
        pnConfiguration.setSecure(true);
        pubnub = new PubNub(pnConfiguration);
    }
```

Na koniec musimy upewnić się, że nasz użytkownik włączył usługi lokalizacyjne dla naszej aplikacji. Można to zrobić za pomocą następującego kodu. Jeśli nasz użytkownik nie udzieli pozwolenia na dostęp do dokładnej lub zgrubnej lokalizacji, nasza aplikacja zażąda tego pozwolenia.

```js
public void checkPermission() {
   if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
           ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
           ) {//Can add more as per requirement
       ActivityCompat.requestPermissions(this,
               new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
               123);
   }
}
```

Aktywność kierowcy
------------------

Jedyną funkcją klasy DriverActivity jest uzyskiwanie lokalizacji kierowcy i publikowanie tych danych w określonych odstępach czasu na kanale PubNub.

Najpierw przyjrzyjmy się, jak wykonać żądanie lokalizacji. Użyjemy klasy

```js
FusedLocationProviderClient
```

z Google Location API, aby zażądać aktualizacji lokalizacji od kierowcy . Użyjemy również klasy

```js
LocationRequest
```

w celu określenia ważnych parametrów naszego żądania lokalizacji, takich jak priorytet, najmniejsze przemieszczenie i interwał czasowy .

W naszym przykładzie użyjemy interwału czasowego 5000 milisekund, aby lokalizacja była aktualizowana co 5 sekund i najmniejszego przemieszczenia 10 metrów, aby zapewnić, że lokalizacja jest aktualizowana tylko wtedy, gdy nastąpiła zmiana o co najmniej 10 metrów. Wreszcie, chcemy, aby żądanie lokalizacji korzystało z GPS urządzenia mobilnego, aby zapewnić wysoką dokładność śledzenia lokalizacji. Ta wysoka dokładność jest ważna, aby pasażer mógł zobaczyć szczegółowy obraz swojego kierowcy (pokazany jako ikona samochodu) poruszającego się po mapie do miejsca docelowego.

```js
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(5000);
locationRequest.setSmallestDisplacement(10);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
```

Teraz, gdy zdefiniowaliśmy parametry żądania lokalizacji, przekażemy ten obiekt LocationRequest do żądania aktualizacji lokalizacji naszego obiektu FusedLocationProviderClient.

```js
mFusedLocationClient.requestLocationUpdates(locationRequest, new LocationCallback() {
   @Override
   public void onLocationResult(LocationResult locationResult) {
       Location location = locationResult.getLastLocation();
...
```

Korzystając z lokalizacji, którą otrzymujemy z naszego żądania, musimy przekonwertować ją na LinkedHashMap, aby pasowała do pożądanego formatu wiadomości o lokalizacji w kanale PubNub. LinkedHashMap będzie miała dwa klucze "lat" i "lng" oraz odpowiednie wartości dla tych kluczy, oba jako ciągi znaków.

Teraz, mając naszą wiadomość w formacie LinkedHashMap, możemy opublikować ją na kanale PubNub. Jest to pokazane w poniższym kodzie. Zauważ, że musimy użyć

```js
MainActivity.pubnub
```

, aby uzyskać instancję PubNub, którą wcześniej utworzyliśmy w metodzie onCreate aktywności Main. Nie chcemy nawiązywać wielu połączeń poprzez ponowną inicjalizację PubNub. Zamiast tego po prostu użyjemy tego, które już utworzyliśmy w klasie MainActivity.

```js
MainActivity.pubnub.publish()
       .message(message)
       .channel(Constants.PUBNUB_CHANNEL_NAME)
       .async(new PNCallback<PNPublishResult>() {
           @Override
           public void onResponse(PNPublishResult result, PNStatus status) {
               // handle publish result, status always present, result if successful
               // status.isError() to see if error happened
               if (!status.isError()) {
                   System.out.println("pub timetoken: " + result.getTimetoken());
               }
               System.out.println("pub status code: " + status.getStatusCode());
           }
       });
```

Aktywność pasażera
------------------

W naszej klasie PassengerActivity chcemy utworzyć MapView, aby pokazać lokalizację aktualizowanego kierowcy. Aby dodać element interfejsu użytkownika MapView do naszej aktywności Passenger, musimy dołączyć następujący kod do odpowiedniego pliku layout.xml dla tej aktywności.

```js
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/map"
   android:name="com.google.android.gms.maps.SupportMapFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.mapwithmarker.MapsMarkerActivity" />
```

Następnie należy utworzyć instancję obiektu

```js
MapFragment
```

przy użyciu identyfikatora zdefiniowanego w pliku układu.W naszym przykładowym kodzie użyliśmy "map" jako ID. Skonfigurujemyrównież naszą metodę

```js
onMapReady
```

poprzez implementację PassengerActivity

```js
onMapReadyCallback
```

. Następnie przekażemy

```js
PassengerActivity.this
```

jako parametr do metody```js
getMapAsync
```

naszego obiektu MapFragment .Pozwoli nam to skonfigurować metodę wywołania zwrotnego onMapReady dla naszego obiektu MapFragment.

```js
mMapFragment = (SupportMapFragment) getSupportFragmentManager()
       .findFragmentById(R.id.map);
mMapFragment.getMapAsync(PassengerActivity.this);
```

Teraz chcemy zasubskrybować kanał lokalizacji kierowcy, aby otrzymywać aktualizacje dotyczące bieżącej lokalizacji kierowcy. Należy to zrobić, gdy mapa będzie gotowa, więc umieścimy ten kod w naszej metodzie wywołania zwrotnego. Dodatkowo musimy dodać słuchacza z adresem

```js
SubscribeCallback
```

, aby nasza aplikacja wiedziała, jak działać po otrzymaniu wiadomości .

Jak określono w dokumentacji PubNub dla systemu Android, musimy to wykonać w kolejności, w której najpierw dodajemy słuchacza, a następnie subskrybujemy instancję PubNub do kanału. Po otrzymaniu wiadomości chcemy przekonwertować dane wyjściowe JSON na LinkedHashMap, które możemy łatwo przeanalizować, aby uzyskać długość i szerokość geograficzną. Aby przekonwertować dane wyjściowe JSON na LinkedHashMap, musimyzaimportować klasę

```js
JsonUtil
```

, która zawiera metodę

```js
fromJson()
```

, która pozwala nam to zrobić. Klasa ta jest pokazana w pełnym kodzie źródłowym. Gdy mamy już lokalizację kierowcy, musimy zaktualizować interfejs użytkownika, wywołując naszą metodę

```js
updateUI()
```

i przekazując nową lokalizację jako parametr .

```js
MainActivity.pubnub.addListener(new SubscribeCallback() {
    @Override
    public void status(PubNub pub, PNStatus status) {
    }
    @Override
    public void message(PubNub pub, final PNMessageResult message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Map<String, String> newLocation = JsonUtil.fromJson(message.getMessage().toString(), LinkedHashMap.class);
                    updateUI(newLocation);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    @Override
    public void presence(PubNub pub, PNPresenceEventResult presence) {
    }
});
MainActivity.pubnub.subscribe()
        .channels(Arrays.asList(Constants.PUBNUB_CHANNEL_NAME)) // subscribe to channels
        .execute();
```

Nasza metoda updateUI ma dwa przypadki, które napotka.

Pierwszy przypadek jest taki, że nie ma jeszcze znacznika dla kierowcy i w tym przypadku utworzymy instancję obiektu

```js
Marker
```

i ustawimy jego pozycję na pierwszą śledzoną lokalizację kierowcy. Musimy również powiększyć mapę, aby zachować widok ulicy samochodu kierowcy.

Drugi przypadek polega na tym, że istnieje już znacznik dla kierowcy i w tym przypadku nie musimy ponownie instancjonować obiektu Marker. Zamiast tego po prostu przenosimy lokalizację znacznika na mapie do nowej lokalizacji. Robimy topoprzez wywołanie metody

```js
animateCar()
```

. Musimy również upewnić się, że przenosimy kamerę MapView do nowej lokalizacji, jeśli znajduje się ona poza granicami kamery. W ten sposób znacznik kierowcy będzie zawsze znajdował się w granicach MapView. Pokazuje to poniższy kod.

```js
private void updateUI(Map<String, String> newLoc) {
        LatLng newLocation = new LatLng(Double.valueOf(newLoc.get("lat")), Double.valueOf(newLoc.get("lng")));
        if (driverMarker != null) {
            animateCar(newLocation);
            boolean contains = mGoogleMap.getProjection()
                    .getVisibleRegion()
                    .latLngBounds
                    .contains(newLocation);
            if (!contains) {
                mGoogleMap.moveCamera(CameraUpdateFactory.newLatLng(newLocation));
            }
        } else {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    newLocation, 15.5f));
            driverMarker = mGoogleMap.addMarker(new MarkerOptions().position(newLocation).
                    icon(BitmapDescriptorFactory.fromResource(R.drawable.car)));
        }
    }
```

W naszej metodzie animateCar musimy przesunąć znacznik samochodu do nowej lokalizacji w sposób płynny w kształcie linii. Animacja musi trwać 5 sekund, ponieważ nasza aplikacja otrzymuje aktualizacje lokalizacji kierowcy co 5 sekund. W ten sposób za każdym razem, gdy animacja się skończy, będzie miała nową aktualizację lokalizacji, aby rozpocząć następną animację. W ten sposób zminimalizujemy opóźnienie w animacji samochodu.

Wykorzystamy również poniższy interfejs

```js
LatLngInterpolator
```

poniżej. Pozwoli nam to zaimplementować metodę

```js
interpolate()
```

która zwraca nową współrzędną LatLng, która jest ułamkiem drogi do ostatecznej newLocation. Ułamek ten jest określany za pomocą metody

```js
getAnimatedFraction()
```

. Wywołamy tę metodę z instancji

```js
ValueAnimator
```

którą otrzymamy z metody

```js
onAnimationUpdate()
```

metody wywołania zwrotnego. Ułamek ten będzie wzrastał w stałym tempie, tak aby po 5 sekundach wynosił 1 (przez całą drogę do nowej lokalizacji). Ta animacja samochodu jest pokazana w poniższych fragmentach kodu.

```js
private void animateCar(final LatLng destination) {
        final LatLng startPosition = driverMarker.getPosition();
        final LatLng endPosition = new LatLng(destination.latitude, destination.longitude);
        final LatLngInterpolator latLngInterpolator = new LatLngInterpolator.LinearFixed();
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(5000); // duration 5 seconds
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                try {
                    float v = animation.getAnimatedFraction();
                    LatLng newPosition = latLngInterpolator.interpolate(v, startPosition, endPosition);
                    driverMarker.setPosition(newPosition);
                } catch (Exception ex) {
                }
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        valueAnimator.start();
    }
```

```js
private interface LatLngInterpolator {
       LatLng interpolate(float fraction, LatLng a, LatLng b);
       class LinearFixed implements LatLngInterpolator {
           @Override
           public LatLng interpolate(float fraction, LatLng a, LatLng b) {
               double lat = (b.latitude - a.latitude) * fraction + a.latitude;
               double lngDelta = b.longitude - a.longitude;
               if (Math.abs(lngDelta) > 180) {
                   lngDelta -= Math.signum(lngDelta) * 360;
               }
               double lng = lngDelta * fraction + a.longitude;
               return new LatLng(lat, lng);
           }
       }
   }
```

Dalsze ulepszenia
-----------------

Jeśli chcesz dalej rozwijać tę próbkę, jedną z funkcji, którą można dodać, byłoby obracanie znacznika podczas skręcania samochodu. Ta funkcja wymagałaby dodania jednej pary klucz-wartość do naszej struktury wiadomości. Musielibyśmy dołączyć klucz "łożysko" i wartość. Następnie po prostu obrócilibyśmy znacznik w zależności od namiaru kierowcy.

Gratulacje!
-----------

Teraz, gdy stworzyliśmy MainActivity, DriverActivity i PassengerActivity, z powodzeniem zbudowaliśmy model publikowania/subskrybowania niezbędny do stworzenia prostego demo Lyft/Uber na Androida. Kliknij tutaj, aby zobaczyć [pełny kod źródłowy](https://github.com/PubNubDevelopers/RealtimeTaxiAndroidDemo).

  
**Jeśli osadzona zawartość nie jest dostępna** na tej stronie, można ją również wyświetlić pod adresem [https://www.youtube.com/embed/kQ6EzqoQu5A](https://www.youtube.com/embed/kQ6EzqoQu5A).

Więcej zasobów
--------------

-   [Czy powinieneś dodać API geolokalizacji do swojej aplikacji?](https://www.pubnub.com/guides/should-you-add-a-geolocation-api-to-your-app/)
    
-   [Nasze demo geolokalizacji](https://www.pubnub.com/demos/geolocation-demo/)
    
-   [Ścieżka deweloperów geolokalizacji](https://www.pubnub.com/developers/geolocation-developer-path/)
    

Jak PubNub może ci pomóc?
=========================

Ten artykuł został pierwotnie opublikowany na [PubNub.com](https://www.pubnub.com/blog/build-your-own-uber-lyft-app-geolocation-tracking-android/)

Nasza platforma pomaga programistom tworzyć, dostarczać i zarządzać interaktywnością w czasie rzeczywistym dla aplikacji internetowych, aplikacji mobilnych i urządzeń IoT.

Fundamentem naszej platformy jest największa w branży i najbardziej skalowalna sieć przesyłania wiadomości w czasie rzeczywistym. Dzięki ponad 15 punktom obecności na całym świecie obsługującym 800 milionów aktywnych użytkowników miesięcznie i niezawodności na poziomie 99,999%, nigdy nie będziesz musiał martwić się o przestoje, limity współbieżności lub jakiekolwiek opóźnienia spowodowane skokami ruchu.

Poznaj PubNub
-------------

Sprawdź [Live Tour](https://www.pubnub.com/tour/introduction/), aby zrozumieć podstawowe koncepcje każdej aplikacji opartej na PubNub w mniej niż 5 minut.

Rozpocznij konfigurację
-----------------------

Załóż [konto](https://admin.pubnub.com/signup/) PubNub, aby uzyskać natychmiastowy i bezpłatny dostęp do kluczy PubNub.

Rozpocznij
----------

[Dokumenty](https://www.pubnub.com/docs) PubNub pozwolą Ci rozpocząć pracę, niezależnie od przypadku użycia lub [zestawu SDK](https://www.pubnub.com/docs).