简体   繁体   English

等到 Kotlin 协程在 onCreateView() 中完成

[英]Wait until Kotlin coroutine finishes in onCreateView()

I have an initialization block in onCreateView , where some variables are assigned from SharedPreferences, DB or Network (currently from SharedPreferences).我在onCreateView中有一个初始化块,其中一些变量是从 SharedPreferences、DB 或网络(当前来自 SharedPreferences)分配的。

I want to update views with these values in onViewCreated .我想在onViewCreated中使用这些值更新视图。 But they update with empty values before a coroutine in onCreateView finishes.但是在onCreateView中的协程完成之前,它们会更新为空值。 How to wait until the coroutine finishes in main thread?如何等到协程在主线程中完成?

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)
}

UPDATE (20-05-2019)更新 (20-05-2019)

Currently I don't use onViewCreated .目前我不使用onViewCreated I write code in onCreate and onCreateView .我在onCreateonCreateView中编写代码。 In onCreateView I access views this way: view.country.text = "abc" and so on.onCreateView中,我以这种方式访问视图: view.country.text = "abc"等等。

It's better to use async instead of launch https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html .最好使用async而不是launch https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html

Create lateinit val dataLoad: Deferred<Pair<String, String> .创建lateinit val dataLoad: Deferred<Pair<String, String> init it as dataLoad = GlobalScope.async(Dispatchers.Defailt) {} in onCreateView or earlier.onCreateView或更早版本中将其初始化为dataLoad = GlobalScope.async(Dispatchers.Defailt) {} launch new coroutine in UI scope in onViewCreated .onViewCreated的 UI 范围内launch新协程。 Wait for result in that coroutine val data = dataLoad.await() and then apply data to ui等待协程val data = dataLoad.await()中的结果,然后将数据应用于 ui

In your case you don't need to use GlobalScope as a coroutine context (you can but it is not recommended as per docs).在您的情况下,您不需要将GlobalScope用作协程上下文(您可以,但根据文档不建议这样做)。 You need a local scope:您需要一个本地范围:

private var job: Job = Job()
override val coroutineContext: CoroutineContext
    get() = Dispatchers.Main + job

@Override
public void onDestroy() {
    super.onDestroy();
    job.cancel()
}

Also your fragment should implement CoroutineScope and to use Dispatchers.Main in Android add dependency to app's build.gradle:此外,您的片段应实现CoroutineScope并在 Android 中使用Dispatchers.Main添加对应用程序的 build.gradle 的依赖:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'

The code to wait until the coroutine finishes:等待协程完成的代码:

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)
    }
}

EDIT:编辑:

In Activity or Fragment you can use lifecycleScope instead of implementing CoroutineScope :ActivityFragment中,您可以使用lifecycleScope而不是实现CoroutineScope

lifecycleScope.launch { ... }

To use lifecycleScope add next line to dependencies of the app's build.gradle file:要使用lifecycleScope ,请将下一行添加到应用程序的build.gradle文件的依赖项中:

implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION"

At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05"在撰写本文时 final LIFECYCLE_VERSION = "2.3.0-alpha05"

One way you can do it, is to use launch into main thread 一种方法是在主线程中使用启动

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)
     }
   }
}

Use runBlocking .使用runBlocking In this case you can block UI thread until coroutine completion.在这种情况下,您可以阻塞 UI 线程直到协程完成。 Of course, you are to be sure it will be fast enough (less than 3 sec) to not raise ANR.当然,您要确保它足够快(少于 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
}

I didn't test it, because use similar code in onBackPressed .我没有测试它,因为在onBackPressed中使用了类似的代码。 When I quit a fragment (or an activity) I should write some data in DB.当我退出片段(或活动)时,我应该在数据库中写入一些数据。 If running too late, onDestroy will be called and coroutines will be finished.如果运行得太晚,将调用onDestroy并结束协程。 In this case data won't be written to DB.在这种情况下,数据不会写入数据库。 So, I have to stop UI thread, write to DB in background thread, then return to UI.所以,我必须停止 UI 线程,在后台线程中写入 DB,然后返回到 UI。 runBlocking works well. runBlocking运行良好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM