Android---Jetpack Compose学习007

Compose 附带效应

a. 纯函数

纯函数指的是函数与外界交换数据只能通过函数参数和函数返回值来进行,纯函数的运行不会对外界环境产生任何的影响。比如下面这个函数:

fun Add(a : Int, b : Int) : Int {return a + b
}

“副作用”(side effect),指的是如果一个操作、函数或表达式在其内部与外界进行了互动,产生运算以外的其他结果,则该操作或表达式具有副作用。

最典型的情况,就是修改了外部环境的变量值。例如如下代码:Add() 函数执行它需要一个外部变量 a,先进行 ++ 操作,然 a + b 返回。只要这个函数一执行,外部变量 a 就会改变。而对于这个 a 所产生的改变,这个就叫做副作用。

var a
fun Add(b : Int) : Unit{a++return a + b
}

因此,组合函数也是一个函数,那么它也分为有副作用的和没副作用的。而组合函数的副作用和其它函数还有一些差异。

组合函数的特点

a. 执行顺序不定;b. 可以并行运行;c. 可能会非常频繁地运行

处理副作用

虽然我们不希望函数执行中出现副作用,但现实情况是有一些逻辑只能作为副作用来处理。例如一些 IO 操作计时日志埋点等,这些都是会对外界或收到外界影响的逻辑,不能无限制的反复执行。所以 Compose 需要能够合理地处理一些副作用。

\bullet 副作用的执行时机是明确的,例如在 Recomposition 时等。

\bullet 副作用的执行次数是可控的,不应该随着函数反复执行。

\bullet 副作用不会造成泄漏,例如对于注册要提供适当的时机取消注册。

组合函数的副作用

组合函数是主要是用来做 UI 声明的、描述的,只要你在可组合函数内做了与 UI 描述不相关的操作,这一类操作其实都属于副作用。

在 Compose 中可组合函数内部理应只做视图相关的事情,而不应该做函数返回之外的事情,如访问文件等,如果有,那这就叫做附带效应,以下操作全部都是危险的附带效应:

\bullet 写入共享对象的属性;

\bullet 更新 ViewModel 中的可观察项。

\bullet 更新共享偏好设置。

可组合函数应该是无副作用的,但是如果我们要在 Compose 里面使用可组合函数而且会产生附带效应,这时就需要使用 EffectAPI,以便以可预测的方式执行那些副作用。一个 effect,就是一个可组合函数,这个可组合函数不生成 UI,而是在组合完成时产生副作用。

组合函数的生命周期

这些 Effect API 是与我们组合函数的生命周期相关联的。可组合项的生命周期比 activity 和 fragment 的生命周期更简单,一般是进入组合、执行0次或者多次重组、退出组合。

\bullet Enter:挂载到树上,首次显示。

\bullet Composition:重组刷新 UI。

\bullet Leave:从树上移除,不再显示。

组合函数中没有自带的生命周期函数,想要监听其生命周期,需要使用 Effect(附带效应)API :

\bullet LaunchedEffect:第一次调用 Compose 函数的时候调用。

\bullet DisposableEffect:内部有一个 onDispose() 函数,当页面退出时调用。

\bullet SideEffect:compose 函数每次执行都会调用该方法。

LaunchedEffect

如果在可组合函数中进行耗时操作(副作用往往都是耗时操作,例如网络请求、I/O等),就需要将耗时操作放入协程中执行,而协程需要在协程作用域中创建,因此 Compose 提供了 LaunchedEffect 用于创建协程。

\bullet 当 LaunchedEffect 进入组件树时,会启动一个协程,并将 block 放入该协程中执行。

\bullet 当组合函数从视图树上 detach 时,协程还未被执行完毕,该协程也会被取消执行

\bullet 当 LaunchedEffect 在重组时其 key 不变,那 LaunchedEffect 不会被重新启动执行 block。

\bullet 当 LaunchedEffect 在重组时其 key 发生了变化,则 LaunchedEffect 会执行 cancel 后,再重新启动一个新协程执行 block

示例:LaunchedEffect 在初次进入组件树时,就会启动一个协程,调用 block 块执行

1. LaunchedEffectSample.kt

