如何使用进度条和返回函数进行API调用,同时在Android上使用Kotlin保持高效?

cqoc49vn  于 7个月前  发布在  Kotlin
关注(0)|答案(1)|浏览(63)

我正在基于从API获取的大量数据制作一个应用程序。在显示实际应用程序之前,我需要这些信息,因此我将负责获取这些信息的代码放在加载屏幕上。我将所有这些信息保存在本地数据库(Room Database)中,这样我就不必在每次启动应用程序时都获取这些信息(因为API没有太大变化),因此我可以实际使用这些信息。

**第一个问题:**当我尝试我的代码时,它会保存对象两次(或更多)(可能是因为协程)。由于我基于是否结束提取取决于发生了多少次保存 * / * 结果数量 *,这会导致Activity认为提取已经完成,停止所有调用。这意味着有些对象没有时间保存。

下面是我的代码(简化):

class FetchFromAPI {
    fun fetchObjects(save: (Object) -> Unit) {
        //Get number objects from API call
        val count = API.getCount()

        //Calculate number of pages
        val limit = 100
        var pageCount = count / limit
        if (count % limit != 0) pageCount++

        //For each page
        for (i in 1..pageCount) {
            CoroutineScope(Dispatchers.IO).launch {
                //Get page
                val page = API.getPage(i)
                //Save objects from page
                savePage(page, save)
            }
        }
    }

    private fun savePage(
        page: JSONArray,
        save: (Object) -> Unit
    ) {
        //For each object in page
        for (i in 0..page.length()) {
            CoroutineScope(Dispatchers.IO).launch {
                //Get object
                val obj = page.getJSONObject(i)
                //Save object
                saveObject(obj, save)
            }
        }
    }

    private fun saveObject(
        obj: JSONObject,
        save: (Object) -> Unit
    ) {
        //Convert Json to Object
        ...

        save(obj)
    }
}

字符串
saveObject(obj)是一个将对象保存在Room Database上并更新提取进度的函数(由Flows控制)。
所以我在谷歌上找到了关于作业和runBlocking{}join(),我想“我不需要进度的确切数字,因为它是用ProgressBar显示的,我正在处理几千个对象,所以如果我能知道我所有的协程何时完成,并基于此而不是进度来调用返回,我很好(即使多个电话浪费了一些资源)。

**第二个问题:**当我尝试使用runBlocking{}join()时,我可以启动一个函数说它已经完成,该函数要么给我一个“应用程序被冻结”错误,要么根本无法启动。

我的代码已更改(简化):

class FetchFromAPI {
    fun fetchObjects(save: (Object) -> Unit) {
        //Get number objects from API call
        val count = API.getCount()

        //Calculate number of pages
        val limit = 100
        var pageCount = count / limit
        if (count % limit != 0) pageCount++

        runBlocking {
            //For each page
            for (i in 1..pageCount) {
                launch {
                    //Get page
                    val page = API.getPage(i)
                    //Save objects from page
                    savePage(page, save)
                }
            }
        }
    }

    private fun savePage(
        page: JSONArray,
        save: (Object) -> Unit
    ) {
        //For each object in page
        runBlocking {
            for (i in 0..page.length()) {
                launch {
                    //Get object
                    val obj = page.getJSONObject(i)
                    //Save object
                    saveObject(obj, save)
                }
            }
        }
    }

    private fun saveObject(
        obj: JSONObject,
        save: (Object) -> Unit
    ) {
        //Convert Json to Object
        ...

        save(obj)
    }
}

fun fetchingFromAPI(onFinished: () -> Unit) {
    CoroutineScope(Dispatchers.IO).launch {
        fetchObjects(::save)
        onFinished()
    }
}


关于协程,join()runBlocking{},我有什么不明白的吗?
如果你还需要我的代码告诉我。

drkbr07n

drkbr07n1#

runBlocking会阻塞你调用它的线程,直到完成它的执行。可能这就是冻结错误的原因,因为你在for循环中阻塞了IO线程,然后你从那里调用了保存回调。我建议你让所有函数挂起,以便能够在没有runBlocking的情况下使用join。当一个函数挂起时,它可以挂起它的执行,并且可以加入其他协程。

class FetchFromAPI {
    suspend fun fetchObjects(save: (Object) -> Unit) {
        //Get number objects from API call
        val count = API.getCount()

        //Calculate number of pages
        val limit = 100
        var pageCount = count / limit
        if (count % limit != 0) pageCount++

        //Create a list of coroutines and join them all, so the fetchObjects 
        //will return after all the pages are saved
        (1..pageCount).map { i ->
            launch {
                //Get page
                val page = API.getPage(i)
                //Save objects from page
                savePage(page, save)
            }
        }.joinAll()
    }

    private suspend fun savePage(
        page: JSONArray,
        save: (Object) -> Unit
    ) {
        //Same logic here
        (0..page.length()).map { i ->
            launch {
                //Get object
                val obj = page.getJSONObject(i)
                //Save object
                saveObject(obj, save)
            }
        }.joinAll()
    }

    //Here if the save(obj) does not generates any new coroutine you do not need to 
    //use suspend other wise you have to use suspend and make also the save fun 
    //suspend and join the coroutine inside it.
    private fun saveObject(
        obj: JSONObject,
        save: (Object) -> Unit
    ) {
        //Convert Json to Object
        ...

        save(obj)
    }
}

字符串
现在你可以像在代码的第二部分那样从协程调用fetchObjects:

fun fetchingFromAPI(onFinished: () -> Unit) {
        CoroutineScope(Dispatchers.IO).launch {
            fetchObjects(::save)
            onFinished()
        }
    }


可能这需要一点调试,但因为我没有你的代码的其余部分,我不能这样做,所以请让我知道,如果有任何错误,或者如果不像你预期的那样工作。
在协程中切换线程的示例:

CoroutineScope(Dispatchers.IO).launch {
            //What ever is inside the fetchObjects will run on the IO thread
            fetchObjects(::save)
            withContext(Dispatchers.Main) {
                //Whatever is inside the onFinished will run on the main thread
                onFinished()
            }
        }

相关问题