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

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

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

相关文章

C语言—指针和数组

写在前 一个指针变量指向某个普通变量&#xff0c;则指针变量就等于普通变量。 指针变量存放的是地址&#xff0c;普通变量存放的是数据。 int * p; int i5,j; p &i;此程序&#xff0c;*pi5&#xff0c;在所有出现 *p 或 i 的位置&#xff0c;两者都可以互相替换。 通过…

单文件组件MVVM

单文件组件&MVVM 所谓组件化开发&#xff0c;就是创建一个个组件。 Vue是一个大类&#xff0c;渲染一切从new Vue开始。 指定视图&#xff1a;el template render:jsx语法 $mount[数学公式] 编译App.vue&#xff0c;作为视图入口 单个组件&#xff1a;结构 样式 data compu…

十大排序之堆排序(详解)

文章目录 &#x1f412;个人主页&#x1f3c5;算法思维框架&#x1f4d6;前言&#xff1a; &#x1f380;堆排序 时间复杂度O(n*logn)&#x1f387;1. 算法步骤思想&#x1f387;2、动画演示&#x1f387;3.代码实现 &#x1f412;个人主页 &#x1f3c5;算法思维框架 &#x1…

vivado产生报告阅读分析22

“ Advanced ”选项卡 “ Advanced ” &#xff08; 高级 &#xff09; 选项卡如下图所示。 在“ Advanced ”选项卡中提供了以下字段 &#xff1a; • “ Report ” &#xff08; 报告 &#xff09;&#xff1a; 选中“ Advanced ”选项卡中的“ Cells to Analyze ” &…

GEE:通过将 Landsat 5、7、8、9 的 C02 数据集合并起来,构建 NDVI 长时间序列

作者:CSDN @ _养乐多_ 本文记录了在 Google Earth Engine(GEE)平台上,将 Landsat-5、Landsat-7、Landsat-8 和 Landsat-9 的数据合成为一个影像集合,并生成 NDVI(归一化植被指数)的时间序列的代码。 代码封装成了函数,方便调用,结果如下图所示, 在实际应用中,可能…

【迅搜03】全文检索、文档、倒排索引与分词

全文检索、文档、倒排索引与分词 今天还是概念性的内容&#xff0c;但是这些概念却是整个搜索引擎中最重要的概念。可以说&#xff0c;所有的搜索引擎就是实现了类似的概念才能称之为搜索引擎。而且今天的内容其实都是相关联的&#xff0c;所以不要以为标题上有四个名词就感觉好…

windows运行Pangolin应用填坑心得——如何在window应用轻量级opengl软件Pangolin库显示3D界面及窗口

目录 0、前言1、最有效的安装打开方式准备工作安装git安装vcpkg&#xff08;1&#xff09;下载&#xff08;2&#xff09;安装&#xff08;3&#xff09;集成至vs 安装cmake 安装pangolin 2、应用实例c工程&#xff08;1&#xff09;vs创建新工程&#xff08;2&#xff09;新工…

电子学会C/C++编程等级考试2021年06月(二级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:数字放大 给定一个整数序列以及放大倍数x,将序列中每个整数放大x倍后输出。 时间限制:1000 内存限制:65536输入 包含三行: 第一行为N,表示整数序列的长度(N ≤ 100); 第二行为N个整数(不超过整型范围),整数之间以一个空格…

linux开启apache服务

vim /etc/apache2/ports.conf 键盘输入i 进入插入编辑模式&#xff0c;修改apache2默认监听端口号为8080 &#xff0c;编辑好后&#xff0c;按Esc键“&#xff1a;wq!” 保存退出。&#xff08;注&#xff1a;端口也可以不修改&#xff09; 在终端输入“/etc/init.d/apache2 …

vivado产生报告阅读分析21

其他命令选项 • -of_objects <suggestion objects> &#xff1a; 启用特定建议的报告。在此模式下运行时 &#xff0c; report_qor_suggestions 不会生成新建议。此命令可快速执行 &#xff0c; 读取 RQS 文件后 &#xff0c; 此命令可用于查看其中包 含的建议。其…

人工智能Keras图像分类器(CNN卷积神经网络的图片识别篇)

上期文章我们分享了人工智能Keras图像分类器(CNN卷积神经网络的图片识别的训练模型),本期我们使用预训练模型对图片进行识别:Keras CNN卷积神经网络模型训练 导入第三方库 from keras.preprocessing.image import img_to_array from keras.models import load_model impor…

将 Hexo 部署到阿里云轻量服务器(保姆级教程)

将 Hexo 部署到阿里云轻量服务器(保姆级教程) 顺哥轻创 1 前言 作为有梦想的,有追求的程序员,有一个自己的个人博客简直就是必须品。你可以选择 wordpress 这种平台,直接使用,在任何地方只要有网络就能写博客。还可以选择 hexo 这种静态博客,但是发文章就没有那么随心…