0

Android入门教程 | Kotlin协程入门

 2 years ago
source link: https://segmentfault.com/a/1190000040952888
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android入门教程 | Kotlin协程入门

发布于 今天 12:42

Android官方推荐使用协程来处理异步问题。

协程的特点:

  • 轻量:单个线程上可运行多个协程。协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack集成:许多Jetpack库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可用于结构化并发。
示例

首先工程中需要引入 Kotlin 与协程。然后再使用协程发起网络请求。

引入:
Android 工程中引入 Kotlin,参考 Android 项目使用 kotlin

有了Kt后,引入协程

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" // 协程
}

启动协程

不同于 Kotlin 工程直接使用 GlobalScope,这个示例在ViewModel中使用协程。需要使用viewModelScope

下面的 CorVm1 继承了 ViewModel。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope // 引入
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class CorVm1 : ViewModel() {

    companion object {
        const val TAG = "rfDevCorVm1"
    }

    fun cor1() {
        viewModelScope.launch { Log.d(TAG, "不指定dispatcher ${Thread.currentThread()}") }
    }
}

在按钮的点击监听器中调用 cor1() 方法,可以看到协程是在主线程中的。

不指定dispatcher Thread[main,5,main]

由于此协程通过viewModelScope启动,因此在 ViewModel 的作用域内执行。如果 ViewModel 因用户离开屏幕而被销毁,则viewModelScope会自动取消,且所有运行的协程也会被取消。

launch() 方法可以指定运行的线程。可以传入Dispatchers来指定运行的线程。

先简单看一下kotlinx.coroutines包里的 Dispatchers ,它有4个属性:

  • Default,默认
  • Main,Android中指定的是主线程
  • Unconfined,不指定线程
  • IO,指定IO线程

都通过点击事件来启动

// CorVm1.kt

fun ioCor() {
    viewModelScope.launch(Dispatchers.IO) {
        Log.d(TAG, "IO 协程 ${Thread.currentThread()}")
    }
}

fun defaultCor() {
    viewModelScope.launch(Dispatchers.Default) {
        Log.d(TAG, "Default 协程 ${Thread.currentThread()}")
    }
}

fun mainCor() {
    viewModelScope.launch(Dispatchers.Main) { Log.d(TAG, "Main 协程 ${Thread.currentThread()}") }
}

fun unconfinedCor() {
    viewModelScope.launch(Dispatchers.Unconfined) {
        Log.d(TAG, "Unconfined 协程 ${Thread.currentThread()}")
    }
}

运行log

IO 协程 Thread[DefaultDispatcher-worker-1,5,main]
Main 协程 Thread[main,5,main]
Default 协程 Thread[DefaultDispatcher-worker-1,5,main]
Unconfined 协程 Thread[main,5,main]

从上面的比较可以看出,如果想利用后台线程,可以考虑Dispatchers.IODefault用的也是DefaultDispatcher-worker-1线程。

模拟网络请求

主线程中不能进行网络请求,我们把请求放到为IO操作预留的线程上执行。一些信息用 MutableLiveData 发出去。

// CorVm1.kt
val info1LiveData: MutableLiveData<String> = MutableLiveData()

private fun reqGet() {
    info1LiveData.value = "发起请求"
    viewModelScope.launch(Dispatchers.IO) {
        val url = URL("https://www.baidu.com/s?wd=abc")
        try {
            val conn = url.openConnection() as HttpURLConnection
            conn.requestMethod = "GET"
            conn.connectTimeout = 10 * 1000
            conn.setRequestProperty("Cache-Control", "max-age=0")
            conn.doOutput = true
            val code = conn.responseCode
            if (code == 200) {
                val baos = ByteArrayOutputStream()
                val inputStream: InputStream = conn.inputStream
                val inputS = ByteArray(1024)
                var len: Int
                while (inputStream.read(inputS).also { len = it } > -1) {
                    baos.write(inputS, 0, len)
                }
                val content = String(baos.toByteArray())
                baos.close()
                inputStream.close()
                conn.disconnect()
                info1LiveData.postValue(content)
                Log.d(TAG, "net1: $content")
            } else {
                info1LiveData.postValue("网络请求出错 $conn")
                Log.e(TAG, "net1: 网络请求出错 $conn")
            }
        } catch (e: Exception) {
            Log.e(TAG, "reqGet: ", e)
        }
    }
}

看一下这个网络请求的流程

  1. 从主线程调用reqGet()函数
  2. viewModelScope.launch(Dispatchers.IO)在协程上发出网络请求
  3. 在协程中进行网络操作。把结果发送出去。

kotlin 协程相关知识点

1. 协程基础

  • 你的第一个协程程序
  • 桥接阻塞与非阻塞的世界
  • 等待一个任务
  • 结构化的并发
  • 作用域构建器
  • 提取函数重构
  • ......

2. 取消与超时

  • 取消协程的执行
  • 取消是协作的
  • 使计算代码可取消
  • 在 finally 中释放资源
  • 运行不能取消的代码块

3. 通道

  • 关闭与迭代通道
  • 构建通道生产者
  • 使用管道的素数
  • 带缓冲的通道
  • 通道是公平的
  • 计时器通道

4. 组合挂起函数

  • 默认顺序调用
  • 使用 async 并发
  • 惰性启动的 async
  • async 风格的函数
  • 使用 async 的结构化并发

5. 协程上下文与调度器

  • 调度器与线程
  • 非受限调度器 vs 受限调度器
  • 调试协程与线程
  • 在不同线程间跳转
  • 上下文中的任务
  • 父协程的职责
  • 命名协程以用于调试
  • 组合上下文中的元素
  • 通过显式任务取消
  • 线程局部数据

6. 异常处理

  • 异常的传播
  • CoroutineExceptionHandler
  • 取消与异常

7. select 表达式

  • 在通道中 select
  • 通道关闭时 select
  • Select 以发送
  • Select 延迟值
  • 在延迟值通道上切换

8. 共享的可变状态与并发

  • volatile 无济于事
  • 线程安全的数据结构
  • 以细粒度限制线程
  • 以粗粒度限制线程
  • Actors

Kotlin入门到精通全系列视频参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK