Deferred Navigation — 什么是延迟导航、原理及实现

作者: IT Sectr 发布日期: 2026-06-10 阅读时间: 9 分钟

Deferred Navigation 是一种延迟导航模式,其中到下一个屏幕的转换在异步操作完成后发生,而不是在用户操作时直接发生。根据 Android Developers (2024),延迟导航可以避免导航和数据加载之间的竞态条件,同时简化来自推送通知和 Deeplink 的转换处理。关键区别 — 路由在所有必要数据可用后计算。

要点

  • Deferred Navigation — 延迟导航,转换在异步操作完成后执行
  • 直接导航立即处理转换,deferred — 等待数据
  • 典型场景:授权、Deeplink、推送通知、配置加载
  • 在 Android 中通过 Navigation Component 配合回调和 StateFlow 实现
  • 在 iOS 中使用 Combine 或 async/await 配合导航协调器

什么是 Deferred Navigation

Deferred Navigation 是一种架构模式,其中导航决策被延迟到所有必要数据可用时。与直接转换不同,用户点击按钮后立即进入新屏幕,延迟导航将触发事件和实际转换分开,在它们之间添加异步操作。

在架构上,Deferred Navigation 基于状态变化:按钮点击启动异步过程,对其结果的订阅触发导航。这在具有 MVVM 或 MVI 架构的应用程序中尤为重要,其中 ViewModel 管理状态,View(Activity、Fragment、SwiftUI View)订阅变化并通过转换做出响应。这种方法消除了 UI 和导航逻辑之间的直接依赖关系。

根据 Google I/O 2023 的数据,延迟导航推荐用于所有导航依赖于网络请求结果、授权检查、配置加载或权限的场景。在处理 Deeplink 时,此模式也是强制性的,当应用程序必须先启动、加载根屏幕然后才能导航到目标路由。

何时需要延迟导航

Deferred Navigation 应用于直接转换导致屏幕状态错误或加载错误的场景。让我们考虑延迟导航是强制性的四种主要情况。

授权和身份验证

如果用户点击受保护的内容,应用程序必须首先检查访问令牌。直接导航到内容屏幕将导致空白屏幕或 401 错误(如果令牌已过期)。延迟导航检查令牌,仅在成功时进入目标屏幕。失败时重定向到登录屏幕。

Deeplink 处理

当应用程序通过外部链接打开时,必须先加载根屏幕、恢复导航状态,然后才能通过 Deeplink 执行转换。没有根上下文的直接导航到目标屏幕将导致异常:导航堆栈为空或 back stack 损坏。

带内容的推送通知

点击推送通知时,应用程序可能处于三种状态:关闭、后台或活动。Deferred Navigation 确定应用程序状态,加载必要内容,然后才显示目标屏幕。iOS 允许通过 UNNotificationContentExtension 处理此场景。

动态功能开关

如果屏幕功能由服务器端的功能开关控制,延迟导航允许先请求配置然后再显示屏幕。如果功能被禁用,用户看到替代内容或占位符而不是空白屏幕。

场景直接导航Deferred Navigation
授权令牌过期时空白屏幕重定向到登录
DeeplinkBack stack 损坏正确的导航堆栈
推送无上下文加载数据在转换前准备好
功能开关显示不可用功能占位符或替代方案

Deferred Navigation vs 直接导航

直接导航 是一种传统方法,其中转换作为对事件的响应立即执行。用户点击按钮,UI 路由器立即切换屏幕。这种方法简单且可预测,但在需要服务器数据或条件检查的场景中受到限制。

Deferred Navigation 添加了一个异步状态的层次。用户事件启动操作,对结果的订阅管理导航。这增加了代码的复杂性,但提供了灵活性:相同的触发器可以根据加载的数据导向不同的屏幕。

两种方法之间的选择取决于需求:如果显示屏幕不需要异步数据 — 使用直接导航。如果屏幕依赖于请求结果、授权或外部条件 — 延迟导航是强制性的。混合方法(其中部分转换是直接的,部分是延迟的)是生产应用程序中最常见的实践。

Android 中的实现:Navigation Component 和 ViewModel

Android Jetpack 提供了在架构级别实现 Deferred Navigation 的机制。主要思想 — ViewModel 管理状态,Activity 或 Fragment 订阅变化并通过 NavController 触发导航。

使用 StateFlow 的 Deferred Navigation

Kotlin 协程中的 StateFlow 是延迟导航的理想工具。ViewModel 使用导航事件更新 StateFlow,Activity 观察它并执行转换。事件处理后,StateFlow 被清除,防止重复导航。

kotlin
class MainViewModel : ViewModel() {
    private val _navigation = MutableSharedFlow<NavigationEvent>()
    val navigation: SharedFlow<NavigationEvent> = _navigation

    fun onDeepLinkReceived(link: String) {
        viewModelScope.launch {
            val data = resolveDeepLink(link)
            _navigation.emit(NavigationEvent.GoToScreen(data))
        }
    }
}

使用 NavController 的 Deferred Navigation

Activity 中,订阅导航触发带有 ViewModel 路由的 NavController。为防止屏幕旋转时重复导航,使用 NavigationEventWrapper 包装器,它只处理一次事件。Jetpack Navigation 2.7+ 支持 Safe Args 用于类型安全的参数传递。

kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val vm: MainViewModel by viewModels()
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            vm.navigation.collect { event ->
                when (event) {
                    is NavigationEvent.GoToScreen ->
                        findNavController(R.id.nav_host)
                            .navigate(event.route)
                }
            }
        }
    }
}

iOS 中的实现:Coordinator Pattern 和 Combine

iOS 没有类似于 Android Jetpack 的内置 Navigation Component,因此开发人员通过 Coordinator Pattern 结合 Combine 或 async/await 实现 Deferred Navigation。Coordinator 管理屏幕堆栈并根据加载的数据做出导航决策。

Coordinator 与 Combine

Coordinator 是一个管理 ViewController 之间导航的对象。与 Combine 配合,ViewModel 通过 PassthroughSubject 发布事件,Coordinator 订阅它们并执行转换。这种方法将 UI 与导航逻辑完全分离,并符合 Apple 的应用程序架构建议。

swift
final class AppCoordinator {
    private var cancellables = Set<AnyCancellable>()

    func start(viewModel: MainViewModel) {
        viewModel.$navigationDestination
            .compactMap { $0 }
            .sink { [weak self] destination in
                self?.navigateTo(destination)
            }
            .store(in: &cancellables)
    }
}

使用 async/await 的 Deferred Navigation

Swift 5.5 引入了结构化并发,允许通过 async/await 实现延迟导航而无需 Combine。ViewModel 提供一个异步函数,在数据加载后返回路由。Coordinator 在 Task 中调用此函数并根据接收到的路由执行转换。

swift
class AuthViewModel: ObservableObject {
    func resolveDeeplink(_ url: URL) async -> AppRoute? {
        guard let token = await AuthService.shared.getValidToken() else { return .login }
        return await DeeplinkRouter.resolve(url, token: token)
    }
}

// 在 Coordinator 中:
Task {
    if let route = await viewModel.resolveDeeplink(url) {
        navigateTo(route)
    }
}

常见错误和最佳实践

Deferred Navigation 简化了异步场景的处理,但需要对状态管理采取纪律性的方法。让我们看看开发人员在实现延迟导航时犯的主要错误。

错误:在初始化完成前导航

最常见的错误 — 在根屏幕完全初始化且 NavController 或 Coordinator 准备好转换之前尝试执行 延迟导航。在 Android 中这导致 IllegalStateException,在 iOS 中 — 未定义的 UI 状态。解决方案 — 在启动导航之前确保组件的生命周期处于 STARTED 或 RESUMED 状态。

错误:返回屏幕时重复导航

如果 StateFlow 或 Subject 在处理后不清除事件,返回上一个屏幕时用户可能被自动重定向到同一屏幕。在 Android 中使用 SharedFlow(replay=0)或在 iOS 中使用处理后设置为 nil 的 CurrentValueSubject,以便导航事件只触发一次。

最佳实践:单一 NavController 或 Coordinator

使用一个中央组件管理应用程序中的整个导航。当每个 Activity、Fragment 或 ViewController 都有自己的导航控制器时,应用程序不同部分之间的延迟导航变得混乱。单一的 Coordinator 便于调试和测试导航场景。

最佳实践:测试延迟导航场景

延迟导航比直接导航更难测试,因为异步操作引入了时间因素。在 Android 中使用 TestDispatcher(kotlinx-coroutines-test)和在 iOS 中使用 XCTestExpectation 来模拟数据加载,并验证导航是否按预期路由执行。模拟授权和深度链接服务以隔离测试每个场景。

常见问题

Deferred Navigation 与 Deep Link 有何不同?

Deferred Navigation 是一种延迟转换模式,可用于任何异步场景。Deep Link 是延迟导航的触发器之一,但不是唯一的。授权和功能开关也使用延迟导航。

能否组合延迟和直接导航?

可以,大多数应用程序使用混合方法。产品列表屏幕(没有异步依赖)可以使用直接导航,而带有数据加载的详情屏幕使用延迟。划分由特定屏幕的架构决定。

如何避免延迟导航中的竞态条件?

使用 Android 中不带 replay 的 SharedFlow 和 iOS 中不带缓冲的 combineLatest。在新触发器时取消先前的订阅。这确保只处理最后一个导航事件。

Jetpack Compose 是否支持延迟导航?

支持,在 Compose 中延迟导航通过订阅 ViewModel 的 StateFlow 并在 LaunchedEffect 中调用 NavController.navigate 来实现。Google 推荐将 Navigation Compose 与事件模型一起用于延迟场景。

应用最小化时如何处理延迟导航?

延迟导航直到应用再次变为活动状态。在 Android 中使用 Lifecycle.State.STARTED 过滤事件。在 iOS 中检查 Combine 或 async/await 块中的 UIApplication.State。

总结

  • Deferred Navigation — 转换在异步操作完成后执行的模式
  • 主要场景:授权、Deeplink、推送通知、功能开关
  • StateFlow/SharedFlow 在 Android 和 Combine/async-await 在 iOS — 实现工具
  • Deferred Navigation 与直接导航不同,不会在异步加载时引起竞态条件
  • Navigation Component 在 Android 和 Coordinator Pattern 在 iOS — 基础架构
  • 单一的 NavController/Coordinator 对于一致的导航是强制性的
  • 测试延迟导航需要 TestDispatcher 和异步服务的 mock

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读