AsyncTask 作为 kotlin 协程
AsyncTask as kotlin coroutine
AsyncTask 的典型用途:我想 运行 另一个线程中的任务,在该任务完成后,我想在我的 UI 线程中执行一些操作,即隐藏进度条.
任务将在TextureView.SurfaceTextureListener.onSurfaceTextureAvailable
开始,完成后我想隐藏进度条。同步执行此操作不起作用,因为它会阻塞构建 UI 的线程,使屏幕变黑,甚至不显示我想在之后隐藏的进度条。
到目前为止我用这个:
inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
// do async
return params[0]!!
}
override fun onPostExecute(result: ProgressBar?) {
super.onPostExecute(result)
result?.visibility = View.GONE
}
}
但是这些 类 非常丑陋,所以我想摆脱它们。
我想用科特林协程来做这件事。我尝试了一些变体,但其中 none 似乎有效。我最有可能怀疑工作的是这个:
runBlocking {
// do async
}
progressBar.visibility = View.GONE
但这不能正常工作。据我了解,runBlocking
不会像 AsyncTask
那样启动新线程,而这正是我需要它做的。但是使用 thread
协同程序,我看不到一种合理的方式来在完成时得到通知。另外,我也不能把progressBar.visibility = View.GONE
放在一个新的线程中,因为只有UI线程才允许进行这样的操作。
我是协程的新手,所以我不太明白我在这里遗漏了什么。
首先,您必须 运行 与 launch(context)
协程,而不是 runBlocking
:
https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
其次,要得到onPostExecute
的效果,就得用
您可以在 UI 主线程上将 ProgressBar 设置为 运行,同时使用协程异步地 运行 您的任务。
在你的 override fun onCreate() 方法中,
GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
yourTask() // your task implementation
}
可以初始化,
private var jobStart: Job? = null
In Kotlin, var declaration means the property is mutable. If you
declare it as val, it is immutable, read-only & cannot be reassigned.
在 onCreate() 方法之外,yourTask() 可以实现为一个挂起函数,它不会阻塞主调用线程。
当函数在等待结果返回时挂起时,它的 运行ning 线程被解除阻塞以供其他函数执行。
private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
jobStart = launch {
try{
// your task implementation
} catch (e: Exception) {
throw RuntimeException("To catch any exception thrown for yourTask", e)
}
}
}
对于您的进度条,您可以创建一个按钮来在单击该按钮时显示进度条。
buttonRecognize!!.setOnClickListener {
trackProgress(false)
}
在 onCreate() 之外,
private fun trackProgress(isCompleted:Boolean) {
buttonRecognize?.isEnabled = isCompleted // ?. safe call
buttonRecognize!!.isEnabled // !! non-null asserted call
if(isCompleted) {
loading_progress_bar.visibility = View.GONE
} else {
loading_progress_bar.visibility = View.VISIBLE
}
}
An additional tip is to check that your coroutine is indeed running on
another thread, eg. DefaultDispatcher-worker-1,
Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
希望这对您有所帮助。
要使用协程,您需要做一些事情:
- 实现CoroutineScope接口。
- 对 Job 和 CoroutineContext 实例的引用。
- 使用 suspend 函数修饰符在调用 运行s 代码的函数时挂起协程而不阻塞 Main Thread 后台线程。
- 使用withContext(Dispatchers.IO)函数在后台线程运行代码和launch函数启动协程。
通常我为此使用单独的 class,例如“Presenter”或“ViewModel”:
class Presenter : CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // to run code in Main(UI) Thread
// call this method to cancel a coroutine when you don't need it anymore,
// e.g. when user closes the screen
fun cancel() {
job.cancel()
}
fun execute() = launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
使用 ViewModel
代码更简洁 viewModelScope
:
class MyViewModel : ViewModel() {
fun execute() = viewModelScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
要使用 viewModelScope
,请将下一行添加到应用的 build.gradle 文件的依赖项中:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
撰写本文时final LIFECYCLE_VERSION = "2.3.0-alpha04"
.
另一种方法是在 CoroutineScope
:
上创建通用扩展函数
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: (R) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
doInBackground()
}
onPostExecute(result)
}
现在我们可以将它用于任何 CoroutineScope
:
-
class MyViewModel : ViewModel() {
fun someFun() {
viewModelScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
}
}
在Activity
或Fragment
中:
lifecycleScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
要使用 viewModelScope
或 lifecycleScope
添加下一行到应用的 build.gradle 文件的依赖项:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
撰写本文时 final LIFECYCLE_VERSION = "2.3.0-alpha05"
。
这不使用协程,但这是一个快速的解决方案,可以在后台执行任务 运行,然后在 UI 上执行某些操作。
与其他方法相比,我不确定这种方法的优缺点,但它有效并且非常容易理解:
Thread {
// do the async Stuff
runOnUIThread {
// do the UI stuff
}
// maybe do some more stuff
}.start()
使用此解决方案,您可以轻松地在两个实体之间传递值和对象。您也可以无限期地嵌套它。
以下方法或许能够满足您的需求。它需要更少的样板代码并且适用于 100% 的用例
GlobalScope.launch {
bitmap = BitmapFactory.decodeStream(url.openStream())
}.invokeOnCompletion {
createNotification()
}
private val TAG = MainActivity::class.simpleName.toString()
private var job = Job()
//coroutine Exception
val handler = CoroutineExceptionHandler { _, exception ->
Log.d(TAG, "$exception handled !")
}
//coroutine context
val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler
//coroutine scope
private val coroutineScope = CoroutineScope(coroutineContext)
fun execute() = coroutineScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String =
withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
//delay(5000) // simulate async work
loadFileFromStorage()
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
LoadingScreen.displayLoadingWithText(this,"Loading Files",false)
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
//progressDialogDialog?.dismiss()
LoadingScreen.hideLoading()
// hide progress
}
我开始将我 Android 项目中的 AsyncTask
内容迁移到使用协同程序...如果你真的需要在完成异步任务后在 UI 上做一些事情(即,您只是在 AsyncTask 中覆盖 doInBackGround 和 onPostExecute)...可以完成这样的事情(我自己尝试过并且有效):
val job = CoroutineScope(Dispatchers.IO).async {
val rc = ...
return@async rc
}
CoroutineScope(Dispatchers.Main).launch {
val job_rc = job.await() // whatever job returns is fed to job_rc
// do UI updates here
}
您的工作不需要使用 I/O 调度程序...如果不是 I/O 密集型,您可以使用默认值。
然而,等待作业完成的协程需要在 Main/UI 线程中,以便您可以更新 UI。
是的,有一些语法糖可以用来使上面的代码看起来更酷,但是当一个人刚开始迁移到使用协程时,这至少更容易掌握。
AsyncTask 的典型用途:我想 运行 另一个线程中的任务,在该任务完成后,我想在我的 UI 线程中执行一些操作,即隐藏进度条.
任务将在TextureView.SurfaceTextureListener.onSurfaceTextureAvailable
开始,完成后我想隐藏进度条。同步执行此操作不起作用,因为它会阻塞构建 UI 的线程,使屏幕变黑,甚至不显示我想在之后隐藏的进度条。
到目前为止我用这个:
inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
// do async
return params[0]!!
}
override fun onPostExecute(result: ProgressBar?) {
super.onPostExecute(result)
result?.visibility = View.GONE
}
}
但是这些 类 非常丑陋,所以我想摆脱它们。 我想用科特林协程来做这件事。我尝试了一些变体,但其中 none 似乎有效。我最有可能怀疑工作的是这个:
runBlocking {
// do async
}
progressBar.visibility = View.GONE
但这不能正常工作。据我了解,runBlocking
不会像 AsyncTask
那样启动新线程,而这正是我需要它做的。但是使用 thread
协同程序,我看不到一种合理的方式来在完成时得到通知。另外,我也不能把progressBar.visibility = View.GONE
放在一个新的线程中,因为只有UI线程才允许进行这样的操作。
我是协程的新手,所以我不太明白我在这里遗漏了什么。
首先,您必须 运行 与 launch(context)
协程,而不是 runBlocking
:
https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
其次,要得到onPostExecute
的效果,就得用
您可以在 UI 主线程上将 ProgressBar 设置为 运行,同时使用协程异步地 运行 您的任务。
在你的 override fun onCreate() 方法中,
GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
yourTask() // your task implementation
}
可以初始化,
private var jobStart: Job? = null
In Kotlin, var declaration means the property is mutable. If you declare it as val, it is immutable, read-only & cannot be reassigned.
在 onCreate() 方法之外,yourTask() 可以实现为一个挂起函数,它不会阻塞主调用线程。
当函数在等待结果返回时挂起时,它的 运行ning 线程被解除阻塞以供其他函数执行。
private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
jobStart = launch {
try{
// your task implementation
} catch (e: Exception) {
throw RuntimeException("To catch any exception thrown for yourTask", e)
}
}
}
对于您的进度条,您可以创建一个按钮来在单击该按钮时显示进度条。
buttonRecognize!!.setOnClickListener {
trackProgress(false)
}
在 onCreate() 之外,
private fun trackProgress(isCompleted:Boolean) {
buttonRecognize?.isEnabled = isCompleted // ?. safe call
buttonRecognize!!.isEnabled // !! non-null asserted call
if(isCompleted) {
loading_progress_bar.visibility = View.GONE
} else {
loading_progress_bar.visibility = View.VISIBLE
}
}
An additional tip is to check that your coroutine is indeed running on another thread, eg. DefaultDispatcher-worker-1,
Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
希望这对您有所帮助。
要使用协程,您需要做一些事情:
- 实现CoroutineScope接口。
- 对 Job 和 CoroutineContext 实例的引用。
- 使用 suspend 函数修饰符在调用 运行s 代码的函数时挂起协程而不阻塞 Main Thread 后台线程。
- 使用withContext(Dispatchers.IO)函数在后台线程运行代码和launch函数启动协程。
通常我为此使用单独的 class,例如“Presenter”或“ViewModel”:
class Presenter : CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // to run code in Main(UI) Thread
// call this method to cancel a coroutine when you don't need it anymore,
// e.g. when user closes the screen
fun cancel() {
job.cancel()
}
fun execute() = launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
使用 ViewModel
代码更简洁 viewModelScope
:
class MyViewModel : ViewModel() {
fun execute() = viewModelScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
要使用 viewModelScope
,请将下一行添加到应用的 build.gradle 文件的依赖项中:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
撰写本文时final LIFECYCLE_VERSION = "2.3.0-alpha04"
另一种方法是在 CoroutineScope
:
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: (R) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
doInBackground()
}
onPostExecute(result)
}
现在我们可以将它用于任何 CoroutineScope
:
-
class MyViewModel : ViewModel() { fun someFun() { viewModelScope.executeAsyncTask(onPreExecute = { // ... }, doInBackground = { // ... "Result" // send data to "onPostExecute" }, onPostExecute = { // ... here "it" is a data returned from "doInBackground" }) } }
在
Activity
或Fragment
中:lifecycleScope.executeAsyncTask(onPreExecute = { // ... }, doInBackground = { // ... "Result" // send data to "onPostExecute" }, onPostExecute = { // ... here "it" is a data returned from "doInBackground" })
要使用 viewModelScope
或 lifecycleScope
添加下一行到应用的 build.gradle 文件的依赖项:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
撰写本文时 final LIFECYCLE_VERSION = "2.3.0-alpha05"
。
这不使用协程,但这是一个快速的解决方案,可以在后台执行任务 运行,然后在 UI 上执行某些操作。
与其他方法相比,我不确定这种方法的优缺点,但它有效并且非常容易理解:
Thread {
// do the async Stuff
runOnUIThread {
// do the UI stuff
}
// maybe do some more stuff
}.start()
使用此解决方案,您可以轻松地在两个实体之间传递值和对象。您也可以无限期地嵌套它。
以下方法或许能够满足您的需求。它需要更少的样板代码并且适用于 100% 的用例
GlobalScope.launch {
bitmap = BitmapFactory.decodeStream(url.openStream())
}.invokeOnCompletion {
createNotification()
}
private val TAG = MainActivity::class.simpleName.toString()
private var job = Job()
//coroutine Exception
val handler = CoroutineExceptionHandler { _, exception ->
Log.d(TAG, "$exception handled !")
}
//coroutine context
val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler
//coroutine scope
private val coroutineScope = CoroutineScope(coroutineContext)
fun execute() = coroutineScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String =
withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
//delay(5000) // simulate async work
loadFileFromStorage()
return@withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
LoadingScreen.displayLoadingWithText(this,"Loading Files",false)
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
//progressDialogDialog?.dismiss()
LoadingScreen.hideLoading()
// hide progress
}
我开始将我 Android 项目中的 AsyncTask
内容迁移到使用协同程序...如果你真的需要在完成异步任务后在 UI 上做一些事情(即,您只是在 AsyncTask 中覆盖 doInBackGround 和 onPostExecute)...可以完成这样的事情(我自己尝试过并且有效):
val job = CoroutineScope(Dispatchers.IO).async {
val rc = ...
return@async rc
}
CoroutineScope(Dispatchers.Main).launch {
val job_rc = job.await() // whatever job returns is fed to job_rc
// do UI updates here
}
您的工作不需要使用 I/O 调度程序...如果不是 I/O 密集型,您可以使用默认值。
然而,等待作业完成的协程需要在 Main/UI 线程中,以便您可以更新 UI。
是的,有一些语法糖可以用来使上面的代码看起来更酷,但是当一个人刚开始迁移到使用协程时,这至少更容易掌握。