我在onCreateView
中有一个初始化块,其中一些变量是从SharedPreferences,数据库或网络(当前是从SharedPreferences)分配的。
我想用onViewCreated
中的这些值更新视图。但是它们会在onCreateView
中的协程完成之前以空值更新。如何等待协程在主线程中完成?
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
GlobalScope.launch(Dispatchers.Main) {
val job = GlobalScope.launch(Dispatchers.IO) {
val task = async(Dispatchers.IO) {
settingsInteractor.getStationSearchCountry().let {
countryName = it.name
}
settingsInteractor.getStationSearchRegion().let {
regionName = it.name
}
}
task.await()
}
job.join()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
country.updateCaption(countryName)
region.updateCaption(regionName)
}
更新(20-05-2019)
目前,我不使用onViewCreated
。我用onCreate
和onCreateView
编写代码。在onCreateView
中,我以这种方式访问视图:view.country.text = "abc"
,依此类推。
答案 0 :(得分:3)
最好使用async
而不是launch
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html。
创建lateinit val dataLoad: Deferred<Pair<String, String>
。在dataLoad = GlobalScope.async(Dispatchers.Defailt) {}
或更早的版本中将其初始化为onCreateView
。
launch
中的UI范围中的onViewCreated
个新协程。在协程val data = dataLoad.await()
中等待结果,然后将数据应用于ui
答案 1 :(得分:2)
在您的情况下,您不需要将GlobalScope
用作协程上下文(可以,但是根据文档,不建议使用)。您需要一个本地范围:
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
@Override
public void onDestroy() {
super.onDestroy();
job.cancel()
}
您的片段还应实现CoroutineScope
并在Android中使用Dispatchers.Main
来向应用程序的build.gradle添加依赖项:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
等待协程完成的代码:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
launch {
val operation = async(Dispatchers.IO) {
settingsInteractor.getStationSearchCountry().let {
countryName = it.name
}
settingsInteractor.getStationSearchRegion().let {
regionName = it.name
}
}
operation.await() // wait for result of I/O operation without blocking the main thread
// update views
country.updateCaption(countryName)
region.updateCaption(regionName)
}
}
答案 2 :(得分:1)
一种实现方法是在主线程中使用启动
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
GlobalScope.launch(Dispatchers.IO) {
settingsInteractor.getStationSearchCountry().let {
countryName = it.name
}
settingsInteractor.getStationSearchRegion().let {
regionName = it.name
}
launch(Dispatchers.Main) {
country.updateCaption(countryName)
region.updateCaption(regionName)
}
}
}
答案 3 :(得分:-1)
使用runBlocking。在这种情况下,您可以阻止UI线程,直到协程完成。当然,您要确保它足够快(少于3秒)以不提高ANR。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
val (countryName, regionName) = runBlocking {
// Switch to background thread and continue blocking UI thread.
withContext(Dispatchers.IO) {
val countryName = settingsInteractor.getStationSearchCountry().let {
it.name
}
val regionName = settingsInteractor.getStationSearchRegion().let {
it.name
}
countryName to regionName
}
}
// Assign data after runBlocking has finished.
view.country_name.text = countryName
view.region_name.text = regionName
return view
}
我没有测试它,因为在onBackPressed
中使用了类似的代码。退出片段(或活动)时,应该在DB中写入一些数据。如果运行太晚,将调用onDestroy
并完成协程。在这种情况下,数据将不会写入数据库。因此,我必须停止UI线程,在后台线程中写入DB,然后返回UI。 runBlocking
效果很好。