拥有协程的编程语言已经有不少了,它们各自对协程的概念都有不一样的定义,可是,为了更好的理解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开发的过程当中,对于线程的需求基本上只有两个: