面试被问到Compose的副作用不会,只怪我没好好学

前言

之前有次面试,有个老哥问我有没有Compose项目经验,我说没有但是平时会写一些demo玩玩,之后就问了我个问题,记得是DisposableEffect,SideEffect以及LaunchedEffect的区别,由于我只用过LaunchedEffect所以这个问题也就没说上来,显然这个面试也是没过的,之后就怀恨在心,运气差自己呆的公司都不用Compose,甚至上上家公司对Compose还带有一些鄙视认为起不来,而如今被一些命好有Compose项目经验的人问问题,实在是有种龙游浅滩的感觉,我玩Compose的时候你们这些人估计还只会写xml吧,不过没办法事实摆在眼前,我这个"老兵"的Compose实力还是不过关的,需要再提升一下,那么就先从副作用api开始吧

什么是副作用以及副作用api

Composable在执行过程中凡是会影响外界操作,都称为副作用,比如谈个Toast或者请求个接口,然后一个Composable会由于重组会反复的执行,在这个过程中副作用是不应该也跟着执行的,因此副作用api就是用来控制副作用只发生在Composable生命周期的特定阶段

DisposableEffect

我们通常会在Activity的OnCreate以及onDestroy两个函数中做一些初始化以及释放资源的操作,而对于一个Composable来说,如果也想在OnActive以及onDispose两个生命周期阶段做一些事情的话,就要用到DisposableEffect这个副作用api

DisposableEffect内部参数是若干个观察参数key以及最后的block,如上述代码中key只是一个Unit,那么就说明block内部代码只在onActive时候执行,类似的把Unit换成true或者false,也是一样的,同时还注意到了在block中还有一个onDispose函数,如果使用DisposableEffect这个副作用函数,那么onDispose函数是必须要写的,不然编辑器会报错,它的作用是当Composable生命周期进入onDispose阶段时候,DisposableEffectonDispose函数就会执行,通常可以做一些释放资源等操作,再来看下面这段代码

DisposableEffect的参数变成了一个可变的变量count,此时block执行的时机除了在onActive的时候,在每次count改变的时候也会执行,也就是在onActive以及onUpdate两个生命周期阶段都会执行,另外当每次有新的副作用到来的时候,前一次副作用都会执行onDispose函数

SideEffect

这个副作用api也是跟重组相关,但是执行时机不是开始重组而是每次成功重组的时候,换句话说如果当前重组还在执行中,那么SideEffect的block是不会执行的,比如下面这段代码

这个Composable中会创建100个Text,对于重组来说会花费一些时间,代码中做了个实验,在重组的开始的地方以及执行SideEffect的地方都打印了时间,来看下日志

明显开始执行重组后并没有立马执行SideEffect的代码,而是等了一会,等的是重组完成,那么问题来了,是不是每次重组完成后都会执行SideEffect呢?答案是不会,原因上面已说明,必须要重组成功才行,比如下面这段代码,SideEffect就不会执行

在重组中抛了个异常,这样造成重组不会成功,这样就不会执行SideEffect的代码

LaunchedEffect

DisposableEffect比较相似,LaunchedEffect的入参也会接收若干个观察参数key以及最后一个block参数

但是也存在着区别,观察仔细的可以发现LaunchedEffect的block参数它是一个挂起函数,通过启动一个协程来执行block中的内容,所以这里面就特别适合执行一些异步操作,比如网络请求等

当参数id不变的时候,LaunchedEffect内部的副作用也就是网络请求只会在onActive的时候执行一遍,之后无论Composable如何重组,只要id不改变,副作用就不会执行,但是如果id发生改变了,那么LaunchedEffect内部的协程将会自动取消,并且启动新的协程来再一次发起网络请求,当然当Composable的生命周期进入onDispose阶段的时候,LaunchedEffect中的协程也会自动取消,这就是为什么LaunchedEffect内部不需要写onDispose函数的原因

rememberCoroutineScope

那么如果当前Composable中已经在使用DisposableEffect这个副作用api了,如果要在副作用中使用协程,并且能够在onDispose阶段取消协程该怎么做呢?当然一个方法就是把DisposableEffect换成上面讲的LaunchedEffect,另一个方法就是使用rememberCoroutineScope创建一个协程作用域

rememberCoroutineScope创建了一个CoroutineScope,由它创建的协程能够在Composable进入onDispose阶段的时候自动取消,当然如果DisposableEffect的观察参数是可变的话,当参数发生改变的时候,协程也会自动取消,并且开启一个新的协程

rememberUpdateState

但有时候我们希望的是副作用中的协程不会随着重组而重启,那么自然会将观察参数设置成不可变的,但是又希望副作用中可以获取到外界某个值的实时更新状态,这种情况就可以尝试下rememberUpdateState这个api去实现,比如如下代码

Composable中有个副作用并使用Flow实现定时打印值的功能,从代码中可以知道副作用中的协程不会随着重组而重启,但是由于打印的state是由rememberUpdateState创建的,所以当外界改变state的时候,副作用中打印的state值也会改变,比如外界使用一个按钮,点击一次让这个值加一

这就是rememberUpdateState的作用,在副作用内部操作不重启的时候,实时改变内部的值

snapshotFlow

刚才rememberUpdateState是用在了Stateless Composable里面,但如果是在Stateful Composable里面,副作用里面如果获取外部状态,这种情况就需要用到snapshotFlow这个api,它订阅了数据的变化,一旦数据有改变,snapshotFlow就会将数据发送到下游,看如下代码

按钮点击一次就改变state的值,LaunchedEffect中的副作用中使用snapshotFlow监听state的变化,观察日志就可以清楚的看到snapshotFlow观察数据的实时性

从点击到获取到数据,只有几毫秒到十几毫秒的时间差,snapshotFlow可以轻松完成在副作用内部监听外部数据变化的需求

总结

本篇我们学了

  • DisposableEffect:能够感知Composable的onActiveonDispose两个阶段,通过观察参数来决定副作用代码是否重新执行,必须在block内部显式的写出onDispose函数,该函数会在Composable进入onDispose阶段或者有新的副作用进来的时候被调用
  • SideEffect:会在一次重组成功执行后被调用,只要重组正在执行中或者执行失败,SideEffect中的代码将不被调用
  • LaunchedEffect:内部block是一个协程作用域,当观察参数变化的时候,协程将会自动取消并重新开启一个新的协程,不必显式的写出onDispose函数,Composable会在进入onDispose阶段后自动取消协程
  • rememberCoroutineScope:能够在非Composable环境下使用协程,比如DisposableEffect中或者一个按钮的onClick事件中
  • rememberUpdateState:能够在副作用中获取外部状态
  • snapshotFlow:能够在副作用中监听外部状态变化
相关推荐
Greenland_122 小时前
Android Gralde补全计划 productFlavors多渠道打包(变体/多客户)
android
Just_Paranoid2 小时前
【TaskStackListener】Android 中用于监听和响应任务栈
android·ams·task·taskstack
权泽谦2 小时前
从零搭建一个 PHP 登录注册系统(含完整源码)
android·开发语言·php
aaajj4 小时前
android contentprovider及其查看
android
fundroid9 小时前
Android Studio + Gemini:重塑安卓 AI 开发新范式
android·android studio·ai编程
vortex510 小时前
谷歌黑客语法挖掘 SQL 注入漏洞
android·数据库·sql
-指短琴长-13 小时前
MySQL快速入门——基本查询(下)
android·mysql·adb
stevenzqzq14 小时前
android lambda回调
android
林北北的霸霸16 小时前
django初识与安装
android·mysql·adb