如何在kotlin中使用Coroutine每秒调用一个函数

问题描述 投票:0回答:6

我刚刚创建了一个应用程序,我的函数 getdata() 每秒调用一次以从服务器获取新数据,而 updateui() 函数将更新 UI 中的视图 我在我的应用程序中不使用任何异步任务或协程 我想这样做,请告诉我怎样才能做到这一点。

这是我的代码...

private fun getdata(){
        try {
            val api = RetroClient.getApiService()
            call = api.myJSON
            call!!.enqueue(object : Callback<ProductResponse> {
                override fun onResponse(
                    call: Call<ProductResponse>,
                    response: Response<ProductResponse>
                ) {
                    if (response.isSuccessful) {
                        productList = response.body()!!.data
                        for (list in productList) {
                            if (list.BB.equals("AAA")) {
                                aProductList.add(list)
                            }
                        }
                        if (recyclerView.adapter != null) {
                            eAdapter!!.updatedata(aProductList)
                        }
                        updateui()
                    }
                }

                override fun onFailure(call: Call<ProductResponse>, t: Throwable) {
                    println("error")
                }
            })
        } catch (ex: Exception) {
        } catch (ex: OutOfMemoryError) {
        }
Handler().postDelayed({
            getdata()
        }, 1000)
}


private fun updateui() {
        try {
            //some code to handel ui
 } catch (e: NumberFormatException) {

        } catch (e: ArithmeticException) {

        } catch (e: NullPointerException) {

        } catch (e: Exception) {

        }
    }
android kotlin coroutine
6个回答
31
投票

使用协程每秒运行一个函数:

val scope = MainScope() // could also use an other scope such as viewModelScope if available
var job: Job? = null

fun startUpdates() {
    stopUpdates()
    job = scope.launch {
        while(true) {
            getData() // the function that should be ran every second
            delay(1000)
        }
    }
}

fun stopUpdates() {
    job?.cancel()
    job = null
}

但是,如果

getData()
starts 网络请求并且不等待其完成,这可能不是一个好主意。该函数将在完成后一秒被调用,但由于网络请求是异步完成的,因此可能会被调度得太多。
例如,如果网络请求需要 5 秒,那么在第一个请求完成之前,它还会启动 4 次!

要解决此问题,您应该找到一种方法来挂起协程,直到网络请求完成。
这可以通过使用 blocking api 来完成,然后将

Dispatchers.IO
传递给
launch
函数以确保它在后台线程上完成。

或者,您可以使用 suspendCoroutine 将基于回调的 api 转换为挂起的 api。


更新 - 生命周期范围

在具有 Android 生命周期的组件内,您可以使用以下代码来自动重复 ui 更新:

fun startUpdates() {
    val lifecycle = this // in Activity
    val lifecycle = viewLifecycleOwner // in Fragment

    lifecycle.lifecycleScope.launch {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // this block is automatically executed when moving into
            // the started state, and cancelled when stopping.
            while (true) {
                getData() // the function to repeat
                delay(1000)
            }
        }
    }
}

此代码需要当前的

androidx.lifecycle:lifecycle-runtime-ktx
依赖项。

上面关于 getData() 内的异步、阻塞或挂起代码的评论仍然适用。


22
投票

不建议每秒都访问服务器。如果您需要连续获取数据,请尝试套接字。因为有时您的服务器需要花费几秒钟的时间来响应您的请求。那么您的所有请求都将排在队列中..如果您仍然需要尝试此操作。

fun repeatFun(): Job {
    return coroutineScope.launch {  
        while(isActive) {
            //do your network request here
            delay(1000)
        }
    }
}

//start the loop
val repeatFun = repeatFun()

//Cancel the loop
repeatFun.cancel()

8
投票

对于刚接触协程的人

在Build.gradle中添加协程

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'

创建重复作业

    /**
     * start Job
     * val job = startRepeatingJob()
     * cancels the job and waits for its completion
     * job.cancelAndJoin()
     * Params
     * timeInterval: time milliSeconds 
     */
    private fun startRepeatingJob(timeInterval: Long): Job {
        return CoroutineScope(Dispatchers.Default).launch {
            while (NonCancellable.isActive) {
                // add your task here
                doSomething()
                delay(timeInterval)
            }
        }
    }

开始:

  Job myJob = startRepeatingJob(1000L)

停止:

    myJob .cancel()

5
投票

我最终用扩展函数做了这样的事情:

fun CoroutineScope.launchPeriodicAsync(repeatMillis: Long, action: () -> Unit) = this.async {
  while (isActive) {
    action()
    delay(repeatMillis)
  }
}

然后这样称呼它:

val fetchDatesTimer = CoroutineScope(Dispatchers.IO)
  .launchPeriodicAsync(TimeUnit.MINUTES.toMillis(1)) {
    viewModel.fetchDeliveryDates()
  }

并取消它,例如:

fetchDatesTimer.cancel()

4
投票

我在 MainViewModel 中的 Kotlin 解决方案

fun apiCall() {
       viewModelScope.launch(Dispatchers.IO) {
         while(isActive) {
            when(val response = repository.getServerData()) {
                is NetworkState.Success -> {
                    getAllData.postValue(response.data)
                }
                is NetworkState.Error -> [email protected] = false
            }

            delay(1000)
        }
    }
}


sealed class NetworkState<out R> {
    data class Success<out T>(val data: T): NetworkState<T>()
    data class Error(val exception: String): NetworkState<Nothing>()
    object Loading: NetworkState<Nothing>()
}

0
投票

我的解决方案是一次运行代码来检查是否启用了某些功能(例如蓝牙),如果没有启用,则定期自动检查。功能是:

fun CoroutineScope.launchPeriodic(repeatMillis: Long, action: () -> Unit) : Job {
            return launch {
                while (!enabled) {
                    action()
                    delay(repeatMillis)
                }
            }
        }

为了启动这个周期性函数,代码在这里(其中动作每 2 秒运行一次)。当启用该功能并且运行一些代码(放置在注释文本的位置)时,它会自动结束:

CoroutineScope(Dispatchers.IO).launchPeriodic(TimeUnit.SECONDS.toMillis(2)) {
           if(checkIfSomethingIsEnabledCodeIsHere) {
               enabled = true
               //some code here to run when it is enabled
           }
        }
© www.soinside.com 2019 - 2024. All rights reserved.