当前位置: 首页 > news >正文

Android Compose是如何使用什么架构,多个Activity?还是Fragment?compose的ui又是如何卸载和挂载的呢?

目录:

  1. 前言
  2. 定义导航图(Navigation Graph)和路由(Route),用代码声明页面跳转逻辑
    2.1 composeable页面卸载的时候,数据如何保存?
    2.2 composeable挂载页面的时候,是覆盖,还是替换呢?

一、前言

最近在学习compose,先是找文章来学习,然后看github上面的源码来学习,最后再找视频来学习,然后再上github的项目学习,接着在根据项目的源码,自己删除再重新写一遍。一波三折。

在学习的过程,我就有一个疑问,只有一个Activity,也没有看到Fragment呢,也没有看到更多的Fragment,我好奇,compose的生命周期是如何的,他的页面又是如何加载的,没看到onstart方法,或者页面停止的时候onstop方法。

接下来,我们来解答一下这些疑惑。


二、通过定义导航图(Navigation Graph)和路由(Route),用代码声明页面跳转逻辑

在 Jetpack Compose 中,不再推荐使用多个 Activity 或 Fragment 来实现页面跳转,而是采用 单一 Activity + 多 Composable 组件 + 导航库 的架构。这是 Compose 的核心理念:通过声明式 UI 和状态驱动的导航来简化开发。

  1. 单一 Activity:整个应用通常只有一个 Activity(MainActivity),所有页面切换通过 Composable 组件 的替换完成。
  2. 无 Fragment:Compose 完全抛弃 Fragment,直接通过 NavController 和 NavHost 管理页面栈。
  3. 声明式导航:通过定义导航图(Navigation Graph)和路由(Route),用代码声明页面跳转逻辑。

那么,为什么要使用这种方式呢?

  1. 传统多 Activity/Fragment 的痛点:
    1.1 每个 Activity 都有自己的生命周期,跨页面数据传递需要序列化(Parcelable/Serializable),容易导致内存泄漏或状态不一致
    1.2 每次跳转新 Activity 都会触发冷启动,导致界面卡顿。
  2. Compose 新架构的核心优势
    2.1 Composable 函数根据状态自动重组,无需手动更新视图。页面切换本质是 Composable 组件的重组,无需启动新 Activity 或 Fragment。
    2.2 通过 NavController 管理全局导航栈,跳转逻辑集中且类型安全。

接下来,我们就看看导航是如何使用的(需要有一定基础~~)。


2.1 代码例子

1、导航代码

fun AppScaffold() {
    val navCtrl = rememberNavController() // 获取导航控制器
    val navBackStackEntry by navCtrl.currentBackStackEntryAsState() // 监听导航栈变化 从而确定当前选中的底部导航项。这样可以确保底部导航栏的状态与当前显示的路由一致。
    val currentDestination = navBackStackEntry?.destination // 当前路由目标
    //接下来我要构建底部一个导航栏,所以需要使用到Scaffold
    Scaffold(
        modifier = Modifier.
        statusBarsPadding().
        navigationBarsPadding(),
        //底部的导航栏内容,会放到NavHost里面。
        bottomBar = {
            when (currentDestination?.route) {
                 RouteName.HOME-> {BottomNavBarView(navCtrl = navCtrl)}
                RouteName.CATEGORY-> {BottomNavBarView(navCtrl = navCtrl)}
                RouteName.COLLECTION-> {BottomNavBarView(navCtrl = navCtrl)}
                RouteName.PROFILE-> {BottomNavBarView(navCtrl = navCtrl)}
            }
        },
        content = {
            // 定义需要保持状态的页面索引(防止页面切换时重置)
            var homeIndex = remember { 0 }
            var categoryIndex = remember { 0 }

            // 导航宿主容器
            NavHost(
                modifier = Modifier.background(MaterialTheme.colors.background),
                navController = navCtrl,
                startDestination = RouteName.HOME // 初始路由
            ) {
                // 首页路由
                composable(route = RouteName.HOME) {
                    HomePage(navCtrl)
                }
                // 分类页路由
                composable(route = RouteName.CATEGORY) {
                    CategoryPage(navCtrl)
                }
                // 收藏页路由
                composable(route = RouteName.COLLECTION) {
                    CollectPage(navCtrl)
                }

                // 个人中心页路由
                composable(route = RouteName.PROFILE) {
                    ProfilePage(navCtrl)
                }
            }
        }
    )
}

