Android Kotlin调用suspend函数getInfo报错,请求排查错误原因
问题:Kotlin协程错误:suspend函数只能在协程或另一个suspend函数中调用
错误信息:
suspend function 'getInfo' should be called only from a coroutine or another suspend function
WidgetService 类代码
import android.appwidget.AppWidgetManager import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent import android.util.Log import com.myapp.test.WakeLocker.acquire import com.myapp.test.WakeLocker.release import org.json.JSONObject import java.util.concurrent.ExecutionException class WidgetService : BroadcastReceiver() { var json = JSONObject() override fun onReceive(context: Context, intent: Intent) { //唤醒设备 acquire(context) //更新widget中的数值 val preferences = context.getSharedPreferences("PREFS", 0) val urlCode = preferences.getString("urlCode", "") Log.d("WidgetService1", urlCode!!) if (urlCode != "") { val getPack = GetPack(urlCode) json = try { getPack.getInfo() } catch (e: ExecutionException) { throw RuntimeException(e) } catch (e: InterruptedException) { throw RuntimeException(e) } val editor = preferences.edit() editor.putString("json", json.toString()) editor.apply() } //强制更新widget val widgetIntent = Intent(context, MyWidget::class.java) widgetIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE val ids = AppWidgetManager.getInstance(context) .getAppWidgetIds(ComponentName(context, MyWidget::class.java)) widgetIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) context.sendBroadcast(widgetIntent) Log.d("WidgetService", "Widget set to update!") //释放唤醒锁 release() } }
GetPack 类代码
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONArray import org.json.JSONObject import org.jsoup.Jsoup import java.io.IOException class GetPack(id: String) { private val baseURL = "http://192.xxx.xxx.xxx" private val finalURL = "$baseURL:8080" suspend fun getInfo(): JSONObject = withContext(Dispatchers.IO) { val json = JSONObject() try { val document = Jsoup.connect(finalURL).get() val steps = JSONArray() val elements = document.select("#progressTracker-container > div") for (ele in elements) { val item = JSONObject() val reached = ele.attr("data-reached") == "reached" val message = ele.select(".milestone-primaryMessage").first().ownText() val data = ele.select(".milestone-primaryMessage > .nowrap").text() item.put("reached", reached) item.put("message", message) item.put("data", data) steps.put(item) } val primaryStatus = document.getElementById("primaryStatus") json.put("status", primaryStatus.text()) json.put("steps", steps) } catch (e: IOException) { e.printStackTrace() } catch (e: Exception) { throw RuntimeException(e) } json } }
依赖配置
dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation 'org.jsoup:jsoup:1.11.2' }
问题原因
getInfo()是标记为suspend的协程函数,这类函数的调用必须在协程作用域内,或者在另一个suspend函数中。但你直接在BroadcastReceiver的onReceive()(普通非suspend函数)里调用它,违反了协程的调用规则。另外你错误捕获了ExecutionException和InterruptedException——这两个是Java线程池相关异常,和Kotlin协程无关。
解决步骤
添加协程依赖
你的gradle配置中缺少Kotlin协程的Android依赖,先补充:implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'在onReceive中启动协程调用suspend函数
使用自定义协程作用域在onReceive()内启动协程,处理耗时的网络请求。同时调整唤醒锁、widget更新的逻辑,确保异步任务完成后再执行收尾操作:import android.appwidget.AppWidgetManager import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent import android.util.Log import com.myapp.test.WakeLocker.acquire import com.myapp.test.WakeLocker.release import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.json.JSONObject class WidgetService : BroadcastReceiver() { // 创建绑定IO调度器的协程作用域 private val coroutineScope = CoroutineScope(Dispatchers.IO) override fun onReceive(context: Context, intent: Intent) { acquire(context) val preferences = context.getSharedPreferences("PREFS", 0) val urlCode = preferences.getString("urlCode", "") ?: "" Log.d("WidgetService1", urlCode) if (urlCode.isNotEmpty()) { coroutineScope.launch { try { val json = GetPack(urlCode).getInfo() val editor = preferences.edit() editor.putString("json", json.toString()) editor.apply() } catch (e: Exception) { Log.e("WidgetService", "获取数据失败", e) } finally { updateWidget(context) release() } } } else { updateWidget(context) release() } } private fun updateWidget(context: Context) { val widgetIntent = Intent(context, MyWidget::class.java) widgetIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE val ids = AppWidgetManager.getInstance(context) .getAppWidgetIds(ComponentName(context, MyWidget::class.java)) widgetIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids) context.sendBroadcast(widgetIntent) Log.d("WidgetService", "Widget set to update!") } }优化GetPack的异常处理
可以将返回值改为可空类型,避免未处理的异常导致崩溃:suspend fun getInfo(): JSONObject? = withContext(Dispatchers.IO) { val json = JSONObject() return@withContext try { val document = Jsoup.connect(finalURL).get() val steps = JSONArray() val elements = document.select("#progressTracker-container > div") for (ele in elements) { val item = JSONObject() val reached = ele.attr("data-reached") == "reached" val message = ele.select(".milestone-primaryMessage").first()?.ownText() ?: "" val data = ele.select(".milestone-primaryMessage > .nowrap").text() item.put("reached", reached) item.put("message", message) item.put("data", data) steps.put(item) } val primaryStatus = document.getElementById("primaryStatus") json.put("status", primaryStatus?.text() ?: "") json.put("steps", steps) json } catch (e: IOException) { e.printStackTrace() null } catch (e: Exception) { Log.e("GetPack", "解析失败", e) null } }
内容的提问来源于stack exchange,提问作者Paul
相关产品推荐
相关产品推荐

