Android入門教程 | Kotlin協程入門

Android_anzi發表於2021-12-06

Android官方推薦使用協程來處理非同步問題。

協程的特點:

  • 輕量:單個執行緒上可執行多個協程。協程支援掛起,不會使正在執行協程的執行緒阻塞。掛起比阻塞節省記憶體,且支援多個並行操作。
  • 記憶體洩漏更少:使用結構化併發機制在一個作用域內執行多項操作。
  • 內建取消支援:取消操作會自動在執行中的整個協程層次結構內傳播。
  • Jetpack整合:許多Jetpack庫都包含提供全面協程支援的擴充套件。某些庫還提供自己的協程作用域,可用於結構化併發。

示例

首先工程中需要引入 Kotlin 與協程。然後再使用協程發起網路請求。

引入: Android 工程中引入 Kotlin,參考 Android 專案使用 kotlin

有了Kt後,引入協程

1
2
3
dependencies {
     implementation  "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" // 協程
}

啟動協程

不同於 Kotlin 工程直接使用 GlobalScope,這個示例在 ViewModel中使用協程。需要使用 viewModelScope

下面的 CorVm1 繼承了 ViewModel。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() 方法,可以看到協程是在主執行緒中的。

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

由於此協程透過 viewModelScope啟動,因此在 ViewModel 的作用域內執行。如果 ViewModel 因使用者離開螢幕而被銷燬,則 viewModelScope會自動取消,且所有執行的協程也會被取消。

launch() 方法可以指定執行的執行緒。可以傳入 Dispatchers來指定執行的執行緒。

先簡單看一下 kotlinx.coroutines包裡的 Dispatchers ,它有4個屬性:

  • Default,預設
  • Main,Android中指定的是主執行緒
  • Unconfined,不指定執行緒
  • IO,指定IO執行緒

都透過點選事件來啟動

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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

1
2
3
4
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 發出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// CorVm1.kt
val info1LiveData: MutableLiveData<String> = MutableLiveData()
 
private fun reqGet() {
     info1LiveData.value =  "發起請求"
     viewModelScope.launch(Dispatchers.IO) {
         val url = URL( " " )
         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

【 】


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2846080/,如需轉載,請註明出處,否則將追究法律責任。

相關文章