安卓coroutines 协程的简单使用

概念

协程既解决开启线程内存消耗大的问题,也在使用上提供销毁,何时结束等API,使用起来很方便

线程是依赖于系统的,一个系统有多个线程。

协程是依赖于线程的,一个线程有多个协程。

首先新开启的 协程 为什么能大大节省内存呢?先从原理上讲,每一个协程,就相当于一个功能块(Code Block) ,这些功能块可以指定在哪一个线程执行。如果指定在主线程执行,那就把这个功能块放在主线程里面执行,如果指定在IO线程执行,那就放在IO线程里面去执行。如果多个功能块放在IO线程里面,那就一个等待一个排队去执行,如果某个协程需要睡眠,线程就会跳过该协程去执行下一个协程,直到该协程唤醒再执行。所以,协程在创建时,并没有新建起新的线程,它只是在共享线程池中取出。所以在内存消耗上很小。

开启一个线程消耗内存远大于开启一个协程。

协程的使用

配置依赖

dependencies {
    // 依赖协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
    // 依赖当前平台所对应的平台库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
}

第一种: 阻塞型 runBlocking(不推荐)

这是一种不推荐的开启协程的方式,因为这种会阻塞线程。启动一个新的协程,并阻塞它的调用线程,直到里面的代码执行完毕。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        val startTime = System.currentTimeMillis()
        Log.d(TAG, "开始获取网络数据...time: $startTime")
        runBlocking {
            val text = getText()
            Log.d(TAG, "网络数据...$text   time: ${System.currentTimeMillis() - startTime}  currentThread:${Thread.currentThread().name}")
        }
        Log.d(TAG, "执行完毕... time: ${System.currentTimeMillis() - startTime}")
    }
 
 
    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

第二种:launch 和 async(不推荐)

注意:launch 和 async 是必须在 协程里面开启 才能编译过去,在协程之外开启,编译会报错

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            launch {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                val text = getText()
            }
        }
    }

runBlocking 开启了一个协程,在该协程内部 又用 launch 开启了一个协程,由于launch 出来的协程并没有指定在哪个线程执行,它会默认在跟随启动它的那个协程。所以runBlocking 在主线程,launch 出来的协程也在主线程。

launch 是可以指定线程:

  • Dispatchers.Default 默认线程
  • Dispatchers.IO IO线程,网络请求,IO流读写
  • Dispatchers.Main 主线程
  • Dispatchers.Unconfined 不指定,默认当前线程

async 和 launch 和差不多,就是比 launch多了一个返回值。返回值 写在闭包 {}最后一行即可,然后通过 await()获取结果

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        runBlocking {
            Log.d(TAG, "runBlocking...  currentThread:${Thread.currentThread().name}")
            val job = async(Dispatchers.IO) {
                Log.d(TAG, "launch...  currentThread:${Thread.currentThread().name}")
                23
            }
            val await = job.await()
            Log.d(TAG, "async... 运行结果:$await")
        }
    }
### 第三种:GlobalScope.launch 和 GlobalScope.async 全局单例(不推荐) 这种开启协程的方式,不会阻塞线程
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        val startTime = System.currentTimeMillis()
        Log.d(TAG, "开始获取网络数据...time: $startTime")
        GlobalScope.launch {
            val name = Thread.currentThread().name
            val text = getText()
            Log.d(TAG, "网络数据...$text   time: ${System.currentTimeMillis() - startTime}  currentThread: $name")
        }
        Log.d(TAG, "执行完毕... time: ${System.currentTimeMillis() - startTime} currentThread:${Thread.currentThread().name}")
    }
 
    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

从日志分析得:通过 GlobalScope 开启单例协程,并没有阻塞线程,但是GlobalScope 开启单例协程相比于runBlocking ,只是没有阻塞线程,并没有多大优势,因为它是全局的单例,跟随application生命周期,不能取消。

GlobalScope.async 相比于 GlobalScope.launch 就是多了一个返回值功能,比如:

runBlocking {
    val job = GlobalScope.async {
        Log.d(TAG, "async...  Thread:${Thread.currentThread().name}")
        1212
    }
    val await = job.await()
    Log.d(TAG, "async... 运行结果:$await")
}
最后,对比GlobalScope.launch 和 launch 的却别,前者开启的是一个全局协程,生命周期和Application一样,后者会随着外部协程的销毁而销毁。
/**
 * 外部协程 1
 * */
runBlocking(Dispatchers.IO) {
    /**
     * 外部协程 2
     * */
    val job = launch {

        /**
         * 内部协程 1   GlobalScope.launch
         * */
        GlobalScope.launch {
            Log.d(TAG, "GlobalScope.launch...  Thread:${Thread.currentThread().name}")
            delay(1500)
            val text = getText()
            Log.d(TAG, "async...  获取结果:${text}")
        }

        /**
         * 内部协程 1   launch
         * */
        launch {
            Log.d(TAG, "launch...  Thread:${Thread.currentThread().name}")
            delay(1500)
            val text = getText()
            Log.d(TAG, "launch...  获取结果:${text}")
        }

    }
    delay(1000)
    job.cancel()
    Log.d(TAG, "外部协程取消...")
    delay(2000)
}

最开始,GlobalScope.launch 和 launch 都有执行,但是外部外部协程取消后,GlobalScope.launch 没有受外部协程影响,依然执行完协程功能。 launch 随着外部协程一同销毁了。

第四种:CoroutineScope (推荐)

    private fun testScope() {
        val coroutineScope = CoroutineScope(Dispatchers.Main)
        coroutineScope.launch(Dispatchers.IO) {
            val text = getText()
            coroutineScope.launch(Dispatchers.Main) {
                tv1.text = text
            }
        }
    }
 
    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

这一种推荐的。由CoroutineScope 创建, 由 Dispatchers 指定在(Dispatchers.Main )主线程。

第五种:lifecycleScope(最推荐)

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'

依赖在Activty,Fragment,ViewModel层面提供协程实例,协程与其声明周期绑定,无须手动创建和取消销毁

Activity中:

class CoroutinesActivity : AppCompatActivity() {
 
 
    companion object {
        fun launch(context: Context) {
            context.startActivity<CoroutinesActivity>()
        }
 
        const val TAG = "CoroutinesTag"
    }
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines)
        val beginTransaction = supportFragmentManager.beginTransaction()
        beginTransaction.add(R.id.container, CoroutinesFragment()).commit()
        lifecycleScope.launch(Dispatchers.IO) {
            Log.i("MyTest", "CoroutinesActivity ${Thread.currentThread().name}")
        }
    }
 
}

Fragment中:

class CoroutinesFragment : Fragment() {
 
    private lateinit var coroutinesVM: CoroutinesVM
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }
 
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.coroutines_fragment_layout, container, false)
    }
 
    override fun onResume() {
        super.onResume()
        coroutinesVM = ViewModelProvider(this, defaultViewModelProviderFactory).get(CoroutinesVM::class.java)
        coroutinesVM.launchCoroutines()
        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
            Log.i("MyTest", "CoroutinesFragment ${Thread.currentThread().name}")
        }
    }
}

ViewModel中:

class CoroutinesVM : ViewModel() {
 
    fun launchCoroutines() {
        viewModelScope.launch(Dispatchers.Default) {
            Log.i("MyTest", "CoroutinesVM ${Thread.currentThread().name}")
        }
    }
}

下面我给三个协程加入10秒延时,然后进入页面后立马关闭(即延时还没结束),看下是否会打印日志:

lifecycleScope.launch(Dispatchers.IO) {
            delay(10 * 1000)
            Log.i("MyTest", "CoroutinesActivity ${Thread.currentThread().name}")
        }
 
 
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
            delay(10 * 1000)
            Log.i("MyTest", "CoroutinesFragment ${Thread.currentThread().name}")
        }
 
fun launchCoroutines() {
        viewModelScope.launch(Dispatchers.Default) {
            delay(10 * 1000)
            Log.i("MyTest", "CoroutinesVM ${Thread.currentThread().name}")
        }
    }

运行结果是没有任何日志打印。

说明了并不需要自己手动取消协程,开的协程会和Activty,Fragment,ViewModel生命周期绑定的。

实际项目中的使用

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()

    }
}

suspend 关键词

    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

suspend 关键词 修饰的方法,只能在协程里面调用,因为suspend 修饰的方法,被调用后,就代表这个协程被挂起。挂起的意思,就是协程里面的代码会切换到指定的线程去执行。等执行完后切回到当前线程继续执行。但是协程之外的代码,会继续执行,所以不会阻塞到当前线程。

协程的异常捕抓

    private fun testScope() {
        val coroutineScope = CoroutineScope(Dispatchers.Main + defaultExceptionHandler)
        coroutineScope.launch(Dispatchers.IO) {
            val text = getText()
            coroutineScope.launch(Dispatchers.Main) {
                tv1.text = text
            }
        }
    }
 
    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }
 
    private val defaultExceptionHandler = object : CoroutineExceptionHandler {
        override val key: CoroutineContext.Key<*> = CoroutineExceptionHandler
 
        override fun handleException(context: CoroutineContext, exception: Throwable) {
        	//捕获异常 do something
        }
    
   }

协程的取消

方式一:CoroutineScope.cancel()

    private fun testScope() {
        val coroutineScope = CoroutineScope(Dispatchers.Main)
        coroutineScope.launch(Dispatchers.IO) {
            val text = getText()
            coroutineScope.launch(Dispatchers.Main) {
                tv1.text = text
            }
        }
        coroutineScope.cancel()
    }
 
    private suspend fun getText(): String {
        Thread.sleep(3000)
        return "网络数据..."
    }

方式二:绑定类的生命周期 CoroutineScope by MainScope()

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()
    }
}

CoroutineScope by MainScope() 实现了该接口的Activity,onDestory()销毁时 调用 cancel()即可取消在 该Activity创建的协程。

方式三:自定义 MainScope()

class MyCoroutineActivity : AppCompatActivity() {
 
    private var mainScope = MainScope()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_coroutine)
        mainScope.launch {
        }
        mainScope.async {
            "返回的数据"
        }
    }
 
    override fun onDestroy() {
        /**
         *      取消 MainScope 创建所有协程
         * */
        mainScope.cancel()
        super.onDestroy()
    }
}

自定义 MainScope() ,onDestory()销毁时 调用 MainScope().cancel() 即可取消协程。

协程同步多个任务

 private fun test() {
        lifecycleScope.launch {
            val startTime = System.currentTimeMillis()
            Log.d(TAG, "2个异步任务开始:${startTime}")
            val mergerData = withContext(Dispatchers.IO) {
                //任务1,通过 async 必包实现
                val data1 = async {
                    getData1()
                }
                //任务2,通过 async 必包实现
                val data2 = async {
                    getData2()
                }
                // 最后一行,就是返回的数据,类型可以随便定义
                // 通过await(),获取各自任务的结果,只有等两个异步任务都结束,才会返回数据
                "合并两个数据: ${data1.await()}, ${data2.await()}"
            }
            val endTime = System.currentTimeMillis()
            Log.d(TAG,"2个异步任务结束,耗时:${endTime - startTime},结果:${mergerData}")
        }
    }
 
    private suspend fun getData1(): String {
        delay(5000)
        Log.d(TAG, "返回Data1")
        return "Data1"
    }
 
    private suspend fun getData2(): String {
        delay(10000)
        Log.d(TAG, "返回Data2")
        return "Data2"
    }
两个异步任务是并行执行的,总耗时10s

最后一行代码,就是两个异步任务返回的结果,类型随便定义。

任务获取结果,需要await()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值