使用Coroutine可以避免异步任务中的回调,首先想到的就是可以把AsyncTask给替换掉。废话少说,先看东西:
AsyncTask
用AsyncTask实现的一段逻辑:点击Button后,TextView依次显示
Start --> 1 ... 10 --> end
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
MyAsyncTask().execute()
}
}
inner class MyAsyncTask : AsyncTask<Void, Int, Void>() {
override fun onPreExecute() {
text.setText("Start")
Thread.sleep(800)
}
override fun doInBackground(vararg param: Void?): Void? {
for (i in 1..10) {
publishProgress(i)
Thread.sleep(800)
}
return null
}
override fun onProgressUpdate(vararg values: Int?) {
text.setText(values[0].toString())
}
override fun onPostExecute(result: Void?) {
text.setText("Finish")
}
override fun onCancelled() {
Log.e(localClassName, "Cancelled")
}
}
}
一个简单的异步任务要实现四个回调,实在不优雅。
Coroutine
同样的需求用Coroutine实现如下:
class MainActivity : AppCompatActivity() {
val scope = CoroutineScope(Dispatchers.Default)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
button.setOnClickListener {
scope.launch {
myTask()
}
}
}
override fun onPause() {
super.onPause()
scope.coroutineContext.cancel()
}
private suspend fun myTask() {
try {
// 相当于onPreExecute,withContext可以在切换线程的同时进行线程同步
withContext(Dispatchers.Main) {
text.text = "Start"
}
// 相当于doInBackground
Thread.sleep(800)
for (i in 1..10) {
//相当于onProgressUpdate
withContext(Dispatchers.Main) {
text.text = i.toString()
}
Thread.sleep(800)
}
// 相当于onPostExecute
withContext(Dispatchers.Main) {
text.text = "Finish"
}
} catch (e: Exception) {
// 相当于onCancelled
Log.e(localClassName, "Cancelled", e)
}
}
}
一气呵成,困扰我多年的便秘终于被治好了。
CoroutineScope
Coroutine版本的代码中Activity中多了针对CoroutineScope的处理。CoroutineScope是Coroutine运行的基础,它提供了创建并启动Coroutine的常用方法,例如我们常用的launch
、async
等,虽然是扩展方法。
每个CoroutineScope管理一个CoroutineContext,通过launch
或者async
启动协程会返回一个句柄Job存放在CoroutineContext进行管理。
CoroutineDispatcher
val scope = CoroutineScope(Dispatchers.Default)
CoroutineScope
创建时可以通过CoroutineDispatcher
指定其被派发到哪个线程执行,CoroutineDispatcher
其实也是CoroutineContext
的子类。
CoroutineDispatchers | Description |
---|---|
Dispatchers.Main | MainThread中执行 |
Dispatchers.Default | 使用CommonPool执行,共享一个后台线程池 |
Dispatchers.IO | 启动新线程执行 |
Dispatchers.Unconfined | 无指定派发线程, 有可能在任意线程派发,要根据运行时的CoroutineContext决定,因为有不确定性不推荐使用 |
Cancel
在生命周期结束时,执行了以下代码
scope.coroutineContext.cancel()
调用CoroutineContext的cancel()
后,内部所有的Job都会被结束,从而有效避免内存泄漏。这与AsyncTask的cancel类似,但是现在有了一个几种cancel的地方,不需要一个一个单独调用cancel了。
Coroutine被cancel后内部会抛出JobCancellationException
,可以通过try…catch捕捉并实现异常处理。