如何在Android Worker中完成Kotlin流

如何在Android Worker中完成Kotlin流,android,kotlin-flow,Android,Kotlin Flow,我正在调查Kotlin Flow在我当前Android应用程序中的使用情况 我的应用程序通过改造API调用从远程服务器检索数据 其中一些API在500个项目页面中返回50000个数据项目 每个API响应都包含一个HTTP链接头,其中包含下一页完整的URL 这些呼叫可能需要2秒钟才能完成 为了减少运行时间,我使用了Kotlin流来并发处理每个页面 在进行下一页API调用的同时调用数据 我的流程定义如下: private val persistenceThreadPool = Executors.n

我正在调查Kotlin Flow在我当前Android应用程序中的使用情况

我的应用程序通过改造API调用从远程服务器检索数据

其中一些API在500个项目页面中返回50000个数据项目

每个API响应都包含一个HTTP链接头,其中包含下一页完整的URL

这些呼叫可能需要2秒钟才能完成

为了减少运行时间,我使用了Kotlin流来并发处理每个页面 在进行下一页API调用的同时调用数据

我的流程定义如下:

private val persistenceThreadPool = Executors.newFixedThreadPool(3).asCoroutineDispatcher()
private val internalWorkWorkState = MutableStateFlow<Response<List<MyPage>>?>(null)
private val workWorkState = internalWorkWorkState.asStateFlow()

private val myJob: Job

init {
    myJob = GlobalScope.launch(persistenceThreadPool) {
        workWorkState.collect { page ->
            if (page == null) {
            } else managePage(page!!)
        }
    }
}
private val persistenceThreadPool=Executors.newFixedThreadPool(3.ascoroutineddispatcher())
private val internalWorkWorkState=MutableStateFlow(null)
private val workstate=internalworkstate.asStateFlow()
我的工作:工作
初始化{
myJob=GlobalScope.launch(persistenceThreadPool){
workstate.collect{page->
如果(第==null页){
}else managePage(第页!!)
}
}
}
我的递归函数定义如下,用于获取所有页面:-

    private suspend fun managePages(accessToken: String, response: Response<List<MyPage>>) {
        when {
            result != null -> return
            response.isSuccessful -> internalWorkWorkState.emit(response)
            else -> {
                manageError(response.errorBody())
                result = Result.failure()
                return
            }
        }

        response.headers().filter { it.first == HTTP_HEADER_LINK && it.second.contains(REL_NEXT) }.forEach {
            val parts = it.second.split(OPEN_ANGLE, CLOSE_ANGLE)
            if (parts.size >= 2) {
                managePages(accessToken, service.myApiCall(accessToken, parts[1]))
            }
        }
    }

   private suspend fun managePage(response: Response<List<MyPage>>) {
        val pages = response.body()

        pages?.let {
            persistResponse(it)
        }
    }

    private suspend fun persistResponse(myPage: List<MyPage>) {
        val myPageDOs = ArrayList<MyPageDO>()

        myPage.forEach { page ->
            myPageDOs.add(page.mapDO())
        }

        database.myPageDAO().insertAsync(myPageDOs)
    }
    
private页面(accessToken:String,response:response){
什么时候{
结果!=null->return
response.issusccessful->internalworkstate.emit(响应)
其他->{
manageError(response.errorBody())
result=result.failure()
返回
}
}
response.headers().filter{it.first==HTTP\u HEADER\u LINK&&it.second.contains(REL\u NEXT)}.forEach{
val parts=it.second.split(打开角度、关闭角度)
如果(零件尺寸>=2){
managePages(accessToken,service.myApiCall(accessToken,parts[1]))
}
}
}
专用暂停页面(响应:响应){
val pages=response.body()
页面?让我们{
持续响应(it)
}
}
私有暂停响应(myPage:列表){
val myPageDOs=ArrayList()
myPage.forEach{page->
myPageDOs.add(page.mapDO())
}
database.myPageDAO().insertAsync(myPageDOs)
}
我的许多问题是

  • 此代码不会插入我检索的所有数据项

  • 检索完所有数据项后,如何完成流程

  • 检索并持久化所有数据项后,如何完成GlobalScope作业

  • 更新

    通过进行以下更改,我成功地插入了所有数据

     private val persistenceThreadPool = Executors.newFixedThreadPool(3).asCoroutineDispatcher()
        private val completed = CompletableDeferred<Int>()
    
        private val channel = Channel<Response<List<MyPage>>?>(UNLIMITED)
        private val channelFlow = channel.consumeAsFlow().flowOn(persistenceThreadPool)
    
        private val frank: Job
    
        init {
            frank = GlobalScope.launch(persistenceThreadPool) {
                channelFlow.collect { page ->
                    if (page == null) {
                        completed.complete(totalItems)
                    } else managePage(page!!)
                }
            }
        }
    
    
    ...
    ...
    ...
    
       channel.send(null)
       completed.await()
    
       return result ?: Result.success(outputData)
    
    private val persistenceThreadPool=Executors.newFixedThreadPool(3.ascoroutineddispatcher())
    private val completed=CompletableDeferred()
    专用val通道=通道(无限制)
    private val channelFlow=channel.consumeAsFlow().floon(persistenceThreadPool)
    二等兵瓦尔·弗兰克:工作
    初始化{
    frank=GlobalScope.launch(persistenceThreadPool){
    channelFlow.collect{page->
    如果(第==null页){
    已完成。已完成(总计项)
    }else managePage(第页!!)
    }
    }
    }
    ...
    ...
    ...
    channel.send(空)
    已完成。等待()
    返回结果?:result.success(outputData)
    

    我不喜欢依赖于
    CompletableDeferred
    ,有没有比这更好的方法来了解流何时完成了所有事情?

    有几种方法可以实现所需的行为。我建议使用专门为并行分解设计的。它还提供了良好的即时取消和错误处理行为。与之相结合使实现变得非常简单。从概念上讲,实现可能如下所示:

     suspend fun fetchAllPages() {
        coroutineScope {
            val channel = Channel<MyPage>(Channel.UNLIMITED)
            launch(Dispatchers.IO){ loadData(channel) }
            launch(Dispatchers.IO){ processData(channel) }
        }
    }
    
    suspend fun loadData(sendChannel: SendChannel<MyPage>){
        while(hasMoreData()){
            sendChannel.send(loadPage())
        }
        sendChannel.close()
    }
    
    suspend fun processData(channel: ReceiveChannel<MyPage>){
        for(page in channel){
            // process page
        }
    }
    
    suspend fun fetchAllPages(){
    共线镜{
    val通道=通道(通道无限)
    启动(Dispatchers.IO){loadData(channel)}
    启动(Dispatchers.IO){processData(channel)}
    }
    }
    暂停趣味加载数据(sendChannel:sendChannel){
    while(hasMoreData()){
    sendChannel.send(loadPage())
    }
    sendChannel.close()
    }
    暂停处理数据(通道:接收通道){
    用于(频道中的页面){
    //进程页
    }
    }
    
    其工作方式如下:

  • coroutineScope
    暂停,直到所有子项都完成。因此,您不再需要
    CompletableDeferred
  • loadData()
    循环加载页面并将其发布到频道中。加载所有页面后,它会立即关闭频道
  • processData
    逐个从频道获取项目并对其进行处理。一旦处理完所有项目(且通道已关闭),该循环将立即结束
  • 在这个实现中,生产者协同程序独立工作,没有背压,因此如果处理速度慢,它可能会占用大量内存。限制缓冲区容量,以便在缓冲区已满时暂停生产者协同程序。 使用行为启动多个处理器以加快计算速度也是一个好主意。

    您正在寻找和:

    suspend fun getData():Flow=Flow{
    var页面数据:列表
    变量pageUrl:字符串?=“bla”
    while(pageUrl!=null){
    TODO(“从pageUrl获取pageData并将pageUrl更改为下一页”)
    emitAll(页面数据)
    }
    }
    .floon(Dispatchers.IO/*不需要线程池执行器,IO会自动执行它*/)
    .缓冲区(3)
    
    您可以像普通流、迭代等一样使用它。如果您想知道输出的总长度,应该使用可变闭包变量在使用者上计算它。注意,您不需要在任何地方使用GlobalScope(理想情况下永远都不需要)