提问人:Damon 提问时间:11/4/2023 更新时间:11/5/2023 访问量:65
如何使用 Kotlin 在 Android 上高效使用进度条和返回函数进行 API 调用?
How can I make API calls with a progress bar and a return function while being efficient on Android using Kotlin?
问:
我正在根据我从 API 获得的大量数据制作一个应用程序。在显示实际应用程序之前,我需要这些信息,因此我将负责获取这些信息的代码放在加载屏幕上。我将其全部保存在本地数据库(Room Database)上,因此我不必在每次启动应用程序时都获取它(因为 API 没有太大变化),因此我实际上可以使用这些信息。
第一个问题:当我尝试我的代码时,它会保存两次(或更多)对象(可能是因为协程)。而且由于我基于提取是否结束取决于发生了多少次保存/结果数量,这导致活动认为获取已完成,停止所有调用。这意味着有些对象没有时间保存。
这是我的代码(简化):
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)
是一个函数,用于将对象保存在房间数据库上并更新提取进度(由 Flow 控制)。
所以我用谷歌搜索并了解了工作,我想“我不需要一个确切的进度数字,因为它是用 ProgressBar 显示的,而且我正在处理几千个对象,所以如果我能知道我所有的协程何时完成,并基于这个而不是进度的返回调用, 我很好(即使多次调用浪费了一些资源)”。join()
runBlocking{}
第二个问题:当我尝试使用 or 时,我可以启动一个功能说它已经完成,该功能要么给我一个“应用程序已冻结”错误,要么根本无法启动。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{}
告诉我你是否需要从我的代码中获取任何其他内容。
答:
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()
}
}
评论
val dispatcher = Dispatcher() dispatcher.setMaxRequests(set max here) dispatcher.setMaxRequestsPerHost(set max per host here) val client = OkHttpClient.Builder() .dispatcher(dispatcher) .build();
评论