2、点击切换

@Composable
fun BottomNavBarView(navCtrl: NavHostController) {
    //导航的标题,图片,路径
    val bottomNavList = listOf(
        BottomNavRoute.Home,
        BottomNavRoute.Category,
        BottomNavRoute.Collection,
        BottomNavRoute.Profile
    )
    //底部导航栏ui。
    BottomNavigation {
        val navBackStackEntry by navCtrl.currentBackStackEntryAsState() // 监听导航栈变化 从而确定当前选中的底部导航项。这样可以确保底部导航栏的状态与当前显示的路由一致。
        val currentDestination = navBackStackEntry?.destination// 当前路由目标
        bottomNavList.forEach { screen ->
            BottomNavigationItem(
                modifier = Modifier.background(AppTheme.colors.themeUi),
                icon = {
                    Icon(
                        imageVector = screen.icon,
                        contentDescription = null
                    )
                },
                label = { Text(text = stringResource(screen.stringId)) },
                //判断当前页面是否在某个导航图中
                selected = currentDestination?.hierarchy?.any { it.route == screen.routeName } == true,//那个被选择,那个就高亮。
                onClick = {
                    //如果点击相同的,那么就不管
                    if (currentDestination?.route == screen.routeName) {
                        return@BottomNavigationItem
                    }

                    //跳转
                    navCtrl.navigate(screen.routeName)

                }
            )
        }
    }
}

3、HomePage :一个背景,其他页面也都是,用来测试,就写的简单一些~

@Composable
fun HomePage(
    navCtrl: NavHostController,
) {
   Box(
       modifier =Modifier.fillMaxSize().background(Color.Red)
   )
}

在这里插入图片描述
这里,还有一个疑问,那么他重组,其他页面内容又是如何保存的呢?还是说直接就销毁了呢?比如从首页切换到分类,那么是覆盖,还是什么呢?


2.2 挂载和卸载

Composable 的挂载/卸载:当 Composable 进入/退出组合(Composition)时,可以视为类似 onStart/onStop 的时机。可以通过

1、挂载(进入组合)

当 Composable ​首次被调用 或 ​在重组中被重新需要 时,会挂载到组合中。

初始化状态:通过 remember 或 mutableStateOf 创建的状态会被初始化。
​执行副作用:如 LaunchedEffect、DisposableEffect 的首次运行。
​构建 UI 树:将 Composable 添加到 Compose 的 UI 树中。

示例


@Composable
fun MyComponent() {
    // 挂载时初始化状态
    val count = remember { mutableStateOf(0) }
    
    // 挂载时启动副作用
    LaunchedEffect(Unit) {
        delay(1000)
        println("Component mounted!")
    }
    
    Text("Count: ${count.value}")
}
2、卸载(退出组合)

当 Composable ​在重组过程中不再被调用 时,会从组合中卸载。

常见场景:

条件语句跳过:因 if/when 条件不满足而不再执行。
​导航离开页面:通过 NavController 跳转到其他页面。
​父组件卸载:父 Composable 被卸载导致子组件连带卸载。

行为:
​清理副作用:触发 DisposableEffect 的 onDispose 或取消 LaunchedEffect 的协程。
​释放资源:如取消网络请求、关闭数据库连接。
​从 UI 树移除:Composable 的 UI 节点被销毁。

示例


@Composable
fun ParentComponent(showChild: Boolean) {
    if (showChild) {
        ChildComponent() // 挂载
    }
    // 当 showChild 变为 false 时,ChildComponent 卸载,因为条件不满足了,这里要好好理解。因为ChildComponent方法不被调用了,就会卸载掉。
}

@Composable
fun ChildComponent() {
    // 副作用:挂载时启动,卸载时清理
    DisposableEffect(Unit) {
        val resource = allocateResource()
        onDispose {
            releaseResource(resource) // 卸载时执行清理
        }
    }
    
    Text("Child Component")
}

现在我们可以回答一个问题,比如从首页切换到分类,那么是覆盖,还是什么呢?不是覆盖,是重新绘制,消失就会被卸载掉,不会留着,绘制变更的内容。

那么页面数据呢?也会初始化,重新获取数据?看你使用的是什么样的

一、Composable 卸载后的状态

  1. ​状态是否销毁?

    ​普通状态(remember)​:
    使用 remember 保存的状态 ​会被销毁。当 Composable 卸载后,其内部状态会被释放,下次挂载时会重新初始化。

    
    @Composable
    fun Counter() {
        val count = remember { mutableStateOf(0) } // 卸载时销毁
        Button(onClick = { count.value++ }) {
            Text("Count: ${count.value}")
        }
    }
    

    ​可保存状态(rememberSaveable)​:
    使用 rememberSaveable 的状态会在 ​配置变更(如屏幕旋转)时保留,但 ​Composable 完全卸载后依然会被销毁。


  val count = rememberSaveable { mutableStateOf(0) } // 配置变更保留,卸载销毁
  1. ​副作用是否清理?

    LaunchedEffect/DisposableEffect
    卸载时会自动取消协程或触发 onDispose 清理资源。

    
     DisposableEffect(Unit) {
         // 挂载时执行
         onDispose { 
             // 卸载时清理 
         }
     }
    

二、重新挂载时的行为

当 Composable ​再次被调用进入组合 时(例如条件语句重新满足、导航返回页面等),会触发 ​重新挂载,此时:

  1. ​重新执行整个函数体

Composable 函数会从头到尾重新执行,包括:

所有 remember 的初始化
所有副作用的启动(如 LaunchedEffect)
  1. ​状态重置

除非使用 ​状态提升 或 ​持久化存储,否则状态会被重置:

// 示例:重新挂载时状态重置
@Composable
fun DynamicComponent(visible: Boolean) {
    if (visible) {
        val count = remember { mutableStateOf(0) } // 每次挂载都从 0 开始
        Button(onClick = { count.value++ }) {
            Text("Count: ${count.value}")
        }
    }
}

那么怎么办呢?使用viewmodel,大家应该都知道viewmodel的生命周期。其生命周期与 Activity/Fragment 或导航图绑定。通过 ViewModel 保存状态。


@Composable
fun PersistentCounter(viewModel: CounterViewModel = viewModel()) {
    Button(onClick = { viewModel.increment() }) {
        Text("Count: ${viewModel.count.value}")
    }
}

class CounterViewModel : ViewModel() {
    val count = mutableStateOf(0)
    fun increment() { count.value++ }
}

比如说,我们发起一起请求,请求到的数据放到viewModel中,那么重新挂载的时候,直接把数据拿出来就可以,不需要重新执行请求

相关文章:

  • 【day10】智慧导览:学习LBS定位精度标准
  • markdown转docx
  • Java基础关键_020_集合(四)
  • 电网电压暂态扰动机理与工业设备抗失压防护策略研究
  • 【SpringBoot】深入剖析 Spring Boot 自动装配原理(附源码与实战)
  • 内存检测工具——Qt Creator
  • git使用命令总结
  • python总结(3)
  • QT工程打开、编译、运行流程
  • Netty基础—2.网络编程基础三
  • Dify平台训练个人文档助手
  • 2024年群智能SCI1区TOP:混沌可行性恢复粒子群算法CEPSO,深度解析+性能实测
  • 20250212:linux系统DNS解析卡顿5秒的bug
  • 关于mybatis查询时,时间字段的映射问题
  • Java 集合框架大师课:集合框架源码解剖室(五)
  • 内网安全防护新思路 —— HFish + ELK 与 T-Pot 全面蜜罐系统比较分析
  • Ollama杂记
  • bin/python: bad interpreter: No such file or directory
  • Python:正则表达式
  • Java数据结构第二十二期:Map与Set的高效应用之道(一)
  • 王毅谈中拉论坛第四届部长级会议重要共识
  • 反犹、资金与抗议:特朗普的施压如何撕裂美国大学?|907编辑部
  • 6连败后再战萨巴伦卡,郑钦文期待打出更稳定发挥
  • 这一次,又被南昌“秀”到了
  • 全球前瞻|特朗普访问中东三国,印巴军方将于12日再次对话
  • 当创业热土遇上年轻气息,上海南汇新城发展如何再发力?