尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

一文看懂 Compose Navigation 实现原理

一文看懂 Compose Navigation 实现原理 前言一个纯 Compose 项目少不了页面导航的支持而navigation-compose几乎是这方面的唯一选择这也使得它成为 Compose 工程的标配二方库。介绍navigation-compose如何使用的文章很多了然而在代码设计上 Navigation 也非常值得大家学习那么本文就带大家深挖一下其实现原理。1. 从 Jetpack Navigation 说起Jetpack Navigatioin 是一个通用的页面导航框架navigation-compose只是其针对 Compose 的的一个具体实现。抛开具体实现Navigation 在核心公共层定义了以下重要角色角色说明NavHost定义导航的入口同时也是承载导航页面的容器NavController导航的全局管理者维护着导航的静态和动态信息静态信息指 NavGraph动态信息即导航过长中产生的回退栈 NavBackStacksNavGraph定义导航时需要收集各个节点的导航信息并统一注册到导航图中NavDestination导航中的各个节点携带了 routearguments 等信息Navigator导航的具体执行者NavController 基于导航图获取目标节点并通过 Navigator 执行跳转上述角色中的NavHost、Navigatot、NavDestination等在不同场景中都有对应的实现。例如在传统视图中我们使用 Activity 或者 Fragment 承载页面以navigation-fragment为例:Frament 就是导航图中的一个个 NavDestination我们通过 DSL 或者 XMlL 方式定义 NavGraph 将 Fragment 信息以 NavDestination 的形式收集到导航图NavHostFragment 作为 NavHost 为 Fragment 页面的展现提供容器我们通过 FragmentNavigator 实现具体页面跳转逻辑FragmentNavigator#navigate 的实现中基于 FragmentTransaction#replace 实现页面替换通过 NavDestination 关联的的 Fragment 类信息实例化 Fragment 对象完成 replace。再看一下我们今天的主角navigation-compose。像navigation-fragment一样Compose 针对 Navigator 以及 NavDestination 都是自己的具体实现有点特殊的是 NavHost它只是一个 Composable 函数所以与公共库没有继承关系不同于 Fragment 这样对象组件Compose 使用函数定义页面那么navigation-compose是如何将 Navigation 落地到 Compose 这样的声明式框架中的呢接下来我们分场景进行介绍。2. 定义导航NavHost(navController navController, startDestination profile) { composable(profile) { Profile(/*...*/) } composable(friendslist) { FriendsList(/*...*/) } /*...*/ }Compose 中的 NavHost 本质上是一个 Composable 函数与navigation-runtime中的同名接口没有派生关系但职责是相似的主要目的都是构建 NavGraph。 NavGraph 创建后会被 NavController 持有并在导航中使用因此 NavHost 接受一个 NavController 参数并为其赋值 NavGraph//androidx/navigation/compose/NavHost.kt Composable public fun NavHost( navController: NavHostController, startDestination: String, modifier: Modifier Modifier, route: String? null, builder: NavGraphBuilder.() - Unit ) { NavHost( navController, remember(route, startDestination, builder) { navController.createGraph(startDestination, route, builder) }, modifier ) } Composable public fun NavHost( navController: NavHostController, graph: NavGraph, modifier: Modifier Modifier ) { //... //设置 NavGraph navController.graph graph //... }如上在 NavHost 及其同名函数中完成对 NavController 的 NavGraph 赋值。代码中 NavGraph 通过navController#createGraph进行创建内部会基于 NavGraphBuilder 创建 NavGraph 对象在 build 过程中调用NavHost{...}参数中的 builder 完成初始化。这个 builder 是 NavGraphBuilder 的扩展函数我们在使用NavHost{...}定义导航时会在 {…} 这里面通过一系列 · 定义 Compose 中的导航页面。· 也是 NavGraphBuilder 的扩展函数通过参数传入页面在导航中的唯一 route。//androidx/navigation/compose/NavGraphBuilder.kt public fun NavGraphBuilder.composable( route: String, arguments: ListNamedNavArgument emptyList(), deepLinks: ListNavDeepLink emptyList(), content: Composable (NavBackStackEntry) - Unit ) { addDestination( ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply { this.route route arguments.forEach { (argumentName, argument) - addArgument(argumentName, argument) } deepLinks.forEach { deepLink - addDeepLink(deepLink) } } ) }compose(...)的具体实现如上创建一个ComposeNavigator.Destination并通过NavGraphBuilder#addDestination添加到 NavGraph 的 nodes 中。 在构建 Destination 时传入两个成员:provider[ComposeNavigator::class]通过 NavigatorProvider 获取的 ComposeNavigatorcontent: 当前页面对应的 Composable 函数当然这里还会为 Destination 传入 routeargumentsdeeplinks 等信息。//androidx/navigation/compose.ComposeNavigator.kt public class Destination( navigator: ComposeNavigator, internal val content: Composable (NavBackStackEntry) - Unit ) : NavDestination(navigator)非常简单就是在继承自 NavDestination 之外多存储了一个 Compsoable 的 content。Destination 通过调用这个 content显示当前导航节点对应的页面后文会看到这个 content 是如何被调用的。3. 导航跳转跟 Fragment 导航一样Compose 当好也是通过NavController#navigate指定 route 进行页面跳转navController.navigate(friendslist)如前所述 NavController· 最终通过 Navigator 实现具体的跳转逻辑比如FragmentNavigator通过FragmentTransaction#replace实现 Fragment 页面的切换那我们看一下ComposeNavigator#navigate的具体实现//androidx/navigation/compose/ComposeNavigator.kt public class ComposeNavigator : NavigatorDestination() { //... override fun navigate( entries: ListNavBackStackEntry, navOptions: NavOptions?, navigatorExtras: Extras? ) { entries.forEach { entry - state.pushWithTransition(entry) } } //... }这里的处理非常简单没有 FragmentNavigator 那样的具体处理。NavBackStackEntry代表导航过程中回退栈中的一个记录entries就是当前页面导航的回退栈。state 是一个NavigatorState对象这是 Navigation 2.4.0 之后新引入的类型用来封装导航过程中的状态供 NavController 等使用比如 backStack 就是存储在NavigatorState中//androidx/navigation/NavigatorState.kt public abstract class NavigatorState { private val backStackLock ReentrantLock(true) private val _backStack: MutableStateFlowListNavBackStackEntry MutableStateFlow(listOf()) public val backStack: StateFlowListNavBackStackEntry _backStack.asStateFlow() //... public open fun pushWithTransition(backStackEntry: NavBackStackEntry) { //... push(backStackEntry) } public open fun push(backStackEntry: NavBackStackEntry) { backStackLock.withLock { _backStack.value _backStack.value backStackEntry } } //... }当 Compose 页面发生跳转时会基于目的地 Destination 创建对应的 NavBackStackEntry 然后经过pushWithTransition压入回退栈。backStack 是一个 StateFlow 类型所以回退栈的变化可以被监听。回看NavHost{...}函数的实现我们会发现原来在这里监听了 backState 的变化根据栈顶的变化调用对应的 Composable 函数实现了页面的切换。//androidx/navigation/compose/ComposeNavigator.kt Composable public fun NavHost( navController: NavHostController, graph: NavGraph, modifier: Modifier Modifier ) { //... // 为 NavController 设置 NavGraph navController.graph graph //SaveableStateHolder 用于记录 Composition 的局部状态后文介绍 val saveableStateHolder rememberSaveableStateHolder() //... // 最新的 visibleEntries 来自 backStack 的变化 val visibleEntries //... val backStackEntry visibleEntries.lastOrNull() if (backStackEntry ! null) { Crossfade(backStackEntry.id, modifier) { //... val lastEntry backStackEntry lastEntry.LocalOwnersProvider(saveableStateHolder) { //调用 Destination#content 显示当前导航对应的页面 (lastEntry.destination as ComposeNavigator.Destination).content(lastEntry) } } } //... }如上NavHost 中除了为 NavController 设置 NavGraph更重要的工作是监听 backStack 的变化刷新页面。navigation-framgent中的页面切换在 FragmentNavigator 中命令式的完成的而navigation-compose的页面切换是在 NavHost 中用响应式的方式进行刷新这也体现了声明式 UI与命令式 UI 在实现思路上的不同。visibleEntries是基于NavigatorState#backStack得到的需要显示的 Entry它是一个 State所以当其变化时 NavHost 会发生重组Crossfade会根据 visibleEntries 显示对应的页面。页面显示的具体实现也非常简单在 NavHost 中调用 BackStack 应的Destination#content即可这个 content 就是我们在NavHost{...}中为每个页面定义的 Composable 函数。4. 保存状态前面我们了解了导航定义和导航跳转的具体实现原理接下来看一下导航过程中的状态保存。navigation-compose的状态保存主要发生在以下两个场景中点击系统 back 键或者调用 NavController#popup 时导航栈顶的 backStackEntry 弹出导航返回前一页面此时我们希望前一页面的状态得到保持在配合底部导航栏使用时点击 nav bar 的 Item 可以在不同页面间切换此时我们希望切换回来的页面保持之前的状态上述场景中我们希望在页面切换过程中不会丢失例如滚动条位置等的页面状态但是通过前面的代码分析我们也知道了 Compose 导航的页面切换本质上就是在重组调用不同的 Composable。默认情况下Composable 的状态随着其从 Composition 中的离开即重组中不再被执行而丢失。那么navigation-compose是如何避免状态丢失的呢这里的关键就是前面代码中出现的SaveableStateHolder了。SaveableStateHolder rememberSaveableSaveableStateHolder 来自compose-runtime定义如下interface SaveableStateHolder { Composable fun SaveableStateProvider(key: Any, content: Composable () - Unit) fun removeState(key: Any) }从名字上不难理解SaveableStateHolder维护着可保存的状态Saveable State我们可以在它提供的SaveableStateProvider内部调用 Composable 函数Composable 调用过程中使用rememberSaveable定义的状态都会通过 key 进行保存不会随着 Composable 的生命周期的结束而丢弃当下次 SaveableStateProvider 执行时可以通过 key 恢复保存的状态。我们通过一个实验来了解一下 SaveableStateHolder 的作用Composable fun SaveableStateHolderDemo(flag: Boolean) { val saveableStateHolder rememberSaveableStateHolder() Box { if (flag) { saveableStateHolder.SaveableStateProvider(true) { Screen1() } } else { saveableStateHolder.SaveableStateProvider(false) { Screen2() } } }上述代码我们可以通过传入不同 flag 实现 Screen1 和 Screen2 之前的切换saveableStateHolder.SaveableStateProvider可以保证 Screen 内部状态被保存。例如你在 Screen1 中使用rememberScrollState()定义了一个滚动条状态当 Screen1 再次显示时滚动条仍然处于消失时的位置因为 rememberScrollState 内部使用 rememberSaveable 保存了滚动条的位置。如果不了解 rememberSaveable 可以参考 developer.android.com/jetpack/com… remember rememberSaveable 可以跨越 Composable 的生命周期更长久的保存状态在横竖屏切换甚至进程重启的场景中可以实现状态恢复。需要注意的是如果我们在 SaveableStateProvider 之外使用 rememberSaveable 虽然可以在横竖屏切换时保存状态但是在导航场景中是无法保存状态的。因为使用 rememberSaveable 定义的状态只有在配置变化时会被自动保存但是在普通的 UI 结构变化时不会触发保存而 SaveableStateProvider 主要作用就是能够在onDispose的时候实现状态保存主要代码如下//androidx/compose/runtime/saveable/SaveableStateHolder.kt Composable fun SaveableStateProvider(key: Any, content: Composable () - Unit) { ReusableContent(key) { // 持有 SaveableStateRegistry val registryHolder ... CompositionLocalProvider( LocalSaveableStateRegistry provides registryHolder.registry, content content ) DisposableEffect(Unit) { ... onDispose { //通过 SaveableStateRegistry 保存状态 registryHolder.saveTo(savedStates) ... } } }rememberSaveable 中的通过SaveableStateRegistry进行保存上面代码中可以看到在 onDispose 生命周期中通过registryHolder#saveTo将状态保存到了 savedStatessavedStates 用于下次进入 Composition 时的状态恢复。顺便提一下这里使用ReusableContent{...}可以基于 key 复用 LayoutNode有利于 UI 更快速地重现。导航回退时的状态保存简单介绍了一下 SaveableStateHolder 的作用之后我们看一下在 NavHost 中它是如何发挥作用的Composable public fun NavHost( ... ) { ... //SaveableStateHolder 用于记录 Composition 的局部状态后文介绍 val saveableStateHolder rememberSaveableStateHolder() ... Crossfade(backStackEntry.id, modifier) { ... lastEntry.LocalOwnersProvider(saveableStateHolder) { //调用 Destination#content 显示当前导航对应的页面 (lastEntry.destination as ComposeNavigator.Destination).content(lastEntry) } } ... }lastEntry.LocalOwnersProvider(saveableStateHolder)内部调用了Destination#content LocalOwnersProvider 内部其实就是对 SaveableStateProvider 的调用Composable public fun NavBackStackEntry.LocalOwnersProvider( saveableStateHolder: SaveableStateHolder, content: Composable () - Unit ) { CompositionLocalProvider( LocalViewModelStoreOwner provides this, LocalLifecycleOwner provides this, LocalSavedStateRegistryOwner provides this ) { // 调用 SaveableStateProvider saveableStateHolder.SaveableStateProvider(content) } }如上在调用 SaveableStateProvider 之前通过 CompositonLocal 注入了很多 Owner这些 Owner 的实现都是 this即指向当前的 NavBackStackEntryLocalViewModelStoreOwner : 可以基于 BackStackEntry 的创建和管理 ViewModelLocalLifecycleOwner提供 LifecycleOwner便于进行基于 Lifecycle 订阅等操作LocalSavedStateRegistryOwner通过 SavedStateRegistry 注册状态保存的回调例如 rememberSaveable 中的状态保存其实通过 SavedStateRegistry 进行注册并在特定时间点被回调可见在基于导航的单页面架构中NavBackStackEntry 承载了类似 Fragment 一样的责任例如提供页面级的 ViewModel 等等。前面提到SaveableStateProvider 需要通过 key 恢复状态那么这个 key 是如何指定的呢。LocalOwnersProvider 中调用的 SaveableStateProvider 没有指定参数 key原来它是对内部调用的包装Composable private fun SaveableStateHolder.SaveableStateProvider(content: Composable () - Unit) { val viewModel viewModelBackStackEntryIdViewModel() //设置 saveableStateHolder后文介绍 viewModel.saveableStateHolder this // SaveableStateProvider(viewModel.id, content) DisposableEffect(viewModel) { onDispose { viewModel.saveableStateHolder null } } }真正的 SaveableStateProvider 调用在这里而 key 是通过 ViewModel 管理的。因为 NavBackStackEntry 本身就是 ViewModelStoreOwner新的 NavBackStackEntry 被压栈时下面的 NavBackStackEntry 以及其所辖的 ViewModel 依然存在。当 NavBackStackEntry 重新回到栈顶时可以从 BackStackEntryIdViewModel 中获取之前保存的 id传入 SaveableStateProvider。BackStackEntryIdViewModel 的实现如下//androidx/navigation/compose/BackStackEntryIdViewModel.kt internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() { private val IdKey SaveableStateHolder_BackStackEntryKey // 唯一 ID可通过 SavedStateHandle 保存和恢复 val id: UUID handle.getUUID(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) } var saveableStateHolder: SaveableStateHolder? null override fun onCleared() { super.onCleared() saveableStateHolder?.removeState(id) } }虽然从名字上看BackStackEntryIdViewModel 主要是用来管理 BackStackEntryId 的但其实它也是当前 BackStackEntry 的 saveableStateHolder 的持有者ViewModel 在 SaveableStateProvider 中被传入 saveableStateHolder只要 ViewModel 存在UI 状态就不会丢失。当前 NavBackStackEntry 出栈后对应 ViewModel 发生 onCleared 此时会通过 saveableStateHolder#removeState removeState 清空状态后续再次导航至此 Destination 时不会遗留之前的状态。底部导航栏切换时的状态保存navigation-compose 常用来配合 BottomNavBar 实现多Tab页的切换。如果我们直接使用 NavController#navigate 切换 Tab 页会造成 NavBackStack 的无限增长所以我们需要在页面切换后从栈里及时移除不需要显示的页面例如下面这样val navController rememberNavController() Scaffold( bottomBar { BottomNavigation { ... items.forEach { screen - BottomNavigationItem( ... onClick { navController.navigate(screen.route) { // 避免 BackStack 增长跳转页面时将栈内 startDestination 之外的页面弹出 popUpTo(navController.graph.findStartDestination().id) { //出栈的 BackStack 保存状态 saveState true } // 避免点击同一个 Item 时反复入栈 launchSingleTop true // 如果之前出栈时保存状态了那么重新入栈时恢复状态 restoreState true } } ) } } } ) { NavHost(...) { ... } }上面代码的关键是通过设置 saveState 和 restoreState保证了 NavBackStack 出栈时保存对应 Destination 的状态当 Destination 再次被压栈时可以恢复。状态想要保存就意味着相关的 ViewModle 不能销毁而前面我们知道了 NavBackStack 是 ViewModelStoreOwner如何在 NavBackStack 出栈后继续保存 ViewModel 呢其实 NavBackStack 所辖的 ViewModel 是存在 NavController 中管理的从上面的类图可以看清他们的关系 NavController 持有一个 NavControllerViewModel它是 NavViewModelStoreProvider 的实现通过 Map 管理着各 NavController 对应的 ViewModelStore。NavBackStackEntry 的 ViewModelStore 就取自 NavViewModelStoreProvider 。当 NavBackStackEntry 出栈时其对应的 Destination#content 移出画面执行 onDisposeCrossfade(backStackEntry.id, modifier) { ... DisposableEffect(Unit) { ... onDispose { visibleEntries.forEach { entry - //显示中的 Entry 移出屏幕调用 onTransitionComplete composeNavigator.onTransitionComplete(entry) } } } lastEntry.LocalOwnersProvider(saveableStateHolder) { (lastEntry.destination as ComposeNavigator.Destination).content(lastEntry) } }onTransitionComplete 中调用 NavigatorState#markTransitionCompleteoverride fun markTransitionComplete(entry: NavBackStackEntry) { val savedState entrySavedState[entry] true ... if (!backQueue.contains(entry)) { ... if (backQueue.none { it.id entry.id } !savedState) { viewModel?.clear(entry.id) //清空 ViewModel } ... } ... }默认情况下 entrySavedState[entry] 为 false这里会执行 viewModel#clear 清空 entry 对应的 ViewModel但是当我们在 popUpTo { … } 中设置 saveState 为 true 时entrySavedState[entry] 就为 true因此此处就不会执行 ViewModel#clear。如果我们同时设置了 restoreState 为 true当下次同类型 Destination 进入页面时k可以通过 ViewModle 恢复状态。//androidx/navigation/NavController.kt private fun navigate( ... ) { ... //restoreState设置为true后命中此处的 shouldRestoreState() if (navOptions?.shouldRestoreState() true backStackMap.containsKey(node.id)) { navigated restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras) } ... }restoreStateInternal 中根据 DestinationId 找到之前对应的 BackStackId进而通过 BackStackId 找回 ViewModel恢复状态。5. 导航转场动画navigation-fragment 允许我们可以像下面这样通过资源文件指定跳转页面时的专场动画findNavController().navigate( R.id.action_fragmentOne_to_fragmentTwo, null, navOptions { anim { enter android.R.animator.fade_in exit android.R.animator.fade_out } } )由于 Compose 动画不依靠资源文件navigation-compose 不支持上面这样的 anim { … } 但相应地 navigation-compose 可以基于 Compose 动画 API 实现导航动画。注意navigation-compose 依赖的 Comopse 动画 API 例如 AnimatedContent 等目前尚处于实验状态因此导航动画暂时只能通过 accompanist-navigation-animation 引入待动画 API 稳定后未来会移入 navigation-compose。dependencies { implementation com.google.accompanist:accompanist-navigation-animation:version }添加依赖后可以提前预览 navigation-compose 导航动画的 API 形式AnimatedNavHost( navController navController, startDestination AppScreen.main, enterTransition { slideInHorizontally( initialOffsetX { it }, animationSpec transSpec ) }, popExitTransition { slideOutHorizontally( targetOffsetX { it }, animationSpec transSpec ) }, exitTransition { ... }, popEnterTransition { ... } ) { composable( AppScreen.splash, enterTransition null, exitTransition null ) { Splash() } composable( AppScreen.login, enterTransition null, exitTransition null ) { Login() } composable( AppScreen.register, enterTransition null, exitTransition null ) { Register() } ... }API 非常直观可以在AnimatedNavHost中统一指定 Transition 动画也可以在各个 composable 参数中分别指定。回想一下NavHost 中的Destination#content是在 Crossfade 中调用的熟悉 Compose 动画的就不难联想到可以在此处使用 AnimatedContent 为 content 的切换指定不同的动画效果navigatioin-compose正是这样做的//com/google/accompanist/navigation/animation/AnimatedNavHost.kt Composable public fun AnimatedNavHost( navController: NavHostController, graph: NavGraph, modifier: Modifier Modifier, contentAlignment: Alignment Alignment.Center, enterTransition: (AnimatedContentScopeNavBackStackEntry.() - EnterTransition) { fadeIn(animationSpec tween(700)) }, exitTransition: ..., popEnterTransition: ..., popExitTransition: ..., ) { ... val backStackEntry visibleTransitionsInProgress.lastOrNull() ?: visibleBackStack.lastOrNull() if (backStackEntry ! null) { val finalEnter: AnimatedContentScopeNavBackStackEntry.() - EnterTransition { ... } val finalExit: AnimatedContentScopeNavBackStackEntry.() - ExitTransition { ... } val transition updateTransition(backStackEntry, label entry) transition.AnimatedContent( modifier, transitionSpec { finalEnter(this) with finalExit(this) }, contentAlignment, contentKey { it.id } ) { ... currentEntry?.LocalOwnersProvider(saveableStateHolder) { (currentEntry.destination as AnimatedComposeNavigator.Destination) .content(this, currentEntry) } } ... } ... }如上 AnimatedNavHost 与普通的 NavHost 的主要区别就是将 Crossfade 换成了Transition#AnimatedContent。finalEnter和finalExit是根据参数计算得到的 Compose Transition 动画通过transitionSpec进行指定。以 finalEnter 为例看一下具体实现val finalEnter: AnimatedContentScopeNavBackStackEntry.() - EnterTransition { val targetDestination targetState.destination as AnimatedComposeNavigator.Destination if (composeNavigator.isPop.value) { //当前页面即将出栈执行pop动画 targetDestination.hierarchy.firstNotNullOfOrNull { destination - //popEnterTransitions 中存储着通过 composable 参数指定的动画 popEnterTransitions[destination.route]?.invoke(this) } ?: popEnterTransition.invoke(this) } else { //当前页面即将入栈执行enter动画 targetDestination.hierarchy.firstNotNullOfOrNull { destination - enterTransitions[destination.route]?.invoke(this) } ?: enterTransition.invoke(this) } }如上popEnterTransitions[destination.route]是 composable(…) 参数中指定的动画所以 composable 参数指定的动画优先级高于 AnimatedNavHost 。6. Hilt Navigation由于每个 BackStackEntry 都是一个 ViewModelStoreOwner我们可以获取导航页面级别的 ViewModel。使用hilt-viewmodle-navigation可以通过 Hilt 为 ViewModel 注入必要的依赖降低 ViewModel 构造成本。dependencies { implementation androidx.hilt:hilt-navigation-compose:1.0.0 }基于 hilt 获取 ViewModel 的效果如下// import androidx.hilt.navigation.compose.hiltViewModel Composable fun MyApp() { NavHost(navController, startDestination startRoute) { composable(example) { backStackEntry - // 通过 hiltViewModel() 获取 MyViewModel val viewModel hiltViewModelMyViewModel() MyScreen(viewModel) } /* ... */ } }我们只需要为MyViewModel添加HiltViewModel和Inject注解其参数依赖的repository可以通过 Hilt 自动注入省去我们自定义 ViewModelFactory 的麻烦。HiltViewModel class MyViewModel Inject constructor( private val savedStateHandle: SavedStateHandle, private val repository: ExampleRepository ) : ViewModel() { /* ... */ }简单看一下 hiltViewModel 的源码Composable inline fun reified VM : ViewModel hiltViewModel( viewModelStoreOwner: ViewModelStoreOwner checkNotNull(LocalViewModelStoreOwner.current) { No ViewModelStoreOwner was provided via LocalViewModelStoreOwner } ): VM { val factory createHiltViewModelFactory(viewModelStoreOwner) return viewModel(viewModelStoreOwner, factory factory) } Composable PublishedApi internal fun createHiltViewModelFactory( viewModelStoreOwner: ViewModelStoreOwner ): ViewModelProvider.Factory? if (viewModelStoreOwner is NavBackStackEntry) { HiltViewModelFactory( context LocalContext.current, navBackStackEntry viewModelStoreOwner ) } else { null }前面介绍过LocalViewModelStoreOwner就是当前的 BackStackEntry拿到 viewModelStoreOwner 之后通过HiltViewModelFactory()获取 ViewModelFactory。 HiltViewModelFactory 是hilt-navigation的范围这里就不深入研究了。7. 最后navigation-compose的其他一些功能例如 DeeplinksArguments 等等在实现上针对 Compose 没有什么特殊处理这里就不特别介绍了有兴趣可以翻阅navigation-common的源码。通过本文的一系列介绍我们可以看出navigation-compose无论在 API 的设计上还是在具体实现上都遵循了声明式的基本思想当我们需要开发自己的 Compose 三方库时可以从中参考和借鉴。作者fundroid链接https://juejin.cn/post/7135253864411824165学习资源如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。一、架构师筑基必备技能1、深入理解Java泛型2、注解深入浅出3、并发编程4、数据传输与序列化5、Java虚拟机原理6、高效IO……二、Android百大框架源码解析1.Retrofit 2.0源码解析2.Okhttp3源码解析3.ButterKnife源码解析4.MPAndroidChart 源码解析5.Glide源码解析6.Leakcanary 源码解析7.Universal-lmage-Loader源码解析8.EventBus 3.0源码解析9.zxing源码分析10.Picasso源码解析11.LottieAndroid使用详解及源码解析12.Fresco 源码分析——图片加载流程三、Android性能优化实战解析腾讯Bugly:对字符串匹配算法的一点理解爱奇艺安卓APP崩溃捕获方案——xCrash字节跳动深入理解Gradle框架之一Plugin, Extension, buildSrc百度APP技术Android H5首屏优化实践支付宝客户端架构解析Android 客户端启动速度优化之「垃圾回收」携程从智行 Android 项目看组件化架构实践网易新闻构建优化如何让你的构建速度“势如闪电”…四、高级kotlin强化实战1、Kotlin入门教程2、Kotlin 实战避坑指南3、项目实战《Kotlin Jetpack 实战》从一个膜拜大神的 Demo 开始Kotlin 写 Gradle 脚本是一种什么体验Kotlin 编程的三重境界Kotlin 高阶函数Kotlin 泛型Kotlin 扩展Kotlin 委托协程“不为人知”的调试技巧图解协程suspend五、Android高级UI开源框架进阶解密1.SmartRefreshLayout的使用2.Android之PullToRefresh控件源码解析3.Android-PullToRefresh下拉刷新库基本用法4.LoadSir-高效易用的加载反馈页管理框架5.Android通用LoadingView加载框架详解6.MPAndroidChart实现LineChart折线图7.hellocharts-android使用指南8.SmartTable使用指南9.开源项目android-uitableview介绍10.ExcelPanel 使用指南11.Android开源项目SlidingMenu深切解析12.MaterialDrawer使用指南六、NDK模块开发1、NDK 模块开发2、JNI 模块3、Native 开发工具4、Linux 编程5、底层图片处理6、音视频开发7、机器学习七、Flutter技术进阶1、Flutter跨平台开发概述2、Windows中Flutter开发环境搭建3、编写你的第一个Flutter APP4、Flutter开发环境搭建和调试5、Dart语法篇之基础语法(一)6、Dart语法篇之集合的使用与源码解析(二)7、Dart语法篇之集合操作符函数与源码分析(三)…八、微信小程序开发1、小程序概述及入门2、小程序UI开发3、API操作4、购物商场项目实战……全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证卡片免费领取↓↓↓
返回列表