妈!Jetpack Compose太难学了,别怕,这里帮你理清几个概念(二)

简介: 妈!Jetpack Compose太难学了,别怕,这里帮你理清几个概念

五、解决附带效应


附带效应是指发生在可组合函数作用域之外的应用状态的变化,例如当进入页面的时候更新一下当前的位置,亦或者修改一下全局变量,如果直接在可组合函数里面引入这些附带效应的话,会让逻辑出现严重的问题,下面是两种常见的错误的附带效应的使用案例:


/**
 * 手机位置更新服务
 */
object PhoneLocationUpdateService{
    //...
    fun updateMyLocation(){
        // TODO: 更新当前的位置
    }
    //...
}
/**
 * 全局变量、可组合函数以外的变量
 */
var globalParams:Int=0
@Composable
fun TestScreen(){
    //❎的做法一
    PhoneLocationUpdateService.updateMyLocation()
    //❎错误的做法二
    globalParams++
    Column{
        //...
    }
}


为什么附带效应在Compose中存在问题?


这要结合Compose的生命周期来说,回归到生命周期的这张图中

image.png

如果我们希望可组合函数显示的时候,都让某个全局变量增加1的话,确实很容易直接在可组合函数的开头几句中直接让全局变量+1,但是需要重视的是,可组合函数会在生命周期期间多次重组的,也就是自身会被多次调用,这样就和我们需要的业务相违背了。

相似地,如果我们在AndroidView中的onLayout中插入某些访问全局变量的代码的话,可想而知会出现多大的问题(因为onLayout会频繁被调用而且次数未知)。


var globalParams:Int=0
class CrazyView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        //让人疯狂!!!
        globalParams++
    }
}

解决附带效应利器——SideEffect Api


Compose 编程思想文档中所述,可组合项应该没有附带效应。如果您需要更改应用的状态(如管理状态文档中所述),您应该使用 Effect API,以便以可预测的方式执行这些附带效应

换句话说,我们的可组合函数确实有些情况需要带一点附带效应,但是我们希望以"可预测的方式"的执行。

注:SideEffect较多,属于最难上手的部分,但是只要你搞懂了他们的使用场景和解决的问题,就可以大胆使用了。


1.LaunchedEffect:在某个可组合项的作用域内运行挂起函数


使用场景:希望在一个组合内进行异步操作

LaunchedEffect会提供一个协程作用域,这个作用域不会随着重组消失,它只会在该组合销毁的时候停止。

LaunchedEffect和remember一样,使用key作为是否重启的标志,当key发生变化的时候,会重新启动运行挂起函数

下图展示了一个组件显示3秒之后会弹出一个"我显示了"的文字的可组合函数

image.png

图中的key1传入了Unit,也就是LaunchedEffect不会重启,你可以通过改变key的方式让它重启,具体得看业务需要。


2.rememberCoroutineScope:获取组合感知作用域,以便在可组合项外启动协程


使用场景:希望在非重组作用域启动协程任务

Compose中并不都是重组作用域,有一些诸如点击回调的地方,我们也希望启动协程任务,这样LaunchedEffect就无法满足我们的需求了,因为LaunchedEffect是一个可组合函数,他无法在重组作用域以外的地方调用。

下面看看案例,我们在重组作用域使用rememberCoroutineScope()方法生成一个scope,这个scope的生命周期和组合的生命周期也是一致的,从组合出现到销毁,中间的重组并不会影响它,同时我们根据名字也可以知道,这个scope内部是被remember处理过,我们不用担心重组之后又生成一个Scope。

接着我们就可以在非重组作用域(图中是onClick回调)中使用协程来完成异步操作。

image.png

你看懂了吗,点击按钮的3秒后,Text就会显示一段文字。


3.rememberUpdatedState:在效应中引用某个值,该效应在值改变时不应重启


使用场景:LaunchedEffect中执行了一段异步操作之后,希望取到最新的方法参数的值

假设我们拥有这样一个可组合函数,他的逻辑希望是:3秒后显示传入的num。

image.png

实际上,当你在3秒内传入了不同的num,在3秒后显示的结果是第一次传入的num。

这是什么情况呢,还记得LaunchedEffect的设计吗,它的设计就是避免异步逻辑遭受重组的干扰,因此只有第一次传入的num会真正被LaunchedEffect的lambda拿走,其余的num都被LaunchedEffect自身的设计忽视了。

这个时候会有人想起,LaunchedEffect的key是可以让它重启的,于是会改造成这样:

image.png

每次num发生变化的时候,都重启LaunchedEffect,这样不就可以在3秒倒计时之后,取到的都是最新的num吗,最终结果来说这是没问题的,显示的也是最新的值,但是问题是:倒计时也重启了。

在这种场景下,就需要使用rememberUpdatedState()了,它本质上非常简单,让我们看看源码:


@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
    mutableStateOf(newValue)
}.apply { value = newValue }

实际上就是把一个值缓存在一个MutableState里面而已,这样有什么用呢,我们看看改造后的代码:

image.png

我们继续看代码,使用rememberUpdatedState()num缓存在一个MutableState中,当LaunchedEffect内部的delay结束时,通过MutableState访问到了最新的值。

等等,为什么这个时候获取到的是最新的值呢,不是说LaunchedEffect不是不会受到重组影响吗,当然不会,还记得MutableStateby使用方式吗,我们访问rememberUpDatedNum实际上是访问了MutableState内部的value变量,MutableState自始至终都没发生过变化,而是它内部的value发生了变化,因此我们可以取到最新的值。


4.DisposableEffect:需要清理的效应


使用场景:当前可组合函数去订阅某些信息,而且可组合函数销毁的时候取消订阅

假设我们有一个这样的天气服务,可以通知所有的订阅者当前的天气。


interface WeatherListener{
    fun onUpdate(weather:String)
}
object WeatherService{
    private val observerList=mutableListOf<WeatherListener>()
    fun addObserver(observer:WeatherListener)= observerList.add(observer)
    fun remove(observer: WeatherListener)=observerList.remove(observer)
    fun update(){
        observerList.forEach {
            it.onUpdate("下雨了")
        }
    }
}

我们希望在一个组合中订阅实时的天气,可以这样做:


@Composable
fun Weather(){
    var weatherString by remember{ mutableStateOf("") }
    DisposableEffect(Unit){
        val listener=object:WeatherListener{
            override fun onUpdate(weather: String) {
                weatherString=weather
            }
        }
        WeatherService.addObserver(listener)
        onDispose {
            WeatherService.remove(listener)
        }
    }
    Text("当前的天气:${weatherString}")
}

DisposableEffectLaunchedEffect很类似,都有key作为重启的标识,只是必须调用onDispose方法结尾,在onDispose中进行解绑操作。


5.derivedStateOf:将一个或多个状态对象转换为其他状态


使用场景:订阅可观察的列表变化、观察多个状态的变化等

有时候我们希望某个状态发生改变的时候,会改变另外一个状态的值,通常可以使用rememberkey来完成这个业务,例如下图,showText的值会在num改变的时候重新生成。

image.png

但是有些可观察的状态我们是无法使用为rememberkey的,因为变化并不是发生在它自身的值的变化,而是其内部的值发生了变化,例如常见的mutableListOf()生成的列表。

image.png

为什么会没用呢,因为remember比较的额是对象自身,而不是对象内部的内容,对于list来说,它从来没有改变为其他引用,我们只改变它内部的元素,因此remember是无法感知到list的变化的,这时候我们就需要使用derivedStateOf来感知。

image.png

derivedStateOf传入的lambda里面的任意一个MutableState发生变化的时候,就会重新生成一个新值。

其他类内部包含了其他State的也可以通过这种方式来观察其变化。


总结


笔者大致讲了一下Compose的基础概念和入门难点,希望大家在入门Compose的过程中少走弯路,多理解不同api在合适的场景下的作用,后续会出更多Compose和其他安卓开发的文章,请关注订阅点赞。


相关文章
|
20天前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
20天前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
14天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
16天前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
16天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
19天前
|
监控 Android开发 数据安全/隐私保护
安卓kotlin JetPack Compose 实现摄像头监控画面变化并录制视频
在这个示例中,开发者正在使用Kotlin和Jetpack Compose构建一个Android应用程序,该程序 能够通过手机后置主摄像头录制视频、检测画面差异、实时预览并将视频上传至FTP服务器的Android应用
|
4天前
深入了解 Jetpack Compose 中的 Modifier
深入了解 Jetpack Compose 中的 Modifier
5 0
|
4天前
|
Android开发
Jetpack Compose: Hello Android
Jetpack Compose: Hello Android
5 0
|
18天前
|
安全 网络安全 API
kotlin安卓开发JetPack Compose 如何使用webview 打开网页时给webview注入cookie
在Jetpack Compose中使用WebView需借助AndroidView。要注入Cookie,首先在`build.gradle`添加WebView依赖,如`androidx.webkit:webkit:1.4.0`。接着创建自定义`ComposableWebView`,通过`CookieManager`设置接受第三方Cookie并注入Cookie字符串。最后在Compose界面使用这个自定义组件加载URL。注意Android 9及以上版本可能需要在网络安全配置中允许第三方Cookie。
111 0
|
20天前
|
Android开发 Kotlin
kotlin安卓开发【Jetpack Compose】:封装SnackBarUtil工具类方便使用
GPT-4o 是一个非常智能的模型,比当前的通义千问最新版本在能力上有显著提升。作者让GPT开发一段代码,功能为在 Kotlin 中使用 Jetpack Compose 框架封装一个 Snackbar 工具类,方便调用