@Composable
fun ScaffoldSample(state : MutableState<Boolean>,scaffoldState : ScaffoldState = rememberScaffoldState()
){// TODO 当我启动这个应用时,组件一开始加载进来,LaunchedEffect() 就会启动一个协程,执行它的 block 块//TODO 当 key = state.value 发生改变时(点击按钮时改变),就会启动协程LaunchedEffect(state.value){// 开启一个弹窗,TODO 是一个 block 块scaffoldState.snackbarHostState.showSnackbar(// 弹窗内容message = "Error message",actionLabel = "Retry message")}// TODO 脚手架Scaffold(scaffoldState = scaffoldState,// 顶部标题栏区域topBar = {TopAppBar(title = { Text(text = "脚手架示例!")})},// 屏幕内容区域content = {Box(modifier = Modifier.fillMaxSize(), // 填充父容器contentAlignment = Alignment.Center // 居中){Button(onClick = {//TODO 点击按钮时,弹窗,改变 state 的值。一个动画效果,为耗时操作,即附带效应state.value = !state.value}) {Text(text = "Click it!")}}})
}@Composable
fun LaunchedEffectSample(){val state = remember { mutableStateOf(false) }ScaffoldSample(state)
}

2. MainActivity.kt

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {LaunchedEffectSample()}}}
}

上面的示例中,当我们启动 App 时就会让 LaunchedEffect 进入组件树时,启动一个协程,并将 block 放入该协程中执行。可以做如下改变,让进入 App 时不执行 block 块。修改 LaunchedEffect 代码如下:

    if(state.value){LaunchedEffect(scaffoldState.snackbarHostState){// 开启一个弹窗,TODO 是一个 block 块scaffoldState.snackbarHostState.showSnackbar(// 弹窗内容message = "Error message",actionLabel = "Retry message")}}

rememberCoroutineScope

由于 LauncedEffect 本身就是个可组合函数,因此只能在其他可组合函数中使用。想要在可组合项外启动协程,且需要对这个协程存在作用域限制,以便协程在退出组合后自动取消,可以使用 rememberCoroutineScope。

此外,如果需要手动控制一个或多个协程的生命周期,请使用 rememberCoroutineScope。拿到协程的作用域。

示例:

1. RememberCoroutineScopeSample.kt

@Composable
fun ScaffoldSample(){val scaffoldState = rememberScaffoldState()// TODO 拿到协程作用域,启动多个协程val scope = rememberCoroutineScope()Scaffold(scaffoldState = scaffoldState,//TODO 左侧抽屉栏,点击了菜单按钮时,弹出drawerContent = {Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center) {Text(text = "抽屉组件中的内容")}                },// 顶部标题栏区域topBar = {TopAppBar(// 左上角的菜单栏按钮,点击后左侧弹窗navigationIcon = {IconButton(onClick = {// TODO 点击菜单按钮时,弹出左侧抽屉栏// TODO 1 启动一个协程scope.launch {// 以动画的形式打开这个抽屉scaffoldState.drawerState.open()}}) {// 菜单按钮Icon(imageVector = Icons.Filled.Menu, contentDescription = null)}},title = { Text(text = "脚手架示例!")})},// 屏幕内容区域content = {Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center) {Text(text = "屏幕内容区域")}},// TODO 右下角的悬浮按钮floatingActionButton = {ExtendedFloatingActionButton(text = { Text(text = "悬浮按钮") },onClick = {// TODO 2 启动一个协程scope.launch {// 弹窗scaffoldState.snackbarHostState.showSnackbar("点击了悬浮按钮")}})})
}

2. MainActivity.kt

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {//LaunchedEffectSample()ScaffoldSample()}}}
}

在上面代码中,我们通过  val scope = rememberCoroutineScope() 拿到协程作用域,以此来控制多个协程生命周期

rememberUpdatedState

如果 key 值有更新,那么 LaunchedEffect 在重组时就会被重新启动。但是有时候需要在 LaunchedEffect 中使用最新的参数值,但是又不想重新启动 LaunchedEffect,此时就需要用到 rememberUpdatedState。

rememberUpdatedState 的作用是给某个参数创建一个引用,来跟踪这些参数,并保证其值被使用时是最新值,参数被改变时不重启 effect。

示例:RememberUpdatedStateSample.kt

@Composable
fun LandingScreen(onTimeOut : () -> Unit){// TODO onTimeOut() 转换成一个状态了val currentOnTimeout by rememberUpdatedState(newValue = onTimeOut)//TODO key1 = Unit 表示这个 key 值不会变LaunchedEffect(key1 = Unit){Log.d("HL", "LaunchedEffect")repeat(10){delay(1000)Log.d("HL", "delay ${it + 1}s")}////onTimeOut()currentOnTimeout()}
}@Composable
fun RememberUpdatedStateSample(){val onTimeOut1 : () -> Unit = { Log.d("HL", "landing timeout 1") }val onTImeOut2 : () -> Unit = { Log.d("HL", "landing timeout 2") }// 创建一个 state, 默认值为 onTimeOut1val changeOnTimeOutState = remember { mutableStateOf(onTimeOut1) }Column {Button(onClick = {// TODO 点击按钮时,改变 changeOnTimeOutState 的值if(changeOnTimeOutState.value == onTimeOut1){changeOnTimeOutState.value = onTImeOut2}else{changeOnTimeOutState.value = onTimeOut1}}) {Text(text = "choose onTimeOut ${if(changeOnTimeOutState.value == onTimeOut1) 1 else 2}")}//TODO changeOnTimeOutState.value == OnTimeOut1 / OnTimeOut2LandingScreen(changeOnTimeOutState.value)}
}

DisposableEffect

DisposableEffect 也是一个可组合函数,当 DisposableEffect 在其 key 值变化或者组合函数离开组件树时,会取消之前启动的协程,并会在取消协程前调用其回收方法进行资源回收相关的操作,可以对一些资源等进行清理。

示例:当开关按钮打开时,拦截返回按钮。

DisposableEffectSample.kt

// 对返回进行一个拦截
@Composable
fun BackHandler(backDispatcher : OnBackPressedDispatcher,onBack : () -> Unit
){// onBack 包装成一个状态, TODO 以便可以随时替换为其它的函数val currentOnBack by rememberUpdatedState(newValue = onBack)val backCallback = remember {object : OnBackPressedCallback(true){override fun handleOnBackPressed() {//onBack()currentOnBack()}}}DisposableEffect(key1 = backDispatcher){// 开关打开,添加拦截 backCallbackbackDispatcher.addCallback(backCallback)// 执行时机为:BackHandler 从组件树中移除,也就是 switch 开关关掉的时候onDispose {Log.d("HL", "onDispose")// 开关一关,从组件树中移除backCallback.remove()}}
}@Composable
fun DisposableEffectSample(backDispatcher : OnBackPressedDispatcher){// TODO 设置一个状态var addBackCallback by remember { mutableStateOf(false) }Row {// 开关按钮Switch(checked = addBackCallback, // 默认选中或不选中onCheckedChange = {// 当点击开关进行切换的时候,调用这里的代码addBackCallback = !addBackCallback})Text(text = if (addBackCallback) "Add back callback" else "Not add back callback")}if(addBackCallback){ // TODO 打开开关,BackHandler() 执行BackHandler(backDispatcher){Log.d("HL", "onBack")}}
}

MainActivity.kt

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {//LaunchedEffectSample()//ScaffoldSample()//RememberUpdatedStateSample()DisposableEffectSample(onBackPressedDispatcher)}}}
}

SideEffect

SideEffect 是简化版的 DisposableEffect,SideEffect 并未接收任何 key 值,所以,每次重组,就会执行其 block。当不需要 onDispose、不需要参数控制时使用 SideEffect。SideEffect 主要用来与非 Compose 管理的对象共享 Compose 状态

SideEffect 在组合函数被创建并载入视图树后才会被调用

例如,我们的分析库可能允许通过将自定义元数据(在此示例中为“用户属性”)附加到所有后续分析事件,来细分用户群体。如需将当前用户的用户类型传递给你的分析库,请使用 SideEffect 更新其值。

prodeceState

produceState 可以将非 Compose(如 Flow、LiveData 或 RxJava)状态转换为 Compose 状态。它接收一个 lambda 表达式作为函数体,能将这些入参经过一些操作后生成一个 State 类型变量并返回

\bullet produceState 创建了一个协程,但它也可用于观察非挂起的数据源

\bullet 当 produceState 进入 Composition 时,获取数据的任务被启动,当其离开 Composition 时,该任务被取消。

derivedStateOf

如果某个状态是从其它状态对象计算或派生得出的,请使用 derivedStateOf。作为条件的状态我们称为条件状态。当任意一个条件状态更新时,结果状态都会重新计算

snapshotFlow

使用 snapshotFlow 可以将 State 对象转换为 Flow。snapshotFlow 会运行传入的 block,并发出从块中读取的 State 对象的结果。当在 snapshotFlow 块中读取的 State 对象之一发生变化时,如果新值与之前发出的值不相等,Flow 会向其收集器发出新值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696518.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯单片机入门记录】静态数码管

目录 一、数码管概述 &#xff08;1&#xff09;认识数码管 &#xff08;2&#xff09;数码管的工作原理 &#xff08;3&#xff09;LED数码管驱动方式-静态显示 二、数码管电路图 三、静态数码管显示例程 &#xff08;1&#xff09;例程1&#xff1a;数码管显示某一位&a…

PYQT5-自定义事件

from PyQt5.QtCore import QEvent, QObject from PyQt5.QtWidgets import QApplication import sys# 自定义事件类 class CustomEvent(QEvent):# PYQT5 预留给用户自定义事件类型的起点为 QEvent.User1000custom_event_type QEvent.registerEventType()# 也可以这样写# custom…

2024.2.22

P1162 #include<map> #include<vector> #include<iostream> #include<math.h> #include<algorithm> #include<string> using namespace std; const int N 1020; int n; int g[N][N];//标记数组 int a[N][N];//储存数组 int dx[] { -1…

webstorm光标变成方块解决办法_webstorm光标变粗不能换行

webstorms光标变了 键盘上的insert是切换的快捷键&#xff0c;敲insert就可以来回切换了

双通道并行网络,想用哪个网络用哪个,MATLAB代码

本期可谓是宝藏篇&#xff01;学会本期的思想&#xff0c;帮助你分分钟找到创新点&#xff0c;且不与别人重复&#xff01; 本期采用MATLAB代码&#xff0c;实现一种“基于格拉姆角场与并行CNN的故障诊断方法”。该方法的具体实现可以参考文献&#xff1a; [1]李宗源,陈谦,钱…

普中51单片机学习(EEPROM)

EEPROM IIC串行总线的组成及工作原理 I2C总线的数据传送 数据位的有效性规定 I2C总线进行数据传送时&#xff0c;时钟信号为高电平期间&#xff0c;数据线上的数据必须保持稳定&#xff0c;只有在时钟线上的信号为低电平期间&#xff0c;数据线上的高电平或低电平状态才允许…

分享WebGL物体三维建模

界面效果 代码结构 模型素材类似CT (Computed Tomography)&#xff0c;即电子计算机断层扫描&#xff0c;它是利用精确准直的X线束、γ射线、超声波等&#xff0c;与灵敏度极高的探测器一同围绕物体的某一部位作一个接一个的断面扫描。 坐标系统 渲染流程 渲染流程是个将之前准…

Sora:OpenAI引领AI视频新时代

Sora - 探索AI视频模型的无限可能 随着人工智能技术的飞速发展&#xff0c;AI视频模型已成为科技领域的新热点。而在这个浪潮中&#xff0c;OpenAI推出的首个AI视频模型Sora&#xff0c;以其卓越的性能和前瞻性的技术&#xff0c;引领着AI视频领域的创新发展。让我们将一起探讨…

【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 实现微服务的限流、降级、熔断

目录 一、Sentinel 介绍1.1 什么是 Sentinel1.2 Sentinel 特性1.3 限流、降级与熔断的区别 二、实战演示2.1 下载启动 Sentinel 控制台2.2 后端微服务接入 Sentinel 控制台2.2.1 引入 Sentinel 依赖2.2.2 添加 Sentinel 连接配置 2.3 使用 Sentinel 进行流控&#xff08;含限流…

如何将cocos2d-x js打包部署到ios上 Mac M1系统

项目环境 cocos2d-x 3.13 xcode 12 mac m1 big sur 先找到你的项目 使用xcode软件打开上面这个文件 打开后应该是这个样子 执行编译运行就好了 可能会碰到的错误 在xcode11版本以上都会有这个错误&#xff0c;这是因为iOS11废弃了system。 将上面代码修改为 #if (CC_TARGE…

Java 面向对象进阶 16 接口的细节:成员特点和接口的各种关系(黑马)

成员变量默认修饰符是public static final的原因是&#xff1a; Java中接口中成员变量默认修饰符是public static final的原因是为了确保接口的成员变量都是公共的、静态的和不可修改的。 - public修饰符确保了接口的成员变量可以在任何地方被访问到。 - static修饰符使得接口…

vue-利用属性(v-if)控制表单(el-form-item)显示/隐藏

表单控制属性 v-if 示例&#xff1a; 通过switch组件作为开关&#xff0c;控制表单的显示与隐藏 <el-form-item label"创建数据集"><el-switch v-model"selectFormVisible"></el-switch></el-form-item><el-form-item label&…

Redis篇----第七篇

系列文章目录 文章目录 系列文章目录前言一、Redis 的回收策略(淘汰策略)?二、为什么 edis 需要把所有数据放到内存中?三、Redis 的同步机制了解么?四、Pipeline 有什么好处,为什么要用 pipeline?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍…

crontab history查看命令的执行时间

crontab crontab学习网站&#xff08;19. crontab 定时任务 — Linux Tools Quick Tutorial&#xff09; 例子 今天实际工作里用到的&#xff08;已经进行了防信息泄露处理 比如我现在希望每周三上午10:00之行一个php脚本 --gpt生成 00 10 * * 3 cd /home/user/project/r…

阿里云SSL免费证书到期自动申请部署程序

阿里云的免费证书只有3个月的有效期&#xff0c;不注意就过期了&#xff0c;还要手动申请然后部署&#xff0c;很是麻烦&#xff0c;于是写了这个小工具。上班期间抽空写的&#xff0c;没有仔细测试&#xff0c;可能存在一些问题&#xff0c;大家可以自己clone代码改改&#xf…

【大模型 数据增强】LLMAAA:使用 LLMs 作为数据标注器

【大模型 数据增强】LLMAAA&#xff1a;使用 LLMs 作为数据标注器 提出背景算法步骤1. LLM作为活跃标注者&#xff08;LLMAAA&#xff09;2. k-NN示例检索与标签表述化3. 活跃学习策略4. 自动重权技术 LLMAAA 框架1. LLM Annotator2. Active Acquisition3. Robust Training 总结…

SkyWalking之APM无侵入可观测原理分析

一、 简介&#xff08;为什么需要用到可观测能力&#xff09; 随着微服务的开发模式的兴起&#xff0c;早期的单体架构系统已拆分为很多的子系统&#xff0c;各个子系统封装为微服务&#xff0c;各服务间通过HTTP协议RESET API或者RPC协议进行调用。 在单体服务或者微服务较少的…

8:00面试,8:05就出来了 ,问的实在是....

从外包出来&#xff0c;没想到竟然死在了另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以我也就忍了。没想到12月一纸通知&#xff0c;所有人都不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个…

ESP8266智能家居(2)——8266发布数据到mqtt服务器

1.公共服务器 学习物联网就离不开服务器&#xff0c;如果你资金充足的话&#xff0c;可以自己购买或者租用一个服务器。本次我选择&#xff0c;使用免费的公共MQTT服务器。它的端口及Broker信息如下&#xff1a; 网址为&#xff1a; 免费的公共 MQTT 服务器 | EMQ (emqx.com)h…

LLMChain使用 | RouterChain的使用 - 用本地大模型搭建多Agents

单个本地大模型搭建参考博客 单个Chain&#xff1a;面对一个需求&#xff0c;我们需要创建一个llmchain&#xff0c;设置一个prompt模板&#xff0c;这个chain能够接收一个用户input&#xff0c;并输出一个结果&#xff1b;多个Chain&#xff1a;考虑到同时面对多个需求&#x…