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

Android笔记(十四):JetPack Compose中附带效应(一)

在Android应用中可以通过定义可组合函数来搭建应用界面。应用界面的更新往往是与可组合函数内部定义的状态值相关联的。当界面的状态值发生变更,会导致应用界面进行更新。在Android笔记(九):Compose组件的状态,对Compose组件的状态进行详细地介绍。理想状态下,可组合函数使用是定义范围内的状态值,通过内部状态的变更,修改可组合项构成的界面。但是,在有些特殊场景下,需要在可组合项中运行一些在可组合函数作用域外的一些应用状态。简单地说,附带效应就是在可组合函数中一些可组合函数作用域外的应用状态的变化。这些效应在可组合函数范围外,对于可组合函数来说并不好控制,容易造成过度使用。因此需要结合Effect API来对这些效应进行可预测地控制和处理。

一、什么是附带效应

在前述中对附带效应进行简单说明,在下面需要通过应用实例来解释:
运行下列代码块:

//全局变量,在MainScreen作用域外
val timer = mutableIntStateOf(0)@Composable
fun MainScreen(){//函数作用域内var runningState = remember{mutableStateOf(false)}val scope = rememberCoroutineScope()Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "计时器",fontSize=30.sp,color=MaterialTheme.colorScheme.primary)Text(text = "${timer.value}秒",fontSize = 24.sp)Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center){IconButton(modifier =  Modifier.width(100.dp),onClick={runningState.value = truescope.launch {while(runningState.value){delay(1000)timer.value +=1Log.d("TAG","${timer} m")}}}){Row{Icon(imageVector = Icons.Filled.Start,tint = Color.Green,contentDescription = null)Text("计时")}}IconButton(modifier = Modifier.width(100.dp),onClick={runningState.value  = false}){Row{Icon(imageVector = Icons.Filled.Stop,tint = Color.Green,contentDescription = null)Text("计时")}}}}}
}

在上述代码中,有几点需要注意:

1.rememberCoroutineScope函数
rememberCoroutineScope函数可以获取感知作用域,在可组合项外启动协程。在上述代码中定义的IconButton中的点击事件onClick的,处理timerState值的变化。因为下列的代码段:

while(runningState.value){
delay(1000)
timer.value +=1
Log.d(“TAG”,"${timer} m")
}

delay函数是一个挂起函数,表示地是在不阻塞线程的情况下将协程延迟给定时间(例如代码中设置的1秒),并在指定时间后恢复。因此需要在协程的范围中调用delay这个挂起函数,因此利用rememberCoroutineScope函数获得一个CorountineScope协程范围,在该范围可以对挂起函数进行调用。

val scope = rememberCoroutineScope()
scope.launch{
while(runningState.value){
delay(1000)
timer.value +=1
Log.d(“TAG”,"${timer} m")
}
}

2.关于外部量的变化
定义了状态值timerState和MainScreen()这个可组合函数。timerState这个可变的状态值是一个全局量,在MainScreen()函数的作用域外。需要将timerState的值进行修改,会发生在两种情况:

(1)MainScreen()可组合函数调用时;
(2)MainScreen()函数进行重组时会调用timerState

而在第二种情况中,可组合函数重组时所触发timerState状态值的变化而变化的情况就是发生了附带效应。但是,上述代码定义的timerState值的变化是不可预知的,因为它的作用域在MainScreen函数外,外界也有可能存在促使timerState值变化的情况。因此需要一些方法对timerState值的变更情况做出预测并做出相应的处理。
在这里插入图片描述
图1 附带效应示意

二、LaunchedEffect

LaunchedEffect函数可以在某个可组合项的作用域内运行挂起函数时,它会启动内部的代码块到协程上下文CoroutineContext中。当函数的key1的值发生变化,会重构LaunchedEffect。这时,LaunchedEffect原来启动的协程会被取消然后又重新启动。当LaunchedEffect退出组合项时,协程会被取消。LaunchedEffect可组合函数定义如下:

@Composable @NonRestartableComposable
@OptIn(InternalComposeApi::class) fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit ) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block)
}
}

其中,LaunchedEffect函数中的参数:

key1:表示关键字可以是任何类型,如果是可变的状态值时,可以根据可变状态值的变化,取消原有协程并启动新的协程。 当key1为Unit或true时,LaunchedEffect函数将与当前重组函数保持一致的生命周期。
block:表示要调用的挂起函数。需要在协程范围中运行;

修改上述计时器的代码段,代码如下所示:

//全局变量,在MainScreen作用域外
val timer = mutableIntStateOf(0)@Composable
fun MainScreen(){//函数作用域内var runningState = remember{mutableStateOf(false)}val scope = rememberCoroutineScope()LaunchedEffect(key1 = timer.value){ //一旦发生修改,不断取消原有协程,创建新的协程,delay(1000)timer.value +=1 //会使得LaunchedEffect不断重构}Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "计时器",fontSize=30.sp,color=MaterialTheme.colorScheme.primary)Text(text = "${timer.value}秒",fontSize = 24.sp)Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center){IconButton(modifier =  Modifier.width(100.dp),onClick={runningState.value = truetimer.value +=1 //点击按钮控制第一次值的修改}){Row{Icon(imageVector = Icons.Filled.Start,tint = Color.Green,contentDescription = null)Text("计时")}}IconButton(modifier = Modifier.width(100.dp),onClick={runningState.value  = falsetimer.value +=1}){Row{Icon(imageVector = Icons.Filled.Stop,tint = Color.Green,contentDescription = null)Text("停止")}}}}}
}

在上述代码中可以发现,当点击计时按钮时,修改了LaunchedEffect函数中的键参数timer.value的值,使得,LaunchedEffect函数timer.value的值的变化,使得原有协程取消,启动新的协程,并在新的协程中修改了timer.value的值,LaunchedEffect函数的不断重构,使得状态值不断修改,导致计时界面MainScreen不断重构和刷新。通过这样的方式,达到对timer.value修改的可预测控制。但是这种不断切换协程的方式并不可取,可以修改代码成如下的形式:

//全局变量,在MainScreen作用域外
val timer = mutableIntStateOf(0)@Composable
fun MainScreen(){//函数作用域内var runningState = remember{mutableStateOf(false)}val scope = rememberCoroutineScope()LaunchedEffect(key1 = runningState.value){while(runningState.value){delay(1000)timer.value +=1Log.d("TAG","${timer} m")}}Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "计时器",fontSize=30.sp,color=MaterialTheme.colorScheme.primary)Text(text = "${timer.value}秒",fontSize = 24.sp)Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center){IconButton(modifier =  Modifier.width(100.dp),onClick={runningState.value = true}){Row{Icon(imageVector = Icons.Filled.Start,tint = Color.Green,contentDescription = null)Text("计时")}}IconButton(modifier = Modifier.width(100.dp),onClick={runningState.value  = false}){Row{Icon(imageVector = Icons.Filled.Stop,tint = Color.Green,contentDescription = null)Text("停止")}}}}}
}

运行效果是:
在这里插入图片描述
在上述代码段中,按钮的点击动作只是修改了可组合函数内部的runningState状态值。但是,

LaunchedEffect(key1 = runningState.value){
scope.launch {
while(runningState.value){
delay(1000)
timer.value +=1
Log.d(“TAG”,"${timer} m")
}
}
}

如果点击按钮,会修改键参数runingState.value的值。而在上述代码的LaunchedEffect函数中监控到runningState.value值的变化会将原来的协程取消,然后再重新启动新的协程,在新的协程中修改MainScreen可组合函数外部的timerState状态值,从而达到计时的功能实现。在这里,是通过控制runningState来达到修改外部timerState的目的。如果runningState.value值没有发生任何变化,那么原有的协程不会取消,会继续运行。如果runningState.value的值为false,那么界面将不会发生变化。通过这样的方式,LaunchedEffect监控key1的变化,使得对timerState.value修改可以控制。

三、rememberUpdatedState

在上述代码中,LaunchedEffect函数会根据关键字的值的变化,重启协程。但是,在某些情况下,并不希望LaunchedEffect重启,但是却需要LaunchedEffect函数中变更的状态的值。因此,可以考虑使用rememberUpdatedState函数用于创建可捕获和更新该值的引用。但是注意这样的处理方式代价会比较高昂。

//全局变量,在MainScreen作用域外
val timer = mutableIntStateOf(0)@Composable
fun MainScreen(){//函数作用域内var runningState = remember{mutableStateOf(true)}val scope = rememberCoroutineScope()val timerState = rememberUpdatedState(newValue = timer)LaunchedEffect(Unit){while(runningState.value){delay(1000)timerState.value.value +=1Log.d("TAG","${timer.value} m")}}Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "计时器",fontSize=30.sp,color=MaterialTheme.colorScheme.primary)Text(text = "${timerState.value.value}秒",fontSize = 24.sp)Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center){IconButton(modifier =  Modifier.width(100.dp),onClick={runningState.value = true}){Row{Icon(imageVector = Icons.Filled.Start,tint = Color.Green,contentDescription = null)Text("计时")}}IconButton(modifier = Modifier.width(100.dp),onClick={runningState.value  = falsetimer.value +=1}){Row{Icon(imageVector = Icons.Filled.Stop,tint = Color.Green,contentDescription = null)Text("停止")}}}}}
}

在上述代码中,MainScreen可组合函数内部定义:

val timerState = rememberUpdatedState(newValue = timer)
而在LaunchedEffect代码部分定义为:
LaunchedEffect(Unit){

        while(runningState.value){delay(1000)timerState.value.value +=1Log.d("TAG","${timer.value} m")}}

LaunchedEffect函数中的键参数为Unit,这表示在MainScreen函数被调用时或重新组合时,才会加载LaunchedEffect函数。并不存在键参数值的变化重新加载协程代码的可能。但是,通过rememberUpdatedState(newValue = timer)函数,一致可以通过timerState.value来获取变化的状态timer.
因此,在上述代码中,由于runningState.value初始值为true,因此一启动MainScreen,就会显示显示动态计时的效果。但是,当点击停止按钮runningState.value的值设置为false,导致代码段停止运行。然后再点击"计时“按钮,可以发现,LaunchedEffect函数并没有重启。

四、DisposableEffect清理效应

对于需要在键发生变化或可组合项退出组合后进行清理的附带效应,可以通过DisposableEffect来实现。DisposableEffect是组合的附效应。DisposableEffect函数定义如下:

@Composable
@NonRestartableComposable
fun DisposableEffect(
key1: Any?,
effect: DisposableEffectScope.() -> DisposableEffectResult ) {
remember(key1) {
DisposableEffectImpl(effect)
}
}
当key1是键参数,可以为任何值。当key1的值发生变化或者DisposableEffect离开可组合项,则必须执行撤销或清除操作。

//全局变量,在MainScreen作用域外
val timer = mutableIntStateOf(0)@Composable
fun MainScreen(){//函数作用域内var runningState = remember{mutableStateOf(true)}val scope = rememberCoroutineScope()LaunchedEffect(Unit){while(runningState.value){delay(1000)timer.value+=1Log.d("TAG","${timer.value} m")}}DisposableEffect(key1 = timer.value){onDispose {timer.value = 0runningState.value = false}}Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){Column(modifier = Modifier.fillMaxWidth(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){Text(text = "计时器",fontSize=30.sp,color=MaterialTheme.colorScheme.primary)Text(text = "${timer.value}秒",fontSize = 24.sp)Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center){IconButton(modifier =  Modifier.width(100.dp),onClick={runningState.value = true}){Row{Icon(imageVector = Icons.Filled.Start,tint = Color.Green,contentDescription = null)Text("计时")}}IconButton(modifier = Modifier.width(100.dp),onClick={runningState.value  = falsetimer.value +=1}){Row{Icon(imageVector = Icons.Filled.Stop,tint = Color.Green,contentDescription = null)Text("停止")}}}}}
}

上述代码的界面没有任何变化,这是因为下列代码

LaunchedEffect(Unit){
scope.launch {
while(runningState.value){
delay(1000)
timer.value+=1
Log.d(“TAG”,"${timer.value} m")
}
}
}

DisposableEffect(key1 = timer.value){onDispose {timer.value = 0runningState.value = false}
}

启动MainScreen时,timer.value修改为1,但是DisposableEffect发现到timer.value变更,就会执行清除操作,修改timer.value=0和runningState.value=false。在后续中将无法动态显示计时效果。DisposableEffect往往和生命周期进行关联,当键参数为生命周期拥有者对象时,可以利用DisposableEffect对生命周期拥有者的变化执行撤销或清理的工作。
在下面定义两个活动MainActivity和OtherActivity,这两个活动分别为不同的生命周期拥有者,具有不同的生命周期对象。具体定义如下:
(1)MainActivity的定义
首先定义依附MainActivity的界面HomeScreen的定义

@Composable
fun HomeScreen(lifecycleOwner:LifecycleOwner = LocalLifecycleOwner.current,onStart:(MutableState<Int>)->Unit,onStop:(MutableState<Int>)->Unit){val startAction by rememberUpdatedState(newValue = onStart)val stopAction by rememberUpdatedState(newValue = onStop)val context  = LocalContext.currentvar timer = remember{mutableIntStateOf(0)}//以键参数为生命周期拥有者lifecycleOwnerDisposableEffect(key1 = lifecycleOwner){//定义生命周期观察者val observer  = LifecycleEventObserver{_,event->if(event == Lifecycle.Event.ON_RESUME){startAction(timer)}else if(event == Lifecycle.Event.ON_STOP){stopAction(timer)}}//生命周期拥有者lifecycleOwner的生命周期lifecycle加入新的观察者observer,观察者可以观察生命周期的变化lifecycleOwner.lifecycle.addObserver(observer)onDispose {//当离开可组合项执行清理处理Log.d("TAG","清理完毕")timer.value  = 0lifecycleOwner.lifecycle.removeObserver(observer)}}Box(contentAlignment= Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text(text = "${timer.value}秒",fontSize=30.sp)Button(onClick = {//跳转到其他活动,即修改了生命周期拥有者对象val intent = Intent(context, OtherActivity::class.java)context.startActivity(intent)}){Text("跳转到其他活动")}}}
}

再定义主活动MainActivity

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Ch06_DemoTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {HomeScreen(onStart = ::onStartCall, onStop = ::onStopCall )}}}}fun onStartCall(timer: MutableState<Int>){Log.d("TAG","startCall")thread{while(timer.value<100){timer.value++Thread.sleep(1000)}}}fun onStopCall(timer: MutableState<Int>){//start停止Log.d("TAG","stopCall")}
}

(2)OtherActivity的定义

class OtherActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent{Box(contentAlignment = Alignment.Center,modifier = Modifier.wrapContentSize()){Text("OtherActivity",fontSize = 30.sp)}}}
}

运行情况说明:
(1)点击运行的效果如下图所示:
在这里插入图片描述
(2)当点击”跳转其他活动“按钮,跳转到OtherActivity的界面,跟踪的日志如下:
日志显示
在这里插入图片描述

(3)当彻底退出关闭这个应用,则在日志中显示:
在这里插入图片描述
这是因为,在跳转到OtherActivity时,LifecycleOwner虽然已经切换到OtherActivity,但是界面HomeScreen已经在后台,因此并没有执行onDispose的代码块。只有在彻底退出应用,彻底离开可组合函数HomeScreen,可组合函数HomeScreen中的DisposableEffect中监测到代码生命周期拥有者的对象发生了变化,因此执行onDispose代码块。

五、SideEffect

可组合函数进行重组时并不是每次都成功,如果出现界面状态值发生变化,但是界面会进行重组。但是,在重组的过程中,一些状态数据又发生了变化,导致上次重组没有完全完成。这就使得一些与界面重组无关的数据和代码也会被多次调用,这种情况显然是没有必要的。在这样的前提下,可以使用SideEfffect。

SideEffect表示“副作用”,它将 Compose 的状态发布为非 Compose 代码。如需与非 Compose 管理的对象共享 Compose 状态,可使用 SideEffect 中可组合项,因为只有每次成功重组时才会调用该可组合项

SideEffect函数定义如下:

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(effect: () -> Unit ) {
currentComposer.recordSideEffect(effect)
}

观察下列代码运行结果和日志:

@Preview
@Composable
fun SideScreen(){//可组合的状态var timer by remember{ mutableIntStateOf(0) }var running by remember{mutableStateOf(false)}Log.d("TAG","在SideEffect前的日志")SideEffect{Log.d("TAG","执行SideEffect函数:running:${running}")if(running) {timer++Thread.sleep(1000)}}Log.d("TAG","在SideEffect函数后的日志")Box(contentAlignment= Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text(text = "${timer}秒",fontSize=30.sp)Row(horizontalArrangement = Arrangement.Center){Button(onClick = {running  = trueLog.d("TAG","running:${running}")}){Text("计时")}Button(onClick = {running = falsetimer = 0Log.d("TAG","running:${running}")}){Text("停止")}}}}
}

上述代码的运行结果如下所示:
在这里插入图片描述
并没有发生动态更新计时时间的显示。这是因为,running的值为false,在条件判断时,使得timer状态值无法发生变化,状态值没有变化,使得界面只会在调用时刷新界面。即使点击”计时“按钮,修改了running的值为true,因为影响界面重组的状态值timer并没有变化,因此界面没有发生重组。这与日志显示是一致的。下图显示的日志就是这样的效果。
在这里插入图片描述
观察日志,可以发现调用可组合函数SideScreen时,重组成功后,才会调用SideEffect函数。因为日志输出SideEffect函数内部的Lambda代码段中的日志是最后调用的。
再来观察下列代码
(1)定义数据类Timer

/*** counter:Int记录已经计时的时间* timerInterval:Int时间间隔* /
data class Timer(var counter:Int,val timeInterval:Int=1000)

(2)定义将Compose状态返回非Compose的值

@Composable
fun rememberTimer(counter:Int):Timer{//定义可组合的对象val timer = remember{Timer(0)}SideEffect{timer.counter = counter}return timer
}

在上述的可组合函数rememberTimer中生成并返回了一个非Compose的Timer对象

(3)在界面中进行测试

@Preview
@Composable
fun SideScreen(){//非状态的对象var timer = rememberTimer(counter = 0)val scope = rememberCoroutineScope()var running by remember{mutableStateOf(true)}Box(contentAlignment= Alignment.Center,modifier = Modifier.fillMaxSize()){Column{Text(text = "${timer.counter}秒",fontSize=30.sp)Row(horizontalArrangement = Arrangement.Center){Button(onClick = {running  = truescope.launch {while(running){delay(1000)timer.counter++Log.d("TAG",""+timer.counter)}}Log.d("TAG","running:${running}")}){Text("计时")}Button(onClick = {running = falsetimer.counter = 0Log.d("TAG","running:${running}")}){Text("停止")}}}}
}

运行效果如上图一致。
执行点击”计时“按钮,即使running的值为true,界面不会重组。这是因为影响界面的timer已经是一个非Compose的对象,并不能对界面的重组起到作用。观察日志,可以发现timer.counter值的确每秒进行变更了。
在这里插入图片描述

参考文献

1.Compose 中的附带效应
https://developer.android.google.cn/jetpack/compose/side-effects?hl=zh-cn

相关文章:

Android笔记(十四):JetPack Compose中附带效应(一)

在Android应用中可以通过定义可组合函数来搭建应用界面。应用界面的更新往往是与可组合函数内部定义的状态值相关联的。当界面的状态值发生变更&#xff0c;会导致应用界面进行更新。在Android笔记&#xff08;九&#xff09;&#xff1a;Compose组件的状态&#xff0c;对Compo…...

【web】Fastapi自动生成接口文档(Swagger、ReDoc )

简介 FastAPI是流行的Python web框架&#xff0c;适用于开发高吞吐量API和微服务&#xff08;直接支持异步编程&#xff09; FastAPI的优势之一&#xff1a;通过提供高级抽象和自动数据模型转换&#xff0c;简化请求数据的处理&#xff08;用户不需要手动处理原始请求数据&am…...

竞赛选题 题目:基于FP-Growth的新闻挖掘算法系统的设计与实现

文章目录 0 前言1 项目背景2 算法架构3 FP-Growth算法原理3.1 FP树3.2 算法过程3.3 算法实现3.3.1 构建FP树 3.4 从FP树中挖掘频繁项集 4 系统设计展示5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…...

188. 股票买卖问题(交易次数为任意正整数)

题目 题解 class Solution:def maxProfit(self, k: int, prices: List[int]) -> int:N len(prices)# 定义状态:dp[i][j][k]表示在第i天&#xff0c;有j次交易机会&#xff0c;持有或不持有的最大利润dp [[[0 for i in range(2)] for j in range(k1)] for m in range(N)]f…...

Typescript怎样对URL参数进行编码?

URL中的参数需要进行编码&#xff08;URL encoding&#xff09;是为了确保传输的参数不包含特殊字符&#xff0c;同时确保数据的可靠性和安全性。 特殊字符如空格、&、?等在URL中有特殊含义&#xff0c;如果直接包含在参数值中&#xff0c;可能会导致解析错误或者安全问题…...

AndroidStudio2022.3.1 Patch3使用国内下载源加速

记录一下这个版本的as在使用国内下载源加速碰到的诸多问题。 一、gradle-8.0-bin.zip下载慢 编辑项目文件夹/gradle/wrapper/gradle-wrapper.properties&#xff0c;文件内容改为如下&#xff1a; #Fri Nov 24 18:50:06 CST 2023 distributionBaseGRADLE_USER_HOME distribu…...

Go语言的学习笔记2——Go语言源文件的结构布局

用一个只有main函数的go文件来简单说一下Go语言的源文件结构布局&#xff0c;主要分为包名、引入的包和具体函数。下边是main.go示例代码&#xff1a; package mainimport "fmt"func main() { fmt.Println("hello, world") }package main就是表明这个文件…...

python给视频增加字幕

python给视频增加字幕 安装所需库 在开始之前&#xff0c;我们需要安装一些Python库。主要使用到的库如下&#xff1a; moviepy&#xff1a;用于处理视频和音频的库。 pydub&#xff1a;用于处理音频的库。 speech_recognition&#xff1a;用于语音识别的库。 首先&#xff0…...

相机设置参数:黑电平(Black Level)详解和示例

本文通过原理和示例对相机设置参数“黑电平”进行讲解&#xff0c;以帮助大家理解和使用。 原理 相机中黑电平原理是将电平增大&#xff0c;可以显示更多暗区细节&#xff0c;可能会损失一些亮区&#xff0c;但图像更多的关注暗区&#xff0c;获取完图像信息再减掉。只是为了…...

Mac Ubuntu双系统解决WiFi和WiFi 5G网络不可用问题

文章目录 设备信息1. Ubuntu WiFi不可用解决方式查看Mac的网卡型号根据网卡型号搜索获取到的解决方法查看WiFi名字问题参考链接 2. 解决WiFi重启后失效问题打开终端创建.sh脚本文件编辑脚本文件复制粘贴脚本修改脚本权限创建并编辑systemd service文件复制粘贴下文到systemd se…...

数据分析基础之《matplotlib(2)—折线图》

一、折线图绘制与保存图片 1、matplotlib.pyplot模块 matplotlib.pyplot包含了一系列类似于matlab的画图函数。它的函数作用于当前图形&#xff08;figure&#xff09;的当前坐标系&#xff08;axes&#xff09; import matplotlib.pyplot as plt 2、折线图绘制与显示 展示城…...

Rust语言入门教程(三) - 函数与模块系统

函数 函数的定义 根据Rust的格式规范&#xff0c;函数名的格式应遵从蛇形命名法&#xff0c;即是用小写字母以及下划线组成&#xff0c;如&#xff1a; fn do_stuff(){ }Rust并不要求函数定义的位置必须在调用它之前&#xff0c;所以如果你习惯于把main函数放在最前面的话&a…...

ubuntu22.04 arrch64版在线安装java环境

脚本 #安装java#!/bin/bashif type -p java; thenecho "Java has been installed."else#2.Installed Java , must install wgetwget -c https://repo.huaweicloud.com/java/jdk/8u151-b12/jdk-8u151-linux-arm64-vfp-hflt.tar.gz;tar -zxvf ./jdk-8u151-linux-arm6…...

概率论与数理统计中常见的随机变量分布律、数学期望、方差及其介绍

1 离散型随机变量 1.1 0-1分布 设随机变量X的所有可能取值为0与1两个值&#xff0c;其分布律为 若分布律如上所示&#xff0c;则称X服从以P为参数的(0-1)分布或两点分布。记作X~ B(1&#xff0c;p) 0-1分布的分布律利用表格法表示为: X01P1-PP 0-1分布的数学期望E(X) 0 *…...

骨传导耳机的优缺点都有哪些?骨传导耳机值得入手吗?

骨传导耳机的优点还是很多的&#xff0c;相比于传统耳机&#xff0c;骨传导耳机要更值得入手&#xff01; 下面让我们了解下骨传导耳机的优缺点都有哪些&#xff1a; 一、优点 1、使用更安全 传统的耳机&#xff0c;在使用时会听不到外界的声音&#xff0c;而骨传导耳机通过…...

在ASP.NET Core 中使用 .NET Aspire 消息传递组件

前言 云原生应用程序通常需要可扩展的消息传递解决方案&#xff0c;以提供消息队列、主题和订阅等功能。.NET Aspire 组件简化了连接到各种消息传递提供程序&#xff08;例如 Azure 服务总线&#xff09;的过程。在本教程中&#xff0c;小编将为大家介绍如何创建一个 ASP.NET …...

NLP学习

参考&#xff1a;NLP发展之路I - 从词袋模型到Transformer - 知乎 (zhihu.com) NLP大致的发展历史。从最开始的词袋模型&#xff0c;到RNN&#xff0c;到Transformers和BERT&#xff0c;再到ChatGPT&#xff0c;NLP经历了一段不断精进的发展道路。数据驱动和不断完善的端到端的…...

Linux-Ubuntu环境下搭建SVN服务器

Linux-Ubuntu环境下搭建SVN服务器 一、背景二、前置工作2.1确定IP地址保持不变2.2关闭防火墙 三、安装SVN服务器四、修改SVN服务器版本库目录五、调整SVN配置5.1查看需要修改的配置文件5.2修改svnserve.conf文件5.3修改passwd文件&#xff0c;添加账号和密码&#xff08;window…...

python tkinter使用(四)

本篇文章主要讲下tkinter 的文本框相关. tkinter中用Entry来实现输入框,类似于android中的edittext. 具体的用法如下: 1:空白输入框 如下: name tk.Entry(window) name.pack()2: 设置输入框的默认文案 name tk.Entry(window) name.pack() name.insert(tk.END, "请…...

记录ruoyi-plus-vue部署的问题

ruoyi-vue-plus5.x 后端 ruoyi-vue-plus5.x 前端 前端本地启动命令 # 克隆项目 git clone https://gitee.com/JavaLionLi/plus-ui.git# 安装依赖 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev# 构建生产环境 yarn build:prod # 前端访问地址…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...