Android Kotlin協程入門

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. 在協程中進行網絡操作。把結果發送出去。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章