如何从协程后台线程更新 UI 线程?
How to update UI thread from coroutines background thread?
我有一个函数 displayDirectoryContents2(file: File) 可以扫描所有文件并检查文件和目录。我想要的是在 UI thread
中的文本视图中显示当前文件路径
lateinit var textView: TextView
GlobalScope.launch(Dispatchers.IO) {
displayDirectoryContents2(file)
}
函数代码
private fun displayDirectoryContents2(dir: File?){
try {
val files = dir?.listFiles()!!
files.forEach {
if (it.isDirectory) {
displayDirectoryContents2(it)
} else {
if (it.isFile) {
textView.text = it.name // to Update the file name in UI thread
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
我是 Kotlin 协程的新手。实际上我想在后台线程中 运行 函数 displayDirectoryContents2(file: File) 并更新函数在 UI 线程中读取的文件的名称,就像 AsyncTask 一样。
您可以使displayDirectoryContents2
暂停功能,然后使用withContext
切换上下文。
suspend fun displayDirectoryContents2() {
...
withContext(Dispatchers.Main) {
textView.text = it.name
}
...
}
您可以切换调度程序上下文(Dispatchers.IO 用于逻辑,然后切换到 Dispatchers.Main 用于更新 UI),或者您可以将代码移至 ViewModel 并在那里使用相同的上下文切换技术或使用 LiveData 的 postvalue()。下面是执行后者的示例。您可以在此处阅读 ViewModel:https://developer.android.com/topic/libraries/architecture/viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MyViewModel() : ViewModel() {
private val files: MutableLiveData<List<String>> by lazy {
MutableLiveData<List<String>>()
}
fun loadFiles(path: String) {
viewModelScope.launch(){
doLoadFiles()
}
}
private suspend fun doLoadFiles() {
withContext(Dispatchers.IO) {
val results = listOf("patha", "pathb")//replace with your actual code
files.postValue(results)
}
}
fun getFiles(): LiveData<List<String>> = files
}
然后从您的 activity
中这样调用它
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
model.getFiles().observe(this, Observer<List<String>>{ paths ->
// update UI
println (paths)
})
model.loadFiles("S")
}
在您的 build.gradle 文件中,确保导入相关依赖项
def lifecycle_ver = "2.2.0-rc02"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ver"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_ver"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ver"
解决这个问题的最佳方法是首先创建一个 returns 文件名的 Flow<String>
函数,该函数在 IO
调度程序上自行执行:
fun File.filenameFlow() = flow<String> { traverseAndEmit(this@filenameFlow) }
.flowOn(Dispatchers.IO)
private suspend fun FlowCollector<String>.traverseAndEmit(dir: File) {
dir.listFiles()?.forEach {
when {
it.isDirectory -> traverseAndEmit(it)
it.isFile -> emit(it.name)
}
}
}
现在您可以在 GUI 线程中的任何时候简单地 collect
它而不会阻塞:
File("target").filenameFlow().collect { textView.text = it }
使用 suspend
函数从主线程扫描文件。
suspend fun loadFiles(): List<String> = withContext(Dispatchers.IO) {
// code for getting the files
return@withContext list }
完成后,使用 activity 的 onCreate()
乐趣中的 lifecycleScope.launch{ }
更新 UI 线程。如下图
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch { // Dispatchers.Main
textView.text = loadFiles()[i]
}
}
我有一个函数 displayDirectoryContents2(file: File) 可以扫描所有文件并检查文件和目录。我想要的是在 UI thread
中的文本视图中显示当前文件路径lateinit var textView: TextView
GlobalScope.launch(Dispatchers.IO) {
displayDirectoryContents2(file)
}
函数代码
private fun displayDirectoryContents2(dir: File?){
try {
val files = dir?.listFiles()!!
files.forEach {
if (it.isDirectory) {
displayDirectoryContents2(it)
} else {
if (it.isFile) {
textView.text = it.name // to Update the file name in UI thread
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
我是 Kotlin 协程的新手。实际上我想在后台线程中 运行 函数 displayDirectoryContents2(file: File) 并更新函数在 UI 线程中读取的文件的名称,就像 AsyncTask 一样。
您可以使displayDirectoryContents2
暂停功能,然后使用withContext
切换上下文。
suspend fun displayDirectoryContents2() {
...
withContext(Dispatchers.Main) {
textView.text = it.name
}
...
}
您可以切换调度程序上下文(Dispatchers.IO 用于逻辑,然后切换到 Dispatchers.Main 用于更新 UI),或者您可以将代码移至 ViewModel 并在那里使用相同的上下文切换技术或使用 LiveData 的 postvalue()。下面是执行后者的示例。您可以在此处阅读 ViewModel:https://developer.android.com/topic/libraries/architecture/viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MyViewModel() : ViewModel() {
private val files: MutableLiveData<List<String>> by lazy {
MutableLiveData<List<String>>()
}
fun loadFiles(path: String) {
viewModelScope.launch(){
doLoadFiles()
}
}
private suspend fun doLoadFiles() {
withContext(Dispatchers.IO) {
val results = listOf("patha", "pathb")//replace with your actual code
files.postValue(results)
}
}
fun getFiles(): LiveData<List<String>> = files
}
然后从您的 activity
中这样调用它import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
model.getFiles().observe(this, Observer<List<String>>{ paths ->
// update UI
println (paths)
})
model.loadFiles("S")
}
在您的 build.gradle 文件中,确保导入相关依赖项
def lifecycle_ver = "2.2.0-rc02"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ver"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_ver"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ver"
解决这个问题的最佳方法是首先创建一个 returns 文件名的 Flow<String>
函数,该函数在 IO
调度程序上自行执行:
fun File.filenameFlow() = flow<String> { traverseAndEmit(this@filenameFlow) }
.flowOn(Dispatchers.IO)
private suspend fun FlowCollector<String>.traverseAndEmit(dir: File) {
dir.listFiles()?.forEach {
when {
it.isDirectory -> traverseAndEmit(it)
it.isFile -> emit(it.name)
}
}
}
现在您可以在 GUI 线程中的任何时候简单地 collect
它而不会阻塞:
File("target").filenameFlow().collect { textView.text = it }
使用 suspend
函数从主线程扫描文件。
suspend fun loadFiles(): List<String> = withContext(Dispatchers.IO) {
// code for getting the files
return@withContext list }
完成后,使用 activity 的 onCreate()
乐趣中的 lifecycleScope.launch{ }
更新 UI 线程。如下图
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch { // Dispatchers.Main
textView.text = loadFiles()[i]
}
}