擁有協程的編程語言已經有不少了,它們各自對協程的概念都有不一樣的定義,可是,爲了更好的理解Kotlin的協程,請不要摻雜任何其它語言的協程概念,讓咱們從線程提及,相信看完幾個示例事後,沒有接觸過協程的Android開發者也能掌握到協程的基本使用方法。java
咱們用一段代碼來演示一下咱們是怎樣使用線程的:android
class ThreadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
recyclerView.addItemDecoration(SpaceItemDecoration(24))
//建立一個線程並啓動
Thread {
try {
//發起HTTP請求
val weChatAuthorsJson = getWeChatAuthorsJson()
//反序列化
val list = weChatAuthorListDeserialization(weChatAuthorsJson)
//切換到UiThread
runOnUiThread {
recyclerView.adapter = WeChatAuthorAdapter(list)
}
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
private fun getWeChatAuthorsJson(): String {
val request = Request.Builder()
.url("https://wanandroid.com/wxarticle/chapters/json")
.build()
val call = OkHttpClient().newCall(request)
val response = call.execute()
return response.body!!.string()
}
private fun weChatAuthorListDeserialization(json: String): List<WeChatAuthor> {
val typeToken = object : TypeToken<ApiResponse<List<WeChatAuthor>>>() {}
val apiResponse = Gson()
.fromJson<ApiResponse<List<WeChatAuthor>>>(json, typeToken.type)
return apiResponse.data
}
}
複製代碼
咱們建立了一個線程,先請求網絡獲取了一段JSON,而後使用Gson把JSON反序列化爲了一個List,而後在子線程中經過runOnUiThread方法切換到了主線程中,爲列表裝配了適配器後,RecyclerView中呈現了數據。git
runOnUiThread方法的使用雖然方便,可是卻帶來了一個問題。咱們能夠在runOnUiThread以前加上Thread.sleep(5000)這行代碼,讓子線程暫停5秒,再在runOnUiThread裏面加一行輸出日誌的代碼:github
Thread {
try {
//發起HTTP請求
val weChatAuthorsJson = getWeChatAuthorsJson()
//反序列化
val list = weChatAuthorListDeserialization(weChatAuthorsJson)
//讓線程暫停5秒
log("準備暫停線程")
Thread.sleep(5000)
//切換到UiThread
runOnUiThread {
log("準備顯示列表")
recyclerView.adapter = WeChatAuthorAdapter(list)
}
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
複製代碼
而後運行程序,咱們在列表顯示以前按返回鍵,再觀察Logcat:編程
2019-10-31 19:35:47.947 21592-21693/com.numeron.coroutine D/ThreadActivity: Thread:Thread-3 準備暫停線程
2019-10-31 19:35:52.965 21592-21592/com.numeron.coroutine D/ThreadActivity: Thread:main 準備顯示列表
複製代碼
能夠看到:即便是咱們已經按下了返回鍵退出了Activity,可是runOnUiThread裏面的代碼依然執行了,看上去好像沒有問題,可是實際上,這會帶來空指針以及內存泄漏的風險。 若是咱們使用Kotlin協程實現以上相同的功能的話,由於Kotlin協程是能夠被取消的,因此咱們也能夠避免這個狀況的出現。 json
首先,咱們須要添加依賴:api
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'
複製代碼
而後,新建一個Activity,編寫如下代碼:bash
class CoroutineActivity : AppCompatActivity() {
private lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
recyclerView.addItemDecoration(SpaceItemDecoration(24))
//在IO調度器上啓動一個協程
job = GlobalScope.launch(Dispatchers.IO) {
try {
//發起HTTP請求獲取json
val weChatAuthorsJson = getWeChatAuthorsJson()
//反序列化
val list = weChatAuthorListDeserialization(weChatAuthorsJson)
//讓協程暫停5秒
log("準備暫停協程")
delay(5000)
//切換到UiThread顯示列表
withContext(Dispatchers.Main) {
log("準備顯示列表")
recyclerView.adapter = WeChatAuthorAdapter(list)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
//在協程結束時,打印一行日誌,並輸出錯誤信息,若是有錯誤的話
job.invokeOnCompletion {
log("協程執行結束", it)
}
}
override fun onDestroy() {
job.cancel("Activity onDestroy.")
super.onDestroy()
}
private fun log(msg: String, e: Throwable? = null) {
Log.d("CoroutineActivity", "Thread:${Thread.currentThread().name}\t" + msg, e)
}
private suspend fun getWeChatAuthorsJson(): String {
return suspendCancellableCoroutine {
val request = Request.Builder()
.url("https://wanandroid.com/wxarticle/chapters/json")
.build()
val call = OkHttpClient().newCall(request)
//當協程取消時,取消請求
it.invokeOnCancellation {
call.cancel()
}
val response = call.execute()
val responseBody = response.body
if (responseBody == null) {
it.resumeWithException(NullPointerException())
} else {
it.resume(responseBody.string())
}
}
}
private suspend fun weChatAuthorListDeserialization(json: String): List<WeChatAuthor> {
return suspendCoroutine {
val typeToken = object : TypeToken<ApiResponse<List<WeChatAuthor>>>() {}
try {
val apiResponse =
Gson().fromJson<ApiResponse<List<WeChatAuthor>>>(json, typeToken.type)
it.resume(apiResponse.data)
} catch (e: Exception) {
it.resumeWithException(e)
}
}
}
}
複製代碼
咱們經過GlobalScope.launch方法啓動了一個協程,指定它在IO調度器上運行,一樣的,咱們先是發送請求獲取JSON,而後反序列化爲List,再經過withContext切換到主線程中,爲RecyclerView裝配適配器,GlobalScope.launch方法會返回一個Job對象,咱們將它保存起來,並重寫Activity的onDestroy方法,在onDestroy方法中添加一行:網絡
job.cancel()
複製代碼
用於在Activity銷燬時,取消協程的運行。咱們把程序跑進來,並在顯示列表以前,按下返回鍵,觀察Logcat:編程語言
2019-10-31 20:31:25.882 32011-32055/com.numeron.coroutine D/CoroutineActivity: Thread:DefaultDispatcher-worker-1 準備暫停協程
...
2019-10-31 20:31:27.160 32011-32055/com.numeron.coroutine D/CoroutineActivity: Thread:DefaultDispatcher-worker-1 協程執行結束
java.util.concurrent.CancellationException: Activity onDestroy.
...
複製代碼
咱們在Logcat中找不到「準備顯示列表」的日誌記錄,而且出現了「協程執行結束」的日誌記錄,這就說明了:在咱們調用了job.cancel()以後,協程沒有再繼續運行下去了。
可是仔細看過getWeChatAuthorsJson()方法和weChatAuthorListDeserialization()方法後,發現了幾個不太明白的地方:
1.方法上多了一個suspend關鍵字,它是幹嗎的?
2.suspendCoroutine和suspendCancellableCoroutine又是幹嗎用的?
關於這兩個問題,涉及到了suspend的特性:
什麼狀況下應該用suspend關鍵字來修飾方法呢?
回到代碼中來,雖然咱們解決了內存泄漏的問題,可是還有另外一個問題:咱們把job保存爲全局變量,若是要同時執行多個協程,那不是要建立多個job變量?這太不優雅了!是的,因此官方已經爲咱們提供了一個推薦的寫法。
咱們讓Activity實現CoroutineScope接口,而後經過Kotlin的by關鍵字,把它代理給MainScope():
class CoroutineActivity : AppCompatActivity(), CoroutineScope by MainScope()
複製代碼
接下來,咱們把全局成員job刪除,之後也再也不使用GlobalScope來啓用Kotlin協程:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
recyclerView.addItemDecoration(SpaceItemDecoration(24))
//在IO調度器上啓動一個協程
launch(Dispatchers.IO) {
try {
//發起HTTP請求獲取json
val weChatAuthorsJson = getWeChatAuthorsJson()
//反序列化
val list = weChatAuthorListDeserialization(weChatAuthorsJson)
//讓協程暫停5秒,把delay換成Thread.sleep也是同樣的效果
log("準備暫停協程")
delay(5000)
//切換到UiThread顯示列表
withContext(Dispatchers.Main) {
log("準備顯示列表")
recyclerView.adapter = WeChatAuthorAdapter(list)
}
} catch (e: Exception) {
e.printStackTrace()
}
}.invokeOnCompletion { //在協程結束時,打印一行日誌,並輸出錯誤信息,若是有錯誤的話
log("協程執行結束", it)
}
}
複製代碼
最後,修改onDestroy中的代碼:
override fun onDestroy() {
cancel("Activity onDestroy.")
super.onDestroy()
}
複製代碼
以上,就是按照官方推薦的寫法修改後的實現了,無論在Activity中經過launch方法啓動了多少個Kotlin協程,只要onDestroy方法運行了,全部正在運行的Kotlin協程都會被取消掉。
總的來講,在Android開發的過程當中,對於線程的需求基本上只有兩個: