You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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协程无关。

解决步骤

  1. 添加协程依赖
    你的gradle配置中缺少Kotlin协程的Android依赖,先补充:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    
  2. 在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!")
        }
    }
    
  3. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:14:55