首页 > 其他分享 >Jetpack Compose学习(13)——Compose生命周期及副作用函数

Jetpack Compose学习(13)——Compose生命周期及副作用函数

时间:2024-08-15 14:29:05浏览次数:14  
标签:13 Compose 协程 val Jetpack Composable fun 执行

原文: Jetpack Compose学习(13)——Compose生命周期及副作用函数-Stars-One的杂货小窝

此文建议需要了解kotlin的lambda表达式使用和协程基础使用,不然可能会有些阅读困难

本篇算是参考他人文章,按照自己理解重新总结了下吧,偏理论

生命周期

Composable 组件都是函数,Composable 函数执行会得到一棵视图树,每一个 Composable 组件对应视图树上的一个节点。

Composable 的生命周期定义如下:

  • onActive(添加到视图树) Composable 首次被执行,即在视图树上创建对应的节点。
  • onUpdate(重组) Composable 跟随重组不断执行,更新视图树上对应的节点。
  • onDispose(从视图树移除) Composable 不再被执行,对应节点从视图树上移除

对于 Compose 编写 UI 来说,页面的变化,是依靠状态的变化,Composable 进行重组,渲染出不同的页面。
当页面可见时,对应的节点被添加到视图树, 当页面不可见时,对应的节点从视图树移除

副作用函数

Composable 重组过程中可能反复执行,并且中间环节有可能被打断,导致与我们预期次数不符

比如说:

在Composable我们有个弹出toast操作,本质上我们是希望它执行一次,但发生重组后,可能会有多次重复执行

类似这样,在 Composable 执行过程中,凡是会影响外界的操作,都属于副作用。

这个时候,如何保证我们想要的预期执行一次?这个时候就得使用副作用函数来解决此问题,即是下面的内容

SideEffect

SideEffect 在每次成功重组的时候都会执行(仅在重组成功才会执行)

Composable 在重组过程中会反复执行,但是重组不一定每次都会成功,有的可能会被中断,中途失败。

特点:

  1. 重组成功才会执行。
  2. 有可能会执行多次。

所以,SideEffect函数不能用来执行耗时操作,或者只要求执行一次的操作。

@Composable
fun HomePage() {
	SideEffect{
		//一些操作
	}	
}

DisposableEffect(预处理)

DisposableEffect 可以感知 Composable 的 onActiveonDispose, 允许使用该函数完成一些预处理和收尾工作。

DisposableEffect(vararg keys: Any?) {
	    // register(callback)
	    onDispose {
	        // unregister(callback)
	    }
	}

这里首先参数 keys 表示,keys可以是任意对象,当 keys 变化时, DisposableEffect 会重新执行,如果在整个生命周期内,只想执行一次,则可以传入 Unit

onDispose 代码块则会在 Composable 进入 onDispose生命周期 时执行。

@Composable
fun HomePage() {
	DisposableEffect(Unit) {
	    // register(callback)
	    onDispose {
	        // unregister(callback)
	    }
	}
}

LaunchedEffect(比较常用)

LaunchedEffect 用于在 Composable 中启动协程,当 Composable 进入 onAtive 时,LaunchedEffect 会自动启动协程,执行 block 中的代码。

当 Composable 进入 onDispose 时,协程会自动取消。

同样的,也是有个key参数,变化就会重新执行

@Composable
fun HomePage() {
	//这里我传了Unit(也是个对象)
	LaunchedEffect(Unit) {
	    // do Something async
	}
}

rememberCoroutineScope

LaunchedEffect只能在@Composable函数作用域使用

如果想要在onclick等事件进行协程等操作,可以使用此rememberCoroutineScope函数来获取到协程的scope,如下代码

@Composable
fun HomePage() {
	val scope = rememberCoroutineScope()
	Button(onClick = {
		scope.launch{
			//相关耗时操作
		}
	}){
		Text("点击操作")
	}
}

rememberUpdatedState

在不中断协程的情况下,保证始终能够获取到最新的值

总结使用情景:一般情况下,如果我们的@Composable组件需要接受外部数值,且外部数值在父级别@Composable会有数值的更新操作,且我们还使用了副作用函数(不管是在子还是父)

那么我们这个@Composable组件最好使用rememberUpdatedState来获取最新数值

如下面的FinalChoose组件,是接受一个外部数值,但这个数值又有可能在外部被更改(ChooseHero里的sheshou变量),而且FinalChoose中有副作用函数

@Composable
fun ChooseHero() {
    var sheshou by remember {
        mutableStateOf("狄仁杰")
    }

    Column {
        Text(text = "预选英雄: $sheshou")
		//点击按钮会修改 sheshou 这个变量
        Button(onClick = {
            sheshou = "马可波罗"
        }) {
            Text(text = "改选:马可波罗")
        }
		//这里传入了一个sheshou变量(但里面有个倒计时)
        FinalChoose(sheshou)
    }
}


@Composable
fun FinalChoose(hero: String) {
    var tips by remember {
        mutableStateOf("游戏倒计时:10s")
    }

	//如果不用这个,此组件的currentHero只会一致等于hero参数
    val currentHero by rememberUpdatedState(newValue = hero)
	// val currentHero = hero

    LaunchedEffect(key1 = Unit) {
		repeat(9) {
            "游戏倒计时:${10-it}s"
            delay(1000)
        }
        tips = "最终选择的英雄是:$currentHero"
    }
    Text(text = tips)
}

更详细说明可以参考此文Compose:长期副作用 + 智能重组 = 若智?聊聊rememberUpdateState - 掘金

derivedStateOf

将其他state派生为新的state,使用此函数可确保仅当计算中使用的状态之一发生变化时才会进行计算,如下代码:

@Composable
fun HomePage() {

    val time by remember { mutableIntStateOf(10) }

	//只要当time变更了,这个newTip数据才会变更
    val newTip by remember { derivedStateOf{"剩余时间:$time"} }
	
}

produceState

定义了一个状态 State, 然后启动了一个协程,在协程中去更新 State 的值。参数 key 发生变化时,协程会取消,然后重新启动,生成新的 State。

将任意数据源转为state对象(实际我们的操作就是在协程作用域里进行的),如下面代码

//这里弄的简单些,返回个字符串
val newData by produceState("无数据"){
	//当前已经在协程作用域里,可以按照需求启动新协程
	this.launch {
		
	}
	
	//异步等操作
	
	//模拟请求api数据
	val result = "数据: {code:200}"
	delay(500)
	//设置数据
	value = result
	
	awaitDispose {
		//一些收尾工作,释放资源之类会取消观察
	}
}

或者整成个方法来进行调用,如API请求之类:

@Composable
fun GetApi(url: String): Recomposer.State<Result<Data>> {
	//这里produceState传的url就相当于是key
    return produceState(initialValue = "无数据", url) {
		//模拟请求api数据
		val result = "数据: {code:200}"
		delay(500)
		//设置数据
		value = result
		
        awaitDispose {
        	//一些收尾工作
        }
    }
}

进阶理解 - 稳定和不稳定

当实体类里存在var关键字的成员变量,编译器宁愿牺牲性能进行一次重组,也不会展示错的UI

用 var 声明 Hero 类的属性时,Hero 类被 Compose 编译器认为是不稳定类型:

  • 即有可能,我们传入的参数引用没有变化,但是属性被修改过了,而 UI 又确实需要显示修改后的最新值。
  • 而当用 val 声明属性了,Compose 编译器认为该对象,只要对象引用不要变,那么这个对象就不会发生变化,自然 UI 也就不会发生变化,所以就跳过了这次重组。

常用的基本数据类型以及函数类型(lambda)都可以称得上是稳定类型,它们都不可变

反之,如果状态是可变的,那么比较 equals 结果将不再可信。在遇到不稳定类型时,Compose 的抉择是宁愿牺牲一些性能,也总好过显示错误的 UI。

如下面的例子:

//注意参数里有个var
data class Hero(var name: String,val age:Int=18)

val shangDan = Hero("吕布")

@Composable
fun StableTest() {
    var greeting by remember {
        mutableStateOf("hello, 鲁班")
    }

    Column {
        
        Text(text = greeting)
        Button(onClick = {
            greeting = "hello, 鲁班大师"
        }) {
            Text(text = "搞错了,是鲁班大师")
        }
		//这里实际上,对象是没有变的
        ShangDan(shangDan)
    }
}

@Composable
fun ShangDan(hero: Hero) {
	println("执行")
    Text(text = hero.name)
}

上面的shandan对象是固定的,但是测试,点击button后,明明没有更新shandan对象的数值,但发现ShangDan这个组件还是进行了重组操作!

这个就是因为Hero类中的name为var,被编译器视为了不稳定,所以牺牲了性能,进行了重组(避免展示了错误的UI)

上面情景中使用var会导致性能会有些损耗,但我们又可能因为业务需求,不能将实体类的成员变量都定为val关键字,这个时候还有什么办法?

