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.IO
。Default
用的也是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)
}
}
}
看一下這個網路請求的流程
- 從主執行緒呼叫
reqGet()
函式 viewModelScope.launch(Dispatchers.IO)
在協程上發出網路請求- 在協程中進行網路操作。把結果傳送出去。