AsyncTask 作为 kotlin 协程
Posted
技术标签:
【中文标题】AsyncTask 作为 kotlin 协程【英文标题】:AsyncTask as kotlin coroutine 【发布时间】:2019-08-08 01:50:23 【问题描述】: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
但是这些课程非常丑陋,所以我想摆脱它们。 我想用 kotlin 协程来做到这一点。我尝试了一些变体,但它们似乎都不起作用。我最有可能怀疑的工作是这样的:
runBlocking
// do async
progressBar.visibility = View.GONE
但这不能正常工作。据我了解,runBlocking
不会像AsyncTask
那样启动新线程,这就是我需要它做的事情。但是使用thread
协程,我看不到一种合理的方式来在它完成时得到通知。另外,我也不能把progressBar.visibility = View.GONE
放到一个新线程中,因为只有UI线程可以做这样的操作。
我是协程的新手,所以我不太明白我在这里缺少什么。
【问题讨论】:
Benjamin Basmaci,您仍然认为接受的答案是正确的吗?也许您会重新考虑您的选择,因为这个问题似乎很受欢迎,而寻求答案的用户可能会感到困惑。 @Sergey 非常感谢您的回答和您的 cmets。我相信你帮助了很多人,它显然显示在赞成票中。我更改了接受的答案,但我没有选择您的任何一个答案。原因是,我特别想要一个苗条、快速的解决方案,它不涉及为我拥有的每个用例编写新类。出于这个原因,我做出了自己的回答并接受了这个答案,因为这就是我目前正在做的事情。不确定这两种方法的优缺点,但这就是我正在寻找的这个问题。 【参考方案1】:要使用协程,您需要做以下几件事:
实现CoroutineScope接口。 对 Job 和 CoroutineContext 实例的引用。 在调用在后台线程中运行代码的函数时,使用 suspend 函数修饰符挂起协程而不阻塞主线程。 使用 withContext(Dispatchers.IO) 函数在后台线程中运行代码并使用 launch 函数启动协程。通常我为此使用一个单独的类,例如“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"
【讨论】:
【参考方案2】:另一种方法是在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
一起使用:
在ViewModel
:
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"
。
【讨论】:
保持熟悉度的最佳解决方案。你可以为此添加一个java版本吗?我试图从 kotlin 反编译它,但生成的代码看起来很垃圾。 @chitgoks 这个实现是基于 Coroutines 的,Java 没有 Coroutines,所以在 Java 中很难做到。 我爱你!我想我只有在阅读了这篇 sn-p 之后才真正理解了协程【参考方案3】:您可以让 ProgressBar 在 UI 主线程上运行,同时使用协程异步运行您的任务。
在你的重写 fun onCreate() 方法中,
GlobalScope.launch(Dispatchers.Main) // Coroutine Dispatcher confined to Main UI Thread
yourTask() // your task implementation
你可以初始化,
private var jobStart: Job? = null
在 Kotlin 中,var 声明意味着属性是可变的。如果你 将其声明为 val,它是不可变的、只读的且不能重新分配。
在 onCreate() 方法之外,yourTask() 可以实现为一个挂起函数,它不会阻塞主调用者线程。
当函数在等待返回结果的过程中被挂起时,它正在运行的线程被解除阻塞,以供其他函数执行。
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
另一个提示是检查您的协程是否确实在运行 另一个线程,例如。 DefaultDispatcher-worker-1,
Log.e("yourTask", "Running on thread $Thread.currentThread().name")
希望这有帮助。
【讨论】:
【参考方案4】:首先,您必须使用launch(context)
运行协程,而不是使用runBlocking
:
https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
其次,要得到onPostExecute
的效果,必须使用
Activity.runOnUiThread(Runnable) 或View.post(Runnable)。
【讨论】:
没有必要使用Activity.runOnUiThread(Runnable) or View.post(Runnable).
。使用适当的Dispatchers
,您可以在 UI 线程中调用函数。看我的回答***.com/a/58900195/1731626【参考方案5】:
这不使用协程,但它是让任务在后台运行并在之后在 UI 上执行某些操作的快速解决方案。
我不确定这种方法与其他方法相比的优缺点,但它很有效并且非常容易理解:
Thread
// do the async Stuff
runOnUIThread
// do the UI stuff
// maybe do some more stuff
.start()
使用此解决方案,您可以轻松地在两个实体之间传递值和对象。你也可以无限嵌套。
【讨论】:
这不是一个好的解决方案。你可能有后台任务执行很长时间,同时你的活动可能会被破坏,调用runOnUiThread
方法时可能会出现异常。顺便说一句,这个解决方案没有使用协程。
@Sergey 虽然我不同意这种方法的风险推理,但我回顾了问题和答案。虽然问题是针对像我这样的解决方案,但现在就是这样写的。原因是我当时缺乏对 kotlin、android 和协程的理解。这种缺乏经验导致我专门要求协程。正如你所说,这个问题很受欢迎,我的解决方案不涉及协程。我最初的意图和问题的确切措辞非常不同。然而,措辞才是最重要的。所以我将重新分配接受的解决方案。谢谢!【参考方案6】:
以下方法可能能够满足您的需求。它需要更少的样板代码,并且适用于 100% 的用例
GlobalScope.launch
bitmap = BitmapFactory.decodeStream(url.openStream())
.invokeOnCompletion
createNotification()
【讨论】:
不建议使用GlobalScope
。来自文档:“...在 GlobalScope 的实例上使用异步或启动是非常不鼓励的。”以上是关于AsyncTask 作为 kotlin 协程的主要内容,如果未能解决你的问题,请参考以下文章