关于此 Codelab
1. 准备工作
此 Codelab 将教您创建一个简单的 Android 应用,该应用使用 Google Maps Platform Navigation SDK 导航到预配置的目的地。
完成后,您的应用将如下所示。
前提条件
- 了解使用 Kotlin 进行 Android 应用开发的基础知识
- 对 Google Maps SDK 的基本概念(例如地图、地点、坐标)有一定了解。
学习内容
- 如何创建一个简单的 Android 应用,使用 Navigation SDK 导航到目的地。
- 如何从远程 Google Maven 制品库集成 Navigation SDK
- 如何管理位置信息权限和 Navigation SDK 最终用户条款的用户同意书
- 如何初始化 SDK
- 如何设置目的地并开始导航。
所需条件
- 安装了最新稳定版 Android Studio。此 Codelab 是使用 Android Studio Jellyfish 创建的。如果您使用的是其他版本,界面和组件的外观和布局可能会有所不同。
- 启用了结算功能的 Google 账号和项目。
- 一部处于开发者模式且启用了 USB 调试的 Android 设备或一个 Android 模拟器。无论您选择哪种方法,都必须满足 Navigation SDK 的最低要求
2. 进行设置
如果您还没有已启用结算功能的 Google Cloud Platform 账号和项目,请按照 Google Maps Platform 使用入门说明设置 Google Cloud 项目 https://developers.google.com/maps/gmp-get-started
在控制台中选择您的 Google Cloud 项目
在 Cloud 控制台中,点击项目下拉菜单,然后选择要用于此 Codelab 的项目。
在项目中启用 Navigation SDK
在 Google Cloud Marketplace 中,启用此 Codelab 所需的 Google Maps Platform API 和 SDK。
在 Google Cloud 控制台中,依次前往“API 和服务”>“库”,然后搜索“Navigation SDK”。
您应该会看到一条搜索结果。
点击 Navigation SDK 结果,打开“Product Details”页面。点击“启用”按钮,为您的项目启用 SDK。
针对 Google Maps SDK for Android 重复此过程。
创建 API 密钥
在 Cloud Console 的凭据页面中,生成 API 密钥。您可以按照 Google Maps Platform 使用入门中快速入门部分第 3 步中的步骤操作。向 Google Maps Platform 发出的所有请求都需要 API 密钥。
3. 获取示例项目文件
本部分介绍如何通过从此 Codelab 的 GitHub 代码库中克隆文件来设置基本的空 Android Studio 项目。GitHub 代码库包含 Codelab 代码的“优化前”和“优化后”版本。本 Codelab 将从空白项目模板开始,逐步构建到完成状态。如果遇到问题,您可以将代码库中已完成的项目作为参考。
克隆此 GitHub 代码库以获取此 Codelab 的代码。
git clone https://github.com/googlemaps-samples/codelab-navigation-101-android-kotlin.git
如果您未安装 git,请点击此按钮获取代码:
为帮助您尽快入门,代码库的 Starter
文件夹中包含一些起始代码,可帮助您顺利完成此 Codelab。起始项目提供了基本的应用界面和 build 配置,但未添加 Navigation SDK。此外,我们还提供了一个已完成的 Solution
项目,以便您随时跳转到后面的部分或查看进度。
在 Android Studio 中打开克隆的代码库
在本地克隆代码库后,使用 Android Studio 将 Starter
文件夹作为现有项目打开。
- 在“Welcome to Android Studio”对话框中,点击“Open”按钮。
- 前往您保存克隆代码库的文件夹,然后选择顶级“
codelab-navigation-101-android-kotlin
”文件夹中的Starter
文件夹。 - 检查项目是否构建并运行。
添加虚拟设备,或连接硬件设备
如需将 Android 设备连接到计算机,请按照有关如何在硬件设备上运行应用的 Android Studio 说明操作。您也可以使用 Android 虚拟设备 (AVD) 管理器来配置虚拟设备。选择模拟器时,请务必选择一个包含 Google API 的映像。
在 Android Studio 中,点击“Run”菜单选项或 Play 按钮图标。按提示选择设备。
4. 将 Navigation SDK 添加到您的应用
将 Navigation SDK 库和 API 密钥添加到您的项目中
如需将 Navigation SDK 库添加到您的应用,您需要修改应用级 build.gradle.kts
,以从 Google Maven 制品库中提取 Navigation SDK 并配置版本号。
在 build 配置中创建一个变量来存储 Navigation SDK 版本号。
在应用级 build.gradle.kts
中设置变量,以包含应用中使用的 Navigation SDK 版本的值,以便将来可以轻松更改为最新版本。
如需了解最新版本号,请参阅 Navigation SDK 版本说明。
val navSdkVersion by extra("6.0.0")
您还可以使用位于 文件 >项目结构 >变量:
向 build 配置添加依赖项
现在,将以下 API 依赖项添加到应用级 build.gradle.kts.
中的依赖项块中。所使用的版本将是您刚刚在应用级 build.gradle.kts
中设置的 ${navSdkVersion}
的值:
dependencies {
// Include the Google Navigation SDK.
api("com.google.android.libraries.navigation:navigation:${navSdkVersion}")
...
添加您的 API 密钥
使用 Secrets Gradle 插件管理 API 密钥
我们建议您使用 Secrets Gradle 插件来安全地管理应用中的 API 密钥。该插件已作为依赖项添加到初始项目模板中,并置于顶级 build.gradle.kts
文件中。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" apply false
//... other plugin definitions here
}
在顶级目录中打开 secrets.properties
文件,然后将 YOUR_API_KEY
替换为您的 API 密钥。由于 secrets.properties
不会签入版本控制系统,因此请将您的密钥存储在此文件中。
MAPS_API_KEY=YOUR_API_KEY
如需详细了解此主题,请参阅 Navigation SDK 文档中的向您的应用添加 API 密钥。
验证 local.defaults.properties 的内容
空项目还在顶级目录(即 secrets.properties
文件所在的文件夹)中包含一个 local.defaults.properties
文件。打开它,然后观察以下代码。
MAPS_API_KEY=DEFAULT_API_KEY
其目的是为 MAPS_API_KEY
属性提供备份值,以防 secrets.properties
未添加到项目中,以免构建失败。无需修改此文件。如果未找到 MAPS_API_KEY
的 secrets.properties
定义,使用默认值将在运行时停止应用运行,并显示 API 密钥错误。
检查 Android 清单是否使用了您指定的 API 密钥
打开 app/src/main/AndroidManifest.xml。您会注意到,MAPS_API_KEY
属性用于为应用设置 API 密钥:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
打开应用级 build.gradle.kts
文件并找到 secrets
属性。
插件的 propertiesFileName
设置应设为 secrets.properties
,defaultPropertiesFileName
应设为 local.defaults.properties
。
secrets {
// Optionally specify a different file name containing your secrets.
// The plugin defaults to "local.properties"
propertiesFileName = "secrets.properties"
// A properties file containing default secret values. This file can be
// checked in version control.
defaultPropertiesFileName = "local.defaults.properties"
}
保存所有文件,然后将项目与 Gradle 同步。
5. 配置应用权限并添加基本界面
请求确切位置信息权限
Navigation SDK 需要依赖 GPS 信号才能正常运行,因此您的应用需要请求用户授予对精确位置数据的访问权限。在 AndroidManifest.xml 中,将访问精确位置信息的权限添加为 <manifest>
元素的子元素。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
/>
</manifest>
如需详细了解 Android 位置信息权限,请参阅 Android 开发者文档的请求位置信息权限部分。
如需在 Android 14 设备上运行应用,请在与精确位置信息访问权限相同的位置添加以下 uses-permission
标记,以请求前台服务位置信息权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
添加具有基本界面的启动 activity
当您的应用运行时,它需要在启动期间执行代码,以检查用户是否已授予访问其位置信息的权限,并处理每种可能的情况,如果尚未授予权限,则请求权限。为此,请向应用添加基本界面。此 Codelab 使用的是您在 Android Studio 中创建新的空白 View 活动时创建的界面。您将对此进行调整,以便在向导航界面 activity 添加代码之前执行位置信息权限检查。
在代码编辑器中打开 MainActivity.kt
文件并检查代码,其中显示了一个基本界面。
在运行时请求位置信息访问权限
在 Navigation SDK 初始化之前,您的应用需要触发请求以访问精确位置信息。
为确保在应用启动时进行此检查,请在 activity 的替换 onCreate()
方法中向 MainActivity
类添加一些代码。
以下代码会检查用户是否已授予精确的位置信息权限。如果未授予,它将请求此权限。在 onCreate()
方法内添加以下代码。
val permissions =
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
arrayOf(permission.ACCESS_FINE_LOCATION, permission.POST_NOTIFICATIONS)
} else {
arrayOf(permission.ACCESS_FINE_LOCATION)
}
if (permissions.any { !checkPermissionGranted(it) }) {
if (permissions.any { shouldShowRequestPermissionRationale(it) }) {
// Display a dialogue explaining the required permissions.
}
val permissionsLauncher =
registerForActivityResult(
RequestMultiplePermissions(),
{ permissionResults ->
if (permissionResults.getOrDefault(permission.ACCESS_FINE_LOCATION, false)) {
onLocationPermissionGranted()
} else {
finish()
}
},
)
permissionsLauncher.launch(permissions)
} else {
android.os.Handler(Looper.getMainLooper()).postDelayed({ onLocationPermissionGranted() }, SPLASH_SCREEN_DELAY_MILLIS)
}
}
private fun checkPermissionGranted(permissionToCheck: String): Boolean =
ContextCompat.checkSelfPermission(this, permissionToCheck) == PackageManager.PERMISSION_GRANTED
向 MainActivity
类添加一个名为 onLocationPermissionGranted
的新函数,该函数将在用户授予分享其位置信息的权限时处理结果。在后续步骤中,我们将在此处添加代码以启动新的导航 activity。
private fun onLocationPermissionGranted() {
//code to initialize Navigation SDK will go here
}
构建您的项目。如果您有任何构建错误,请查找并修复这些错误。
在新的虚拟设备上运行您的项目。应用安装并启动时,您应该会看到权限请求对话框。
6. 添加导航界面
您可以通过以下两种方式添加导航界面:SupportNavigationFragment
或 NavigationView
。
为简单起见,此 Codelab 使用了 NavigationView
。
修改布局
修改 res/layout/activity_main.xml
以添加 NavigationView 的布局。
- 打开文件并切换到代码视图。
- 将该文件的全部内容替换为
RelativeLayout
中的NavigationView
的新布局,如以下示例所示。由于您只需向应用添加一个导航视图,因此简单的布局即可。 - 为 NavigationView 分配 ID“
@+id/navigation_view
”。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.libraries.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
设置 Navigation activity
在 Android Studio 中,在编辑器中打开 MainActivity.kt 文件。
添加一些基本设置代码,以确保导航体验在您的应用中正常运行。在 MainActivity.kt 文件中,进行以下更改:
- 在
MainActivity
类中声明一个变量以引用NavigationView
:
private lateinit var navView: NavigationView
- 向
onCreate()
方法添加一些代码,以获取对NavigationView
的引用:
navView = findViewById(R.id.navigation_view)
navView.onCreate(savedInstanceState)
- 向
onCreate()
方法添加一些代码,以确保屏幕在导航指导期间保持开启状态:
// Ensure the screen stays on during nav.
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- 修改调用
ViewCompat.setOnApplyWindowInsetsListener
的代码以引用NavigationView
的 ID。
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.navigation_view)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
- 将
showToast()
方法添加到类中,以便向用户显示反馈:
private fun showToast(errorMessage: String) {
Toast.makeText(this@MainActivity, errorMessage, Toast.LENGTH_LONG).show()
}
7. 初始化 Navigation SDK
现在,您已完成基本导航 activity 设置,可以初始化 Navigation SDK 了。为此,请将以下代码添加到 MainActivity.kt 文件中:
/** Starts the Navigation API, capturing a reference when ready. */
@SuppressLint("MissingPermission")
private fun initializeNavigationApi() {
NavigationApi.getNavigator(
this,
object : NavigatorListener {
override fun onNavigatorReady(navigator: Navigator) {
// store a reference to the Navigator object
mNavigator = navigator
// code to start guidance will go here
}
override fun onError(@NavigationApi.ErrorCode errorCode: Int) {
when (errorCode) {
NavigationApi.ErrorCode.NOT_AUTHORIZED -> {
// Note: If this message is displayed, you may need to check that
// your API_KEY is specified correctly in AndroidManifest.xml
// and is been enabled to access the Navigation API
showToast(
"Error loading Navigation API: Your API key is " +
"invalid or not authorized to use Navigation."
)
}
NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED -> {
showToast(
"Error loading Navigation API: User did not " +
"accept the Navigation Terms of Use."
)
}
else -> showToast("Error loading Navigation API: $errorCode")
}
}
},
)
}
此代码会创建一个名为 initializeNavigationApi()
的新方法。此方法通过调用 NavigationApi.getNavigator()
获取对 Navigator
对象的引用,并实现 NavigatorListener
来处理回调。
请注意,初始化 Navigation API 后,系统将调用 NavigationListener.onNavigatorReady
方法,并将 Navigator
对象作为参数传递。上述代码将使用传递给此方法的已初始化的 Navigator
对象更新您之前声明的 mNavigator
变量。
最后,从 onLocationPermissionGranted
方法添加对 initializeNavigationApi
方法的调用。
private fun onLocationPermissionGranted() {
initializeNavigationApi()
}
8. 为关键导航事件添加监听器
当用户遵循导航指示时,Navigation SDK 会触发事件,以便通知应用路途中的关键状态变化,例如用户重新路线或到达目的地时。在 MainActivity.kt 文件中,添加监听器来处理以下事件:
- 在
MainActivity
类中,声明两个变量来引用事件监听器对象:
private var arrivalListener: Navigator.ArrivalListener? = null
private var routeChangedListener: Navigator.RouteChangedListener? = null
- 添加
registerNavigationListeners()
方法,以便在初始化导航器时设置监听器。此方法会在 Arrival 事件触发时调用Navigator.clearDestinations()
来重置NavigationView
:
/**
* Registers a number of example event listeners that show an on screen message when certain
* navigation events occur (e.g. the driver's route changes or the destination is reached).
*/
private fun registerNavigationListeners() {
withNavigatorAsync {
arrivalListener =
Navigator.ArrivalListener { // Show an onscreen message
showToast("User has arrived at the destination!")
mNavigator?.clearDestinations()
}
mNavigator?.addArrivalListener(arrivalListener)
routeChangedListener =
Navigator.RouteChangedListener { // Show an onscreen message when the route changes
showToast("onRouteChanged: the driver's route changed")
}
mNavigator?.addRouteChangedListener(routeChangedListener)
}
}
- 在
initializeNavigationApi
方法的onNavigatorReady
回调代码中添加对registerNavigationListeners()
的调用:
override fun onNavigatorReady(navigator: Navigator) {
// store a reference to the Navigator object
mNavigator = navigator
//listen for events en route
registerNavigationListeners()
}
- 配置界面。在导航运行时,您可以控制导航界面的各个方面。一项重要的自定义是摄像头位置。添加对
onNavigatorReady
中返回的navigator
对象的setTaskRemovedBehaviour
方法的调用,如下所示。如果用户滑动关闭应用,系统会终止引导和通知:
// Disables the guidance notifications and shuts down the app and background service
// when the user dismisses/swipes away the app from Android's recent tasks.
navigator.setTaskRemovedBehavior(Navigator.TaskRemovedBehavior.QUIT_SERVICE)
- 添加对
GoogleMap.followMyLocation
的调用以指定CameraPerspective
。您可以通过NavigatorView.getMapAsync()
方法访问GoogleMap
,如下所示:
navView.getMapAsync {
googleMap ->
googleMap.followMyLocation(GoogleMap.CameraPerspective.TILTED)
}
- 为确保导航功能在整个应用生命周期内顺畅运行,请在
MainActivity
类中实现以下方法:
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
navView.onSaveInstanceState(savedInstanceState)
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
navView.onTrimMemory(level)
}
override fun onStart() {
super.onStart()
navView.onStart()
}
override fun onResume() {
super.onResume()
navView.onResume()
}
override fun onPause() {
navView.onPause()
super.onPause()
}
override fun onConfigurationChanged(configuration: Configuration) {
super.onConfigurationChanged(configuration)
navView.onConfigurationChanged(configuration)
}
override fun onStop() {
navView.onStop()
super.onStop()
}
override fun onDestroy() {
navView.onDestroy()
withNavigatorAsync {
// Unregister event listeners to avoid memory leaks.
if (arrivalListener != null) {
navigator.removeArrivalListener(arrivalListener)
}
if (routeChangedListener != null) {
navigator.removeRouteChangedListener(routeChangedListener)
}
navigator.simulator?.unsetUserLocation()
navigator.cleanup()
}
super.onDestroy()
}
9. 设置目的地
现在,您可以设置目的地并开始导航了。在 MainActivity.kt 文件中,进行以下更改:
- 添加了一个新的
navigateToPlace()
方法,用于设置导航目的地并接受placeId
参数。
/**
* Requests directions from the user's current location to a specific place (provided by the
* Place ID).
*/
private fun navigateToPlace(placeId: String) {
}
- 在
navigateToPlace()
方法中,使用Waypoint.builder()
方法根据传递给该方法的地点 ID 创建Waypoint
。在地点 ID 无法解析为精确地址的情况下,处理这会抛出的UnsupportedPlaceIdException
:
val waypoint: Waypoint? =
// Set a destination by using a Place ID (the recommended method)
try {
Waypoint.builder().setPlaceIdString(placeId).build()
} catch (e: Waypoint.UnsupportedPlaceIdException) {
showToast("Place ID was unsupported.")
return
}
- 将以下代码添加到
navigateToPlace()
方法中,以使用航点设置目的地:
val pendingRoute = mNavigator?.setDestination(waypoint)
// Set an action to perform when a route is determined to the destination
pendingRoute?.setOnResultListener { code ->
when (code) {
RouteStatus.OK -> {
// Code to start guidance will go here
}
RouteStatus.ROUTE_CANCELED -> showToast("Route guidance canceled.")
RouteStatus.NO_ROUTE_FOUND,
RouteStatus.NETWORK_ERROR ->
// TODO: Add logic to handle when a route could not be determined
showToast("Error starting guidance: $code")
else -> showToast("Error starting guidance: $code")
}
}
Navigator
对象有一个 setDestinations()
方法,可以接受各种参数。最基本的方法是提供 Waypoint
。这将默认采用 DRIVING
出行模式,适用于四轮车。setDestinations()
方法会返回一个包含 RouteStatus
对象的 ListenableResultFuture
对象。RouteStatus
将指示是否找到前往目的地的路线,并允许您处理各种错误状态(如果没有找到)。
- 进行其他配置更改,以改善导航用户体验:
// Hide the toolbar to maximize the navigation UI
supportActionBar?.hide()
// 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?.simulator?.simulateLocationsAlongExistingRoute(
SimulationOptions().speedMultiplier(5f)
)
}
这些更改包括以下改进:
- 隐藏操作栏以最大限度地增加导航界面的空间。
- 启用音频引导,以播报提醒和导航说明。
- 通过指定速度乘数来设置模拟器以进行调试。
- 找到要用作目的地的地点 ID。理想情况下,该位置不会离用户所在位置太远。请使用 Google Maps Platform 地点 ID 查找工具,或通过 Places API 调用获取地点 ID。
如果您要模拟导航,可以在代码中设置用户位置,也可以从已连接的设备中获取用户位置。此 Codelab 将假定您模拟英国伦敦的某个位置。
- 向
MainActivity
类添加一个伴生对象,以存储起始位置和地点 ID。此 Codelab 将使用伦敦的起始位置和 Trafalgar Square 的地点 ID:
companion object{
const val TRAFALGAR_SQUARE ="ChIJH-tBOc4EdkgRJ8aJ8P1CUxo" //London, UK
val startLocation = LatLng(51.345678, -0.1234456)
}
- 在
initializeNavigationApi
方法内的onNavigatorReady
回调中添加对navigateToPlace()
方法的调用,并添加一个将在调试模式下执行的逻辑分支,用于设置用户位置:
// Disables the guidance notifications and shuts down the app and background service
// when the user dismisses/swipes away the app from Android's recent tasks.
navigator.setTaskRemovedBehavior(Navigator.TaskRemovedBehavior.QUIT_SERVICE)
mNavigator = navigator
if (BuildConfig.DEBUG) {
mNavigator?.simulator?.setUserLocation(MainActivity.startLocation)
}
//listen for events en route
registerNavigationListeners()
navView.getMapAsync {
googleMap ->
googleMap.followMyLocation(GoogleMap.CameraPerspective.TILTED)
}
//navigate to a destination
navigateToPlace(MainActivity.TRAFALGAR_SQUARE)
10. 构建并运行代码
首次运行该应用时,您需要向该应用授予位置信息权限,并接受 Navigation SDK 使用条款。
注意:运行应用将调用 setDestinations() 方法,该方法在使用的目的地数量达到前 1000 个后会产生费用。如需了解详情,请参阅用量和结算。
设置位置
默认情况下,除非您在代码中或使用模拟器属性对话框设置位置,否则所模拟设备的位置可设为位于加利福尼亚州山景城的 Google 园区。
如果是这样,您可能会发现该应用找不到指向您所配置的地点 ID 的路线(默认情况下为澳大利亚悉尼歌剧院)。这会通过 showToast()
方法显示“找不到路线”消息来表明。
对起始位置进行硬编码
如需在代码中设置其他位置,请在 MainActivity.kt 内的 navigateToPlace()
方法中,在对 mNavigator.startGuidance()
的调用之前添加以下代码行:
mNavigator?.simulator?.setUserLocation(startLocation)
在您选择的默认位置启动模拟器
如需在设备模拟器中设置其他位置,请启动模拟器(如果尚未运行),然后点击带有“Extended Controls”提示的三点状菜单。打开的对话框中包含“位置信息”菜单选项。
例如,如果您使用悉尼歌剧院的地点 ID 作为目的地,请选择澳大利亚悉尼的地点。例如,搜索“北京海滩”,选择一条建议,然后点击“保存位置”。您还可以点击“保存点”,将相应地点添加到已保存的列表中,以备日后使用。
如果您为目的地设置了不同的地点 ID,请选择该目的地附近的位置,以确保模拟的路线是真实的路线,并且不会太长,不易调试。
重启应用,现在应用应该会导航到目的地。
12. 更多详情
如果您想进一步进行应用开发,不妨从以下主题中汲取灵感。
- 监听更多导航事件。添加代码,以便在司机重新规划路线或到达时显示消息。
- 自定义导航界面。
- 如果您想接受更大的挑战,不妨尝试添加一个 Places API 地点选择器控件,以允许用户设置目的地。提示:GitHub 上的 Navigation SDK 演示版应用提供了实现示例。
- 通过采用 GitHub 上的 Navigation SDK 演示版应用中所用的方法,防止在异步调用 Navigator 和 GoogleMap 对象时出现潜在问题。在更复杂的应用场景中,当代码运行时,这些对象可能尚未完成初始化。提示:您可以在 MainActivity.kt 文件的末尾添加 InitializedNavScope 类,以便快速实现。