当然有,那就是使用@Stable注解,只要对象引用不变,则不会触发重组,如下代码:

@Stable
data class Hero(var name: String,val age:Int=18)

参考

标签:13,Compose,协程,val,Jetpack,Composable,fun,执行
From: https://www.cnblogs.com/stars-one/p/18360831

相关文章

  • 【MATLAB源码-第137期】基于matlab的NOMA系统和OFDMA系统对比仿真。
    操作环境:MATLAB2022a1、算法描述NOMA(非正交多址)和OFDMA(正交频分多址)是两种流行的无线通信技术,广泛应用于现代移动通信系统中,如4G、5G和未来的6G网络。它们的设计目标是提高频谱效率、支持更多的用户、实现更高的数据传输速率,并满足不断增长的移动数据通信需求。在本文中,我......
  • 【MATLAB源码-第138期】基于matlab的D2D蜂窝通信仿真,对比启发式算法,最优化算法和随机
    操作环境:MATLAB2022a1、算法描述D2D蜂窝通信介绍D2D蜂窝通信允许在同一蜂窝网络覆盖区域内的终端设备直接相互通信,而无需数据经过基站或网络核心部分转发。这种通信模式具有几个显著优点:首先,它可以显著降低通信延迟,因为数据传输路径更短;其次,由于减少了基站的中转,可以提高......
  • docker-compose 一键部署多个微服务
    如果部署微服务项目的话,多个服务需要启动,如果用dockerrun一个一个启动效率实在是太慢了可以用docker-compose一键启动多个服务第一步:服务打成jar之后每个服务一个文件夹并把Dockerfile加进去第二步:编写docker-compose.yml文件version:'3.8'services:sdss-bas......
  • VL13 优先编码器电路
     `timescale1ns/1nsmoduleencoder_0(  input   [8:0]    I_n ,    outputreg[3:0]    Y_n );always@(*)begin  casex(I_n)  9'b1_1111_1111:Y_n=4'b1111;  9'b0_xxxx_xxxx:Y_n=4'b0110;  9'b1_0xxx......
  • 欧阳坚持每周一篇高质量文章,半年后收入1380.27元
    前言大家好,我是欧阳,到目前为止欧阳已经坚持连续高质量周更文章7个多月了。在第6个月时就想写一篇半年总结,但是因为拖延症直到现在才写这篇半年复盘文章。我的成果先来说一下连续周更半年取得的成果,分别是收入1380.27元、电子书一本、微信技术群418人、微信好友459人、文章38篇......
  • Superset Docker-Compose部署
    bi系统是一类旨在帮助企业和组织分析、可视化和理解其业务数据的软件工具之前了解过商业的阿里quickbi,腾讯bi,开源的话用superset,据了解新老公司不少会调研supersethttps://github.com/apache/superset/releases/tag/4.0.2这里我使用了最新的包,经过测试发现一年前的镜像更新到......
  • CSC5113 三节锂电池保护芯片
    CSC5113是专用于3节锂电池保护芯片,通过对每节锂电池的充电电压、放电电压、充电电流和放电电流进行高进度检测,实现对电池的过充电、过放电、充电过电流和放电过电流以及短路电流的保护功能。CSC5113采用SOP8封装。1、CSC5113过充电保护当IC检测到任意一节电池电压超......
  • MT2513B 无外围5W电源芯片
    MT2513是一款高度集成自供电原边反馈最大6W电源芯片。MT2513B内置功率三极管,采用脉冲频率调制(PFM)建立非连续导电模式(DCM)的反激式电源,外围设计极简化。MT2513具有可变原边峰值电流,通过最大原边峰值电流和变压器原副边匝比来设置输出恒流点,通过外置FB电阻设置输出恒压点。MT25......
  • CF1383E Strange Operation
    小清新Counting题,想到转化成序列计数后就不难了考虑将一个0/1串等价转化为一个刻画相邻两个\(1\)之间有几个\(0\)的序列比如样例中的\(00101100011100\)就可以转化为\(\{2,1,0,3,0,0,2\}\)这个序列,显然转化后的序列和原来的0/1串等价考虑此时一次操作相当于将序......
  • Anrdoir 13 关于设置静态IP后,突然断电,在上电开机卡动画
    bug描述:设置静态IP成功后,机器突然断电,然后在上电开机,发现机器一直卡在开机动画,无法成功进入桌面第一时间抓取日志分析,Log如下:08-1311:26:42.45528032803IEthernetServiceImpl:StartingEthernetservice08-1311:26:42.45728032924DConnectivityService:......