앱은 사용자가 경로를 따라 이동할 때 발생하는 이벤트를 수신 대기할 수 있습니다.
개요
다음 인터페이스를 사용하여 탐색 이벤트를 수신 대기합니다.
코드 보기
탐색 활동의 Java 코드를 표시하거나 숨깁니다.
package com.example.navsdkmultidestination;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.android.gms.maps.GoogleMap.CameraPerspective;
import com.google.android.libraries.navigation.ArrivalEvent;
import com.google.android.libraries.navigation.ListenableResultFuture;
import com.google.android.libraries.navigation.NavigationApi;
import com.google.android.libraries.navigation.Navigator;
import com.google.android.libraries.navigation.RoadSnappedLocationProvider;
import com.google.android.libraries.navigation.SimulationOptions;
import com.google.android.libraries.navigation.SupportNavigationFragment;
import com.google.android.libraries.navigation.TimeAndDistance;
import com.google.android.libraries.navigation.Waypoint;
import java.util.ArrayList;
import java.util.List;
/**
* An activity that displays a map and a navigation UI, guiding the user from their current location
* to multiple destinations, also known as waypoints.
*/
public class NavigationActivityMultiDestination extends AppCompatActivity {
private static final String TAG = NavigationActivityMultiDestination.class.getSimpleName();
private static final String DISPLAY_BOTH = "both";
private static final String DISPLAY_TOAST = "toast";
private static final String DISPLAY_LOG = "log";
private Navigator mNavigator;
private RoadSnappedLocationProvider mRoadSnappedLocationProvider;
private SupportNavigationFragment mNavFragment;
private final List<Waypoint> mWaypoints = new ArrayList<>();
private Navigator.ArrivalListener mArrivalListener;
private Navigator.RouteChangedListener mRouteChangedListener;
private Navigator.RemainingTimeOrDistanceChangedListener mRemainingTimeOrDistanceChangedListener;
private RoadSnappedLocationProvider.LocationListener mLocationListener;
private Bundle mSavedInstanceState;
private static final String KEY_JOURNEY_IN_PROGRESS = "journey_in_progress";
private boolean mJourneyInProgress = false;
// Set fields for requesting location permission.
private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
private boolean mLocationPermissionGranted;
/**
* Sets up the navigator when the activity is created.
*
* @param savedInstanceState The activity state bundle.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Save the navigator state, used to determine whether a journey is in progress.
mSavedInstanceState = savedInstanceState;
if (mSavedInstanceState != null && mSavedInstanceState.containsKey(KEY_JOURNEY_IN_PROGRESS)) {
mJourneyInProgress = (mSavedInstanceState.getInt(KEY_JOURNEY_IN_PROGRESS) != 0);
}
setContentView(R.layout.activity_main);
// Initialize the Navigation SDK.
initializeNavigationSdk();
}
/** Releases navigation listeners when the activity is destroyed. */
@Override
protected void onDestroy() {
super.onDestroy();
if ((mJourneyInProgress) && (this.isFinishing())) {
mNavigator.removeArrivalListener(mArrivalListener);
mNavigator.removeRouteChangedListener(mRouteChangedListener);
mNavigator.removeRemainingTimeOrDistanceChangedListener(
mRemainingTimeOrDistanceChangedListener);
if (mRoadSnappedLocationProvider != null) {
mRoadSnappedLocationProvider.removeLocationListener(mLocationListener);
}
displayMessage("OnDestroy: Released navigation listeners.", DISPLAY_LOG);
}
}
/** Saves the state of the app when the activity is paused. */
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mJourneyInProgress) {
outState.putInt(KEY_JOURNEY_IN_PROGRESS, 1);
} else {
outState.putInt(KEY_JOURNEY_IN_PROGRESS, 0);
}
}
/**
* Starts the Navigation SDK and sets the camera to follow the device's location. Calls the
* navigateToPlaces() method when the navigator is ready.
*/
private void initializeNavigationSdk() {
/*
* Request location permission, so that we can get the location of the
* device. The result of the permission request is handled by a callback,
* onRequestPermissionsResult.
*/
if (ContextCompat.checkSelfPermission(
this.getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
} else {
ActivityCompat.requestPermissions(
this,
new String[] {android.Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
if (!mLocationPermissionGranted) {
displayMessage(
"Error loading Navigation SDK: " + "The user has not granted location permission.",
DISPLAY_BOTH);
return;
}
// Get a navigator.
NavigationApi.getNavigator(
this,
new NavigationApi.NavigatorListener() {
/** Sets up the navigation UI when the navigator is ready for use. */
@Override
public void onNavigatorReady(Navigator navigator) {
displayMessage("Navigator ready.", DISPLAY_BOTH);
mNavigator = navigator;
mNavFragment =
(SupportNavigationFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_fragment);
// Set the camera to follow the device location with 'TILTED' driving view.
mNavFragment.getMapAsync(
googleMap -> googleMap.followMyLocation(CameraPerspective.TILTED));
// Navigate to the specified places.
navigateToPlaces();
}
/**
* Handles errors from the Navigation SDK.
*
* @param errorCode The error code returned by the navigator.
*/
@Override
public void onError(@NavigationApi.ErrorCode int errorCode) {
switch (errorCode) {
case NavigationApi.ErrorCode.NOT_AUTHORIZED:
displayMessage(
"Error loading Navigation SDK: Your API key is "
+ "invalid or not authorized to use the Navigation SDK.",
DISPLAY_BOTH);
break;
case NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED:
displayMessage(
"Error loading Navigation SDK: User did not accept "
+ "the Navigation Terms of Use.",
DISPLAY_BOTH);
break;
case NavigationApi.ErrorCode.NETWORK_ERROR:
displayMessage("Error loading Navigation SDK: Network error.", DISPLAY_BOTH);
break;
case NavigationApi.ErrorCode.LOCATION_PERMISSION_MISSING:
displayMessage(
"Error loading Navigation SDK: Location permission " + "is missing.",
DISPLAY_BOTH);
break;
default:
displayMessage("Error loading Navigation SDK: " + errorCode, DISPLAY_BOTH);
}
}
});
}
/** Requests directions from the user's current location to a list of waypoints. */
private void navigateToPlaces() {
// Set up a waypoint for each place that we want to go to.
createWaypoint("ChIJq6qq6jauEmsRJAf7FjrKnXI", "Sydney Star");
createWaypoint("ChIJ3S-JXmauEmsRUcIaWtf4MzE", "Sydney Opera House");
createWaypoint("ChIJLwgLFGmuEmsRzpDhHQuyyoU", "Sydney Conservatorium of Music");
// If this journey is already in progress, no need to restart navigation.
// This can happen when the user rotates the device, or sends the app to the background.
if (mSavedInstanceState != null
&& mSavedInstanceState.containsKey(KEY_JOURNEY_IN_PROGRESS)
&& mSavedInstanceState.getInt(KEY_JOURNEY_IN_PROGRESS) == 1) {
return;
}
// Create a future to await the result of the asynchronous navigator task.
ListenableResultFuture<Navigator.RouteStatus> pendingRoute =
mNavigator.setDestinations(mWaypoints);
// Define the action to perform when the SDK has determined the route.
pendingRoute.setOnResultListener(
new ListenableResultFuture.OnResultListener<Navigator.RouteStatus>() {
@Override
public void onResult(Navigator.RouteStatus code) {
switch (code) {
case OK:
mJourneyInProgress = true;
// Hide the toolbar to maximize the navigation UI.
if (getActionBar() != null) {
getActionBar().hide();
}
// Register some listeners for navigation events.
registerNavigationListeners();
// Display the time and distance to each waypoint.
displayTimesAndDistances();
// Enable voice audio guidance (through the device speaker).
mNavigator.setAudioGuidance(Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE);
// Simulate vehicle progress along the route for demo/debug builds.
if (BuildConfig.DEBUG) {
mNavigator
.getSimulator()
.simulateLocationsAlongExistingRoute(
new SimulationOptions().speedMultiplier(5));
}
// Start turn-by-turn guidance along the current route.
mNavigator.startGuidance();
break;
// Handle error conditions returned by the navigator.
case NO_ROUTE_FOUND:
displayMessage("Error starting navigation: No route found.", DISPLAY_BOTH);
break;
case NETWORK_ERROR:
displayMessage("Error starting navigation: Network error.", DISPLAY_BOTH);
break;
case ROUTE_CANCELED:
displayMessage("Error starting navigation: Route canceled.", DISPLAY_BOTH);
break;
default:
displayMessage("Error starting navigation: " + String.valueOf(code), DISPLAY_BOTH);
}
}
});
}
/**
* Creates a waypoint from a given place ID and title.
*
* @param placeId The ID of the place to be converted to a waypoint.
* @param title A descriptive title for the waypoint.
*/
private void createWaypoint(String placeId, String title) {
try {
mWaypoints.add(Waypoint.builder().setPlaceIdString(placeId).setTitle(title).build());
} catch (Waypoint.UnsupportedPlaceIdException e) {
displayMessage(
"Error starting navigation: Place ID is not supported: " + placeId, DISPLAY_BOTH);
}
}
/** Displays the calculated travel time and distance to each waypoint. */
private void displayTimesAndDistances() {
List<TimeAndDistance> timesAndDistances = mNavigator.getTimeAndDistanceList();
int leg = 1;
String message = "You're on your way!";
for (TimeAndDistance timeAndDistance : timesAndDistances) {
message =
message
+ "\nRoute leg: "
+ leg++
+ ": Travel time (seconds): "
+ timeAndDistance.getSeconds()
+ ". Distance (meters): "
+ timeAndDistance.getMeters();
}
displayMessage(message, DISPLAY_BOTH);
}
/**
* Registers some event listeners to show a message and take other necessary steps when specific
* navigation events occur.
*/
private void registerNavigationListeners() {
mArrivalListener =
new Navigator.ArrivalListener() {
@Override
public void onArrival(ArrivalEvent arrivalEvent) {
displayMessage(
"onArrival: You've arrived at a waypoint: "
+ mNavigator.getCurrentRouteSegment().getDestinationWaypoint().getTitle(),
DISPLAY_BOTH);
// Start turn-by-turn guidance for the next leg of the route.
if (arrivalEvent.isFinalDestination()) {
displayMessage("onArrival: You've arrived at the final destination.", DISPLAY_BOTH);
} else {
mNavigator.continueToNextDestination();
mNavigator.startGuidance();
}
}
};
// Listens for arrival at a waypoint.
mNavigator.addArrivalListener(mArrivalListener);
mRouteChangedListener =
new Navigator.RouteChangedListener() {
@Override
public void onRouteChanged() {
displayMessage(
"onRouteChanged: The driver's route has changed. Current waypoint: "
+ mNavigator.getCurrentRouteSegment().getDestinationWaypoint().getTitle(),
DISPLAY_LOG);
}
};
// Listens for changes in the route.
mNavigator.addRouteChangedListener(mRouteChangedListener);
// Listens for road-snapped location updates.
mRoadSnappedLocationProvider = NavigationApi.getRoadSnappedLocationProvider(getApplication());
mLocationListener =
new RoadSnappedLocationProvider.LocationListener() {
@Override
public void onLocationChanged(Location location) {
displayMessage(
"onLocationUpdated: Navigation engine has provided a new"
+ " road-snapped location: "
+ location.toString(),
DISPLAY_LOG);
}
@Override
public void onRawLocationUpdate(Location location) {
displayMessage(
"onLocationUpdated: Navigation engine has provided a new"
+ " raw location: "
+ location.toString(),
DISPLAY_LOG);
}
};
if (mRoadSnappedLocationProvider != null) {
mRoadSnappedLocationProvider.addLocationListener(mLocationListener);
} else {
displayMessage("ERROR: Failed to get a location provider", DISPLAY_LOG);
}
mRemainingTimeOrDistanceChangedListener =
new Navigator.RemainingTimeOrDistanceChangedListener() {
@Override
public void onRemainingTimeOrDistanceChanged() {
displayMessage(
"onRemainingTimeOrDistanceChanged: Time or distance estimate" + " has changed.",
DISPLAY_LOG);
}
};
// Listens for changes in time or distance.
mNavigator.addRemainingTimeOrDistanceChangedListener(
60, 100, mRemainingTimeOrDistanceChangedListener);
}
/** Handles the result of the request for location permissions. */
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
mLocationPermissionGranted = false;
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION:
{
// If request is canceled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
}
}
}
}
/**
* Shows a message on screen and in the log. Used when something goes wrong.
*
* @param errorMessage The message to display.
*/
private void displayMessage(String errorMessage, String displayMedium) {
if (displayMedium.equals(DISPLAY_BOTH) || displayMedium.equals(DISPLAY_TOAST)) {
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
}
if (displayMedium.equals(DISPLAY_BOTH) || displayMedium.equals(DISPLAY_LOG)) {
Log.d(TAG, errorMessage);
}
}
}
목적지 도착 감지
여기서 대상은 최종 목적지 또는 중간 지점을 의미합니다. 도착을 감지하려면 기기가 목적지에 도착할 때 콜백을 등록하는 Navigator.addArrivalListener()
를 호출합니다.
도착하면 Android용 Navigation SDK가 onArrival()
콜백을 실행하고 세부 경로 안내를 중지합니다. 다음 중간 지점으로 이동하려면 Navigator.continueToNextDestination()
를 명시적으로 호출하고 세부 경로 내비게이션을 계속하려면 Navigator.startGuidance()
를 호출해야 합니다.
continueToNextDestination()
을 호출하면 탐색기가 이전 대상에 관한 모든 정보를 삭제합니다. 이전 경로 구간의 정보를 분석하려면 continueToNextDestination()
를 호출하기 전에 탐색기에서 정보를 가져와야 합니다.
메모리 누수를 방지하려면 더 이상 리스너가 필요하지 않을 때 removeArrivalListener(listener)
를 호출해야 합니다.
mNavigator.addArrivalListener(new Navigator.ArrivalListener() {
@Override
public void onArrival(ArrivalEvent arrivalEvent) {
displayMessage("onArrival: You've arrived at a waypoint: "
+ mNavigator.getCurrentRouteSegment().getDestinationWaypoint().getTitle(),
DISPLAY_BOTH);
// Start turn-by-turn guidance for the next leg of the route.
if (arrivalEvent.isFinalDestination()) {
displayMessage("onArrival: You've arrived at the final destination.",
DISPLAY_BOTH);
} else {
mNavigator.continueToNextDestination();
mNavigator.startGuidance();
}
}
});
위치 업데이트 받기
NavigationApi
에서 RoadSnappedLocationProvider
를 가져온 다음 RoadSnappedLocationProvider.addLocationListener()
를 호출하여 기기의 위치 또는 방향이 변경될 때 콜백을 등록합니다. 이 위치는 도로에 스냅되므로 Google Play 서비스 위치 API의 통합 위치 정보 제공자에서 반환한 위치와 다를 수 있습니다.
Navigation SDK는 최대한 자주 위치 업데이트를 제공합니다. 위치 업데이트를 사용할 수 있게 되면 내비게이션 SDK는 onLocationChanged()
콜백을 실행합니다.
도로 스냅 위치 업데이트는 내비게이션과 무관하며 내비게이션이 중지된 후에도 계속될 수 있습니다. 위치 업데이트를 구독하면 위치 업데이트를 백그라운드에서 실행한 상태로 두면 배터리 소모, 메모리 누수 또는 의도하지 않은 기기 위치 데이터 수집이 발생할 수 있습니다. 리스너가 더 이상 필요하지 않으면 RoadSnappedLocationProvider.removeLocationListener
를 호출합니다.
mRoadSnappedLocationProvider =
NavigationApi.getRoadSnappedLocationProvider(getApplication());
if (mRoadSnappedLocationProvider != null) {
mRoadSnappedLocationProvider.addLocationListener(
new RoadSnappedLocationProvider.LocationListener() {
@Override
public void onLocationChanged(Location location) {
displayMessage("onLocationUpdated: Navigation engine has provided a new"
+ " road-snapped location: "
+ location.toString(),
DISPLAY_LOG);
}
});
} else {
displayMessage("ERROR: Failed to get a location provider", DISPLAY_LOG);
}
시간 및 거리 업데이트 받기
Navigator.addRemainingTimeOrDistanceChangedListener()
를 호출하여 남은 시간 (초) 또는 거리(미터)가 지정된 기준점보다 더 많이 변경될 때 콜백을 등록합니다.
시간 또는 거리의 변화가 지정된 값을 초과하면 Navigation SDK가 onRemainingTimeOrDistanceChanged()
콜백을 실행합니다.
남은 시간과 거리를 찾으려면 Navigator.getTimeAndDistanceList()
를 호출합니다. 목록의 시간과 거리는 누적된 값입니다. 즉, 한 웨이포인트에서 다른 웨이포인트까지의 시간이 아니라 현재 위치에서 각 웨이포인트까지의 시간과 거리를 보여줍니다.
이제 TimeAndDistance
객체도 delaySeverity
를 반환합니다. 이 enum은 heavy, meduim, light, unknown 중 하나입니다. 이는 지도 UI의 예상 도착 시간에 표시되는 색상 (과도한 경우 = 빨간색, 중간 정도 = 노란색, 적은 경우 = 녹색)에 해당합니다. 자체 예상 도착 시간 바닥글을 만들어야 하는 경우에 유용합니다.
메모리 누수를 방지하려면 더 이상 리스너가 필요하지 않을 때 Navigator.removeRemainingTimeOrDistanceChangedListener(listener)
를 호출해야 합니다.
아래 샘플은 남은 시간이 60초 이상 변경되거나 남은 거리가 100미터 이상 변경되면 콜백을 요청합니다.
mNavigator.addRemainingTimeOrDistanceChangedListener(60, 100,
new Navigator.RemainingTimeOrDistanceChangedListener() {
@Override
public void onRemainingTimeOrDistanceChanged() {
displayMessage("onRemainingTimeOrDistanceChanged: Time or distance estimate"
+ " has changed.",
DISPLAY_LOG);
}
});
setEtaCardEnabled()
메서드를 사용하고 값이 TRUE
인 매개변수를 전달하여 내장 디스플레이를 사용하여 남은 시간 및 거리 정보를 표시할 수 있습니다. 시간 및 거리 표시를 숨기려면 이 값을 FALSE
로 설정합니다.
getTimeAndDistanceList()
메서드를 사용하여 여러 경유지의 도착 예정 시간을 노출할 수도 있습니다.
경로 업데이트 수신
Navigator.addRouteChangedListener()
를 호출하여 경로가 변경될 때 콜백을 등록합니다.
경로가 변경되면 Navigation SDK는 onRouteChanged()
콜백을 실행합니다. Navigator.getRouteSegments
및 Navigator.getCurrentRouteSegment()
를 호출하여 새 경로를 찾을 수 있습니다.
메모리 누수를 방지하려면 리스너가 더 이상 필요하지 않을 때 removeRouteChangedListener(listener)
를 호출해야 합니다.
mNavigator.addRouteChangedListener(new Navigator.RouteChangedListener() {
@Override
public void onRouteChanged() {
displayMessage("onRouteChanged: The driver's route has changed. Current waypoint: "
+ mNavigator.getCurrentRouteSegment().getDestinationWaypoint().getTitle(),
DISPLAY_LOG);
}
});
야간 모드 변경 감지
NavigationView.addOnNightModeChangedListener
또는 SupportNavigationFragment.addOnNightModeChangedListener
를 호출하여 야간 모드가 변경될 때 콜백을 등록합니다.
다음 예는 탐색 프래그먼트에서 야간 모드 변경사항을 리슨하는 것을 보여줍니다.
mNavFragment.addOnNightModeChangedListener(new NavigationView.OnNightModeChangedListener() {
@Override
public void onNightModeChanged(NightModeChangedEvent nightModeChangedEvent) {
displayMessage("Night mode is active: " + nightModeChangedEvent.inNightMode(),
DISPLAY_LOG);
}
});
프로그래매틱 방식으로 야간 모드를 설정할 수도 있습니다. 자세한 내용은 야간 모드 설정을 참고하세요.