Android :Compose如何监听生命周期?NavHostController和我们传统的Activity的任务栈有什么不同?

先问几个问题:

在使用传统的Activity+Fragment的时候,程序进入后台或者前台的时候会调用对应的生命周期,那么如果我们全部换成Compose以后,会如何呢?我们需要如何做?

Activity 任务栈 和Compose的Navhost有什么关系?比如compose的页面如何后退,如何跳转,如何传递数据等等。

Compose 从前台退到后台,再进入前台,那么会重组?还是会怎么?

在刚开始开发的时候,我们就会有这些疑问,今天我们就通过一个实战例子来解释一下。

1.1 WanAndroid的例子

本期,我们用的纯compose,来模仿一下Activity+Fragment

大致代码如下 (1)

kt 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContent {
        ComposeWanAndroidTheme {
            // 创建导航控制器
            val navController = rememberNavController()

            // 提供导航控制器给整个应用
            CompositionLocalProvider(LocalNavController provides navController) {
                // 主应用组件
                WanApp(navController)
            }
        }
    }
}
kt 复制代码
@Composable
fun WanApp(navController: NavHostController) {

    NavHost(navController = navController, startDestination = RoutePath.main) {
        composable(route = RoutePath.main) {//主页
            MainScaffold(navController)
        }

        composable(route = RoutePath.login) {//登录页
            LoginCompose(navController)
        }
    }

}

一、NavHostController:持有整个应用的导航状态(当前在哪个页面、后台的页面栈等)到时我们需要他做如下功能:

(1)比如返回:navController.navigateUp()-> 向上导航(类似于返回)。

(2)比如跳转:navController.navigate("login")-> 跳转到登录页。

二、NavHost :算是一个导航的容器吧,可以暂时把他类比为一个activity,然后composable(route = RoutePath.main)类似于Fragment。比如现在就是一个Activity+两个Fragment。他的内部会根据 navController当前指示的目的地,来显示对应的页面内容。 (1) navController = navController:告诉这个 NavHost要听从哪个控制器的指挥。

(2)startDestination = RoutePath.main:指定起始页面(或称主页)。当应用打开或栈为空时,就显示这个页面。RoutePath.main通常是一个字符串常量,比如 "main"

三、Composable :他的作用是注册页面。它告诉导航库:"当导航到 'login'这个地址时,请执行我括号里的 lambda 表达式,并把 LoginCompose这个界面显示出来"。

那么当我们执行这两个页面跳转是如何操作呢?这就需要我们使用一下NavHostController的操作。

kt 复制代码
Text("测试页面跳转", modifier = Modifier.clickable {
    //进行页面跳转,点击返回键,也可以返回。
    navHostController.navigate(RoutePath.login)

})
kt 复制代码
@Composable
fun LoginCompose(navHostController: NavHostController){
    Scaffold{innerPadding->
        Text("login页面", modifier = Modifier.clickable {
            //返回,可以看到其实他是对我们的返回函数,做了拦截处理。
            navHostController.navigateUp()
        }.padding(innerPadding))
    }

}

就这两句代码,我们就可以实现页面的切换和返回。

那么问一个问题,这里的页面跳转,是否会和activity一样,会执行一些onStop,或者返回的时候执行onResume方法?

其实这里和Activity还不太一样。我们先说,会不会重组。

其实是不会的。从 Main页跳到 Login页,再按返回键回到 Main页。Main页会恢复它之前的状态,而不会重新初始化。这是因为导航库自动管理了后台栈,之前的页面只是被保存在了后台,并没有被销毁。重组还是遵守之前的原则,当可观察的状态发生变化时才会执行。

那么如果我想类似于Activity一样,在页面切换的时候,执行一下onstop,返回的时候执行onresume呢?或者compose里面的写法是如何的?

在 Compose 中,我们通过​​副作用(Side Effects)​​ API 来模拟和响应这种"进入/退出"组件树的行为。

  1. LaunchedEffect- 模拟 onStart/ onResume
scss 复制代码
var timerValue by remember { mutableStateOf(0) }
    var isTimerActive by remember { mutableStateOf(true) }

    // 效果1:使用 LaunchedEffect 根据 isTimerActive 状态决定是否启动计时器
    LaunchedEffect(key1 = isTimerActive) { 
        // 只有当 isTimerActive 为 true 时,才启动这个协程
        if (isTimerActive) {
            while (true) {
                delay(1000L) // 每秒一次
                timerValue++
            }
        }
        // 当 isTimerActive 变为 false,或者此Composable退出组合时,协程会自动取消
    }
  1. DisposableEffect- 模拟 onStop/ onDestroy
go 复制代码
// 效果2:使用 DisposableEffect 来响应页面的"进入"和"退出"
    // 它非常适合执行订阅和清理操作
    DisposableEffect(Unit) { // key 是 Unit,意味着这个效果只在整个页面的生命周期内执行一次
        // onStart (相当于 onResume)
        println("MainScaffold: Composed. Starting expensive resource...")
        isTimerActive = true // 页面可见,启动计时器

        // 你必须返回一个 onDispose 闭包,它会在页面退出时调用
        onDispose {
            // onStop (相当于 onPause)
            println("MainScaffold: Disposed. Releasing expensive resource...")
            isTimerActive = false // 页面不可见,停止计时器
            // 这里还可以做其他清理工作,比如取消网络请求、解除广播监听等
        }
    }

执行逻辑:

  1. 进入Main页时,DisposableEffect执行,打印日志并将 isTimerActive设为 trueLaunchedEffect检测到 isTimerActive变为 true,启动计时协程。
  2. 跳转到 LoginMainScaffold从 Composable 树中被​移除​DisposableEffectonDispose块被调用,打印日志并将 isTimerActive设为 falseLaunchedEffect的协程因为其 key(isTimerActive) 变化或 Composable 退出,会​自动取消​
  3. 返回到main页,MainScaffold被重新组合到 Composable 树中。整个过程从第 1 步开始​重复​

总结一下就是:

概念 Activity 生命周期 Compose 方式(等效实现)
​页面创建/进入​ onCreate()-> onStart()-> onResume() DisposableEffect { ...; onDispose{} }内部代码 + LaunchedEffect
​页面退出/不可见​ onPause()-> onStop() DisposableEffectonDispose
​页面被系统销毁​ onDestroy() onDispose块(但 Compose 导航默认保持后台栈,通常不会销毁)

那么我想知道,这些页面的viewmodel又是如何使用的?

1.3 compose中使用viewmodel,他的生命周期如何,作用域如何

首先要理解一个关键点:​​导航库将每个目的地(composable)与一个 NavBackStackEntry关联起来​ ​。这个 NavBackStackEntry就是 ViewModel 的​​作用域单位​​。

  • ​默认情况(不共享)​ ​:当你导航到一个新页面时,会创建一个新的 NavBackStackEntry。在此作用域内获取的 ViewModel 是​​全新的、独立的​ ​,与其他页面的 ViewModel ​​不共享​​。

  • ​返回时​ ​:当你从新页面返回,该页面对应的 NavBackStackEntry及其 ViewModel 会被保留在后台栈中(直到被弹出)。因此,返回时页面状态(如输入框文本、列表滚动位置)得以保留,正是得益于 ViewModel 的存活。

  • ​永久离开时​ ​:当你通过 popBackStacknavigate(...) { popUpTo(...) }将页面从后台栈中​​永久移除​ ​时,其对应的 NavBackStackEntry和 ViewModel 才会被​​清除​​。

1.3.1 使用 Hilt

如果你使用的是Hilt,那么就很简单了。使用 hiltViewModel()方法,它会自动找到当前作用域(即当前页面的 NavBackStackEntry)的 ViewModel。

kt 复制代码
// LoginViewModel.kt
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel // 关键注解
class LoginViewModel @Inject constructor(
    // 你可以在这里注入其他依赖,如 Repository
    private val userRepository: UserRepository
) : ViewModel() {
    // ViewModel 的代码...
    val uiState: MutableStateFlow<LoginUiState> = ...
    fun login() { ... }
}
kt 复制代码
// LoginScreen.kt
import androidx.hilt.navigation.compose.hiltViewModel // 关键导入

@Composable
fun LoginCompose(
    navController: NavController,
    // 如此声明,ViewModel 的作用域即是当前 composable 目的地
    viewModel: LoginViewModel = hiltViewModel()
) {
    // 使用 viewModel 中的状态和逻辑
    val uiState by viewModel.uiState.collectAsState()

    Button(onClick = { viewModel.login() }) {
        Text("Login")
    }

    if (uiState.isSuccess) {
        LaunchedEffect(Unit) {
            navController.popBackStack() // 登录成功后返回
        }
    }
}

1.3.2:不使用 Hilt(手动管理)

添加依赖

kotlin 复制代码
dependencies {
    def lifecycle_version = "2.6.0" // 请使用最新稳定版
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
}
kt 复制代码
// LoginViewModel.kt
import androidx.lifecycle.ViewModel

class LoginViewModel(
    private val userRepository: UserRepository // 依赖
) : ViewModel() {
    // ... 同样的逻辑
}
kt 复制代码
// LoginScreen.kt
import androidx.lifecycle.viewmodel.compose.viewModel // 关键导入

@Composable
fun LoginCompose(
    navController: NavController
) {
    // 这是最简洁的方式!
    // viewModel() 函数会自动为你处理好一切
    val loginViewModel: LoginViewModel = viewModel()

    val uiState by loginViewModel.uiState.collectAsState()

    // 使用 ViewModel
    if (uiState.isSuccess) {
        LaunchedEffect(Unit) {
            navController.popBackStack()
        }
    }
    // ... 其余 UI 代码
}

好的,这篇文章就介绍到这里~~

相关推荐
Lei活在当下12 小时前
【业务场景架构实战】5. 使用 Flow 模式传递状态过程中的思考点
android·架构·android jetpack
前行的小黑炭14 小时前
Android 关于状态栏的内容:开启沉浸式页面内容被状态栏遮盖;状态栏暗亮色设置;
android·kotlin·app
用户0918 小时前
Flutter构建速度深度优化指南
android·flutter·ios
PenguinLetsGo18 小时前
关于「幽灵调用」一事第三弹:完结?
android
namehu21 小时前
搞定 iOS App 测试包分发,也就这么简单!😎
前端·ios·app
雨白1 天前
Android 多线程:理解 Handler 与 Looper 机制
android
sweetying1 天前
30了,人生按部就班
android·程序员
用户2018792831671 天前
Binder驱动缓冲区的工作机制答疑
android
真夜1 天前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app