协程思维
非阻塞(挂起与恢复)
- 线程
- 协程
启动协程
launch
/* delay 函数的定义
注意这个关键字
↓ */
public suspend fun delay(timeMillis: Long) { ... }
// 仅用于研究,生产环境不建议使用GlobalScope
fun main() {
GlobalScope.launch {
delay(1000L)
println("Hello World!")
}
Thread.sleep(2000L)
}
/*
输出结果;
Hello World!
runBlocking
fun main() {
runBlocking { // 1
println("Coroutine started!") // 2
delay(1000L) // 3
println("Hello World!") // 4
}
println("After launch!") // 5
Thread.sleep(2000L) // 6
println("Process end!") // 7
}
/*
输出结果:
Coroutine started!
Hello World!
After launch!
Process end!
*/
async
fun main() = runBlocking {
println("In runBlocking:${Thread.currentThread().name}")
val deferred: Deferred<String> = async {
println("In async:${Thread.currentThread().name}")
delay(1000L) // 模拟耗时操作
return@async "Task completed!"
}
println("After async:${Thread.currentThread().name}")
val result = deferred.await()
println("Result is: $result")
}
/*
输出结果:
In runBlocking:main @coroutine#1
After async:main @coroutine#1 // 注意,它比“In async”先输出
In async:main @coroutine#2
Result is: Task completed!
*/
总结
挂起函数
-
要定义挂起函数,我们只需在普通函数的基础上,增加一个 suspend 关键字。 suspend 这个关键字。
-
挂起函数,由于它拥有挂起和恢复的能力,因此对于同一行代码来说,“=”左右两边 的代码分别可以执行在不同的线程之上。
-
挂起函数的本质,就是 Callback。
Channel
// 代码段1
fun main() = runBlocking {
// 1,创建管道
val channel = Channel<Int>()
launch {
// 2,在一个单独的协程当中发送管道消息
(1..3).forEach {
channel.send(it) // 挂起函数
logX("Send: $it")
}
}
launch {
// 3,在一个单独的协程当中接收管道消息
for (i in channel) { // 挂起函数
logX("Receive: $i")
}
}
logX("end")
}
/*
================================
end
Thread:main @coroutine#1
================================
================================
Receive: 1
Thread:main @coroutine#3
================================
================================
Send: 1
Thread:main @coroutine#2
================================
================================
Send: 2
Thread:main @coroutine#2
================================
================================
Receive: 2
Thread:main @coroutine#3
================================
================================
Receive: 3
Thread:main @coroutine#3
================================
================================
Send: 3
Thread:main @coroutine#2
================================
// 4,程序不会退出
*/
Flow
Flow的基本用法
class MainViewModel : ViewModel() {
val timeFlow = flow {
var time = 0
while (true) {
emit(time)
delay(1000)
time++
}
}
}
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
}
}
}
}
}
效果
流速不均匀问题
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
1:这里修改
delay(3000)
}
}
}
}
}
效果
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
1: 修改这里
mainViewModel.timeFlow.collectLatest { time ->
textView.text = time.toString()
delay(3000)
}
}
}
}
}
效果
Flow的生命周期管理
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
Log.d("FlowTest", "Update time $time in UI.")
}
}
}
}
}
class MainViewModel : ViewModel() {
val timeFlow = flow {
var time = 0
while (true) {
emit(time)
delay(1000)
time++
}
}
}
效果
- 当我们按下Home键回到桌面后,控制台的日志依然会持续打印。
- 我们并没有很好地管理Flow的生命周期,它没有与Activity的生命周期同步。
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
1: 修改这里
lifecycleScope.launchWhenStarted {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
Log.d("FlowTest", "Update time $time in UI.")
}
}
}
}
}
- 切回后台就停止打印了。
- Flow的管道中一直会暂存着一些的旧数据,这些数据不仅可能已经失去了时效性,而且还会造成一些内存上的问题。
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
Log.d("FlowTest", "Update time $time in UI.")
}
}
}
}
}
}
StateFlow的基本用法
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
mainViewModel.startTimer()
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.stateFlow.collect {
textView.text = it.toString()
}
}
}
}
}
- StateFlow其中一个重要的价值就是它和LiveData的用法保持了高度一致性。如果你的项目之前使用的是LiveData,那么终于可以放宽了心,零成本地迁移到Flow上了。
StateFlow的高级用法
class MainViewModel : ViewModel() {
val timeFlow = flow {
var time = 0
while (true) {
emit(time)
delay(1000)
time++
}
}
}
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.timeFlow.collect { time ->
textView.text = time.toString()
}
}
}
}
}
效果
- 原来除了程序进入后台之外,手机发生横竖屏切换也会让计时器重新开始计时。
class MainViewModel : ViewModel() {
private val timeFlow = flow {
var time = 0
while (true) {
emit(time)
delay(1000)
time++
}
}
val stateFlow =
timeFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
0
)
}
- stateIn函数可以将其他的Flow转换成StateFlow。
- stateIn函数接收3个参数,其中第1个参数是作用域,传入viewModelScope即可。第3个参数是初始值,计时器的初始值传入0即可。
- 这里我们通过stateIn函数的第2个参数指定了一个5秒的超时时长,那么只要在5秒钟内横竖屏切换完成了,Flow就不会停止工作。
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
1:修改为stateFlow
mainViewModel.stateFlow.collect { time ->
textView.text = time.toString()
}
}
}
}
}
效果
SharedFlow
class MainViewModel : ViewModel() {
private val _clickCountFlow = MutableStateFlow(0)
val clickCountFlow = _clickCountFlow.asStateFlow()
fun increaseClickCount() {
_clickCountFlow.value += 1
}
}
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
mainViewModel.increaseClickCount()
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.clickCountFlow.collect { time ->
textView.text = time.toString()
}
}
}
}
}
效果
-
当手机发生横竖屏切换时,整个Activity都重新创建了,则此调用clickCountFlow的collect函数之后,并没有什么新的数据发送过来,但我们仍然能在界面上显示之前计数器的数字。
-
由此说明,StateFlow确实是粘性的。
class MainViewModel : ViewModel() {
private val _loginFlow = MutableStateFlow("")
val loginFlow = _loginFlow.asStateFlow()
fun startLogin() {
// Handle login logic here.
_loginFlow.value = "Login Success"
}
}
class MainActivity : AppCompatActivity() {
private val mainViewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
mainViewModel.startLogin()
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.loginFlow.collect {
if (it.isNotBlank()) {
Toast.makeText(this@MainActivity, it, Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
效果
-
当我们尝试去旋转一下屏幕,此时又会弹出一个Login Success的Toast,这就不对劲了。
-
而这,就是粘性所导致的问题。
class MainViewModel : ViewModel() {
private val _loginFlow = MutableSharedFlow<String>()
val loginFlow = _loginFlow.asSharedFlow()
fun startLogin() {
// Handle login logic here.
viewModelScope.launch {
_loginFlow.emit("Login Success")
}
}
}
效果
- SharedFlow无法像StateFlow那样通过给value变量赋值来发送消息,而是只能像传统Flow那样调用emit函数。而emit函数又是一个挂起函数,所以这里需要调用viewModelScope的launch函数启动一个协程,然后再发送消息。
参考
- 《朱涛 · Kotlin编程第一课》
- Kotlin Flow响应式编程,基础知识入门
- Kotlin Flow响应式编程,StateFlow和SharedFlow