Zbuduj własną aplikację Uber/Lyft z funkcją śledzenia geolokalizacji

PubNub Developer Relations - Feb 22 - - Dev Community

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

Google Maps API i konfiguracja PubNub

Najpierw musimy napisać nasze zależności Gradle, aby korzystać z PubNub Android SDK i usług Google Play dla funkcji mapy. Zaimplementujemy równieżbiblioteki

com.fasterxml.jackson
Enter fullscreen mode Exit fullscreen mode

, 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.

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'
Enter fullscreen mode Exit fullscreen mode

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.

<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"/>
Enter fullscreen mode Exit fullscreen mode

Aby skonfigurować PubNub w aplikacji na Androida, należy utworzyć aplikację PubNub w PubNub Admin Dashboard (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. 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.

<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" />
Enter fullscreen mode Exit fullscreen mode

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

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));
   }
});
Enter fullscreen mode Exit fullscreen mode

Następnie, używając naszych poświadczeń PubNub z panelu administracyjnego, utworz ymyinstancję

PNConfiguration
Enter fullscreen mode Exit fullscreen mode

, która pozwoli nam utworzyć instancję

PubNub
Enter fullscreen mode Exit fullscreen mode

łą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

onCreate()
Enter fullscreen mode Exit fullscreen mode

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

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);
    }
Enter fullscreen mode Exit fullscreen mode

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.

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);
   }
}
Enter fullscreen mode Exit fullscreen mode

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

FusedLocationProviderClient
Enter fullscreen mode Exit fullscreen mode

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

LocationRequest
Enter fullscreen mode Exit fullscreen mode

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.

LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(5000);
locationRequest.setFastestInterval(5000);
locationRequest.setSmallestDisplacement(10);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Enter fullscreen mode Exit fullscreen mode

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

mFusedLocationClient.requestLocationUpdates(locationRequest, new LocationCallback() {
   @Override
   public void onLocationResult(LocationResult locationResult) {
       Location location = locationResult.getLastLocation();
...
Enter fullscreen mode Exit fullscreen mode

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ć

MainActivity.pubnub
Enter fullscreen mode Exit fullscreen mode

, 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.

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());
           }
       });
Enter fullscreen mode Exit fullscreen mode

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.

<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" />
Enter fullscreen mode Exit fullscreen mode

Następnie należy utworzyć instancję obiektu

MapFragment
Enter fullscreen mode Exit fullscreen mode

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

onMapReady
Enter fullscreen mode Exit fullscreen mode

poprzez implementację PassengerActivity

onMapReadyCallback
Enter fullscreen mode Exit fullscreen mode

. Następnie przekażemy

PassengerActivity.this
Enter fullscreen mode Exit fullscreen mode

jako parametr do metody

getMapAsync
Enter fullscreen mode Exit fullscreen mode

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

mMapFragment = (SupportMapFragment) getSupportFragmentManager()
       .findFragmentById(R.id.map);
mMapFragment.getMapAsync(PassengerActivity.this);
Enter fullscreen mode Exit fullscreen mode

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

SubscribeCallback
Enter fullscreen mode Exit fullscreen mode

, 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ę

JsonUtil
Enter fullscreen mode Exit fullscreen mode

, która zawiera metodę

fromJson()
Enter fullscreen mode Exit fullscreen mode

, 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ę

updateUI()
Enter fullscreen mode Exit fullscreen mode

i przekazując nową lokalizację jako parametr .

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();
Enter fullscreen mode Exit fullscreen mode

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

Marker
Enter fullscreen mode Exit fullscreen mode

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

animateCar()
Enter fullscreen mode Exit fullscreen mode

. 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.

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)));
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

LatLngInterpolator
Enter fullscreen mode Exit fullscreen mode

poniżej. Pozwoli nam to zaimplementować metodę

interpolate()
Enter fullscreen mode Exit fullscreen mode

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

getAnimatedFraction()
Enter fullscreen mode Exit fullscreen mode

. Wywołamy tę metodę z instancji

ValueAnimator
Enter fullscreen mode Exit fullscreen mode

którą otrzymamy z metody

onAnimationUpdate()
Enter fullscreen mode Exit fullscreen mode

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.

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();
    }
Enter fullscreen mode Exit fullscreen mode
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);
           }
       }
   }
Enter fullscreen mode Exit fullscreen mode

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.

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.

Więcej zasobów

Jak PubNub może ci pomóc?

Ten artykuł został pierwotnie opublikowany na PubNub.com

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, aby zrozumieć podstawowe koncepcje każdej aplikacji opartej na PubNub w mniej niż 5 minut.

Rozpocznij konfigurację

Załóż konto PubNub, aby uzyskać natychmiastowy i bezpłatny dostęp do kluczy PubNub.

Rozpocznij

Dokumenty PubNub pozwolą Ci rozpocząć pracę, niezależnie od przypadku użycia lub zestawu SDK.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .