一、开启协程的方式
1、顶层开启协程的函数
基本使用:
// 方法一,使用 runBlocking 顶层函数
runBlocking {
getImage(imageId)
}
// 方法二,使用 GlobalScope 单例对象
// 👇 可以直接调用 launch 开启协程,或者aysnc需要await接收结果
GlobalScope.launch {
getImage(imageId)
}
// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象
// 👇 需要一个类型为 CoroutineContext 的参数
val coroutineScope = CoroutineScope(context)
coroutineScope.launch { //或者async,需要await接收结果
getImage(imageId)
}
-
方法一通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。
-
方法二和使用
runBlocking
的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和 app 一致,且不能取消。 -
方法三是比较推荐的使用方法,我们可以通过
context
参数去管理和控制协程的生命周期(这里的context
和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)
以上三个方法的共同特点就是,在非协程的代码中开启协程的方法。
2、切协程方法/操作符
操作符/函数 | 描述 |
---|---|
withContext | 这个函数可以在指定的上下文中执行协程代码。例如,可以使用Dispatchers.IO 来在IO线程中执行协程。 |
launch | 用于启动一个新的协程。它会返回一个Job 对象,可以用来取消协程 |
async | 与launch 类似,但它会返回一个Deferred 对象,可以通过调用await() 方法获取协程的结果 |
注意:launch 和 async 是必须在 协程里面开启 才能编译过去,在协程之外开启,编译会报错;withContext则必须在协程或者suspend修饰的方法中使用,否则编译报错。
launch和withContext的区别
在Kotlin协程中,launch
和withContext
是两个用于启动协程的函数,它们之间存在一些差异。具体分析如下:
-
launch
:launch
函数用于在协程作用域内创建一个新的协程。它不阻塞当前线程,而是将协程任务放入后台执行。launch
通常用于执行不需要返回值的任务,例如在后台执行一些操作而不需要等待结果。launch
函数的特点是它会将协程任务挂起,直到任务完成或者被取消。 -
withContext
:withContext
函数用于在特定的上下文中执行协程代码块。它可以确保代码块在指定的协程作用域内执行,并且可以使用指定的协程调度器或Job。withContext
通常用于需要切换协程作用域或调度器的场景,例如在IO密集型任务中使用Dispatchers.IO
调度器。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
// 在这里执行协程代码
}
// 等待协程执行完成
job.join()
}
fun main() = runBlocking {
val deferred = async {
// 在这里执行协程代码并返回结果
}
// 获取协程的结果
val result = deferred.await()
println("Result: $result")
}
fun main() = runBlocking {
withContext(Dispatchers.IO) {
// 在这里执行协程代码
}
}
fun test2(){
val job = GlobalScope.launch {
// 在这里执行协程代码
print(t1()+t2())
}
Thread.sleep(3000)
print("After runBlocking ${job.isActive} ")
}
suspend fun t1() = withContext(Dispatchers.IO){
delay(1000L) // 模拟耗时操作
"After delay 1 "
}
suspend fun t2() = withContext(Dispatchers.IO){
delay(2000L) // 模拟耗时操作
"After delay 2"
}
二、Android中开启协程
在Android Activity中开启协程,通常需要结合CoroutineScope
和生命周期感知组件(如lifecycleScope
)来确保协程的生命周期与Activity的生命周期自动绑定
1、使用lifecycleScope:
lifecycleScope
是一个与Activity或Fragment的生命周期绑定的特殊CoroutineScope
。当UI组件被销毁时,所有在这个范围内启动的协程都会自动取消,防止内存泄漏。
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 使用lifecycleScope启动协程
lifecycleScope.launch {
// 在这里执行协程代码
}
}
}
2、 手动管理协程作用域:
如果你想更灵活地管理协程,可以创建一个自定义的CoroutineScope
并显式地绑定到LifecycleOwner
,例如Activity
或Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
class MyActivity : AppCompatActivity() {
private lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 创建一个新的协程作用域
val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
// 绑定协程作用域到Activity生命周期
job = coroutineScope.launch {
// 在这里执行协程代码
}
}
override fun onDestroy() {
super.onDestroy()
// 取消协程
job.cancel()
}
}
3、使用ViewModel和LiveData:
如果你正在使用ViewModel
和LiveData
架构组件,可以在ViewModel
中启动协程,并通过LiveData
将结果传递给UI层。
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 初始化ViewModel
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 观察LiveData的变化
viewModel.resultLiveData.observe(this, Observer { result ->
// 更新UI界面
})
// 请求数据
viewModel.fetchData()
}
}
import androidx.lifecycle.*
import kotlinx.coroutines.*
class MyViewModel(private val repository: MyRepository) : ViewModel() {
val resultLiveData = MutableLiveData<String>()
fun fetchData() {
// 在ViewModel中启动协程
viewModelScope.launch {
// 从仓库获取数据
val result = repository.getDataFromApi()
if (result is Success) {
// 设置LiveData的值
resultLiveData.value = result.data
} else {
// 处理错误
}
}
}
}
在上述代码中,viewModelScope
是专为ViewModel
设计的CoroutineScope
,它会在ViewModel
清除时自动取消协程。这确保了即使Activity
或Fragment
被销毁,协程也不会继续运行,从而避免内存泄漏。
4、lifecycleScope.launch、GlobalScope.launch和CoroutineScope(Dispatchers.IO).launch 区别
GlobalScope.launch和CoroutineScope(Dispatchers.IO).launch在协程的生命周期、调度器选择以及使用场景上有所区别。具体分析如下:
-
生命周期:使用
GlobalScope.launch
启动的协程会持续存在直到明确取消,它与应用程序的生命周期绑定,即使离开原始作用域(如Activity或Fragment)也不会自动取消。这可能导致内存泄漏的风险,因为它不会随着UI组件的销毁而自动清理。而CoroutineScope(Dispatchers.IO).launch
创建的协程则通常用于执行后台任务,并且需要显式管理其生命周期,例如在UI组件销毁时取消协程来避免内存泄漏。 -
调度器选择:
GlobalScope.launch
默认使用Dispatchers.Default
作为调度器,这意味着它会在后台线程池中执行协程,但这并非专为IO密集型任务设计。如果要在IO操作中使用协程而不阻塞线程,应使用Dispatchers.IO
调度器。CoroutineScope(Dispatchers.IO).launch
则直接指定了协程应该在Dispatchers.IO
调度器上运行,这是专门用于IO操作的线程池,能够提供更好的性能。 -
使用场景:
GlobalScope.launch
适用于全局范围内长期存在的后台任务,但需谨慎使用以避免内存泄漏。相比之下,CoroutineScope(Dispatchers.IO).launch
更适用于临时性的、与特定任务关联的后台工作,比如网络请求或数据库操作,这些任务通常需要在用户界面相关的生命周期内完成。
总的来说,CoroutineScope(Dispatchers.IO).launch
提供了更多的控制和灵活性,尤其是在需要精细管理协程生命周期的情况下。而GlobalScope.launch
虽然使用起来简单,但可能会引起内存泄漏等问题,因此在实际开发中应当谨慎使用。
5、lifecycleScope.launch{}与 viewModelScope.launch{} 的区别
lifecycleScope.launch
和viewModelScope.launch
确实存在差异。具体分析如下:
-
lifecycleScope.launch:
lifecycleScope
是与LifecycleOwner
(如Activity
、Fragment
或任何实现了Lifecycle
的组件)的生命周期绑定的协程作用域。当LifecycleOwner
被销毁时,在这个作用域内启动的所有协程都会自动取消。这有助于避免因忘记取消协程而导致的内存泄漏问题。此外,lifecycleScope
默认在主线程中启动协程,但也提供了其他扩展方法,如launchWhenCreated
、launchWhenStarted
、launchWhenResumed
等,这些方法允许你根据组件的特定生命周期状态来启动协程。 -
viewModelScope.launch:
viewModelScope
是与ViewModel
的生命周期绑定的协程作用域。当ViewModel
清除时,这个作用域内启动的所有协程也会被自动取消。这对于需要在ViewModel
生命周期内执行的后台任务非常有用,例如网络请求或数据库操作。viewModelScope
有助于确保当ViewModel
不再需要时,相关的后台工作也会被清理,从而避免潜在的内存泄漏。需要注意的是,viewModelScope
是在AndroidX Lifecycle v2.1.0(alpha阶段)中引入的,因此API可能会发生变化。
总结来说,lifecycleScope.launch
适用于与UI组件的生命周期紧密相关的场景,而viewModelScope.launch
适用于与ViewModel
的生命周期紧密相关的场景。两者都提供了自动取消协程的机制,以帮助开发者管理协程的生命周期并防止内存泄漏。在实际开发中,选择哪种方式取决于你的后台任务需要与哪种组件的生命周期绑定。
6、launchWhenStarted创建的协程什么时机销毁
launchWhenStarted
创建的协程会在LifecycleOwner
(如Activity
或Fragment
)的生命周期状态从STARTED
变为DESTROYED
时被取消。
具体来说,launchWhenStarted
是lifecycleScope
的一个扩展方法,它会延迟协程的启动直到LifecycleOwner
进入STARTED
状态。一旦LifecycleOwner
的生命周期状态从STARTED
变为DESTROYED
,即组件被销毁时,由launchWhenStarted
创建的协程将自动被取消。
这种机制的好处在于:
-
自动管理协程:开发者不需要手动管理协程的生命周期,避免了忘记取消协程可能导致的内存泄漏问题。
-
资源利用效率:与
lifecycleScope.launch
相比,launchWhenStarted
不会在LifecycleOwner
的整个生命周期内运行协程,而是在组件开始和销毁之间,这样可以更有效地利用资源,避免不必要的后台任务执行。 -
安全性:由于协程会在组件销毁时自动取消,这增加了应用程序的稳定性和安全性。
总的来说,使用lifecycleScope.launchWhenStarted
可以帮助开发者在Android应用中更加安全和高效地执行与组件生命周期相关的后台任务。
7、MainScope介绍
-
实际项目中的使用法一
class MainActivity : AppCompatActivity() {
private var mainScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
test1()
// test2()
// test3()
// test4()
}
private fun test1() {
mainScope.launch(Dispatchers.IO) {
val text = getText()
Log.i("cyc", "IO:")
mainScope.launch(Dispatchers.Main) {
Log.i("cyc", "Main:")
tv1.text = text
}
}
}
private suspend fun getText(): String {
Thread.sleep(3000)
return "网络数据..."
}
private fun test2() {
lifecycleScope.launch(Dispatchers.IO) {
val lg = async(Dispatchers.IO) {
Log.i("cyc", "请求网络IO")
}
lg.await().run {
Log.i("cyc", "逻辑处理")
}
}
}
private fun test3() {
lifecycleScope.launch(Dispatchers.Default) {
delay(3000L)
withContext(Dispatchers.Main) {
Toast.makeText(this@MainActivity, "aa", Toast.LENGTH_SHORT).show()
}
delay(2000L)
withContext(Dispatchers.Main) {
Toast.makeText(this@MainActivity, "bb", Toast.LENGTH_SHORT).show()
}
}
}
private fun test4() {
lifecycleScope.launch(Dispatchers.Default) {
startPolling(3000L) {
Log.i("cyc", "startPolling: ")
}
}
}
private suspend fun startPolling(intervals: Long, block: () -> Unit) {
flow {
while (true) {
delay(intervals)
emit(0)
}
}
.catch { Log.e("cyc", "startPolling--catch: $it") }
.flowOn(Dispatchers.IO)//作用于flow代码块
.collect {
withContext(Dispatchers.Main) {
block.invoke()
}
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
}
-
实际项目中的使用法二
通过CoroutineScope by MainScope(),在需要的地方直接cancle
class MyCoroutineActivity : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my_coroutine)
runBlocking(Dispatchers.IO) {
launch(Dispatchers.IO) {
}
}
}
override fun onDestroy() {
/**
* 取消 Activity 里面的所有协程
* */
cancel()
super.onDestroy()
}
}
三、执行多个并行异步操作,所有异步结束后执行合并方案:
1、传统java方案:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(2);
int[] data = new int[2];
for (int i = 0; i < 2; i++) {
int finalI = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "start running");
try {
data[finalI] = finalI;
Thread.sleep(finalI * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "complete-----" + System.currentTimeMillis() + ",finalI:" + finalI);
countDownLatch.countDown();
}).start();
}
System.out.println("waiting for all thread to complete... ");
countDownLatch.await();
int sum = 0;
for (int datum : data) {
sum += datum;
}
System.out.println("When all threads are executed, the main thread continues to execute" + "++++++" + countDownLatch.getCount() + "," + System.currentTimeMillis() + "," + sum);
}
}
2、协程方案
CoroutineScope(Dispatchers.IO).launch {
// 在协程中执行异步任务
val result = async(Dispatchers.IO) { fetchDataFromApi(1) }
val result2 = async(Dispatchers.IO){ fetchDataFromApi(4) }
Log.d("TAG", "launch: ${Thread.currentThread()}")
updateUIWithResult(result.await() + result2.await())
}
private suspend fun fetchDataFromApi(time: Int = 1): String {
// 模拟网络请求的延迟
delay(1000L * time)
Log.d("TAG", "fetchDataFromApi: ${Thread.currentThread()}")
return "Hello, Coroutines!"
}