Kotlin 中的异步请求 Android

Async requests in Kotlin Android

当我尝试从某些 api 获取信息时,我经常遇到错误 android.os.NetworkOnMainThreadException。我知道这个问题与主 android 线程有关,但我不明白如何解决它 - 协程、异步 okhttp 或两者兼而有之? P.S我的英语不好,抱歉。

我的代码:

MainAtivity.kt

class MainActivity: AppCompatActivity(), Alert {
    private lateinit var binding: ActivityMainBinding
    lateinit var api: ApiWeather
    var okHttpClient: OkHttpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        api = ApiWeather(okHttpClient)

        binding.buttonGetWeather.setOnClickListener {
            val cityInput = binding.textInputCity.text.toString()
            if (cityInput.isEmpty()) {
                errorAlert(this, "...").show()
            } else {
                val city = "${cityInput.lowercase()}"
                val limit = "1"
                val appId = "key"
                val urlGeocoding = "http://api.openweathermap.org/geo/1.0/direct?" +
                        "q=$city&limit=$limit&appid=$appId"

                var status = false
                val coordinates: MutableMap<String, Double> = mutableMapOf()
                val job1: Job = lifecycleScope.launch {
                        val geo = api.getGeo(urlGeocoding)
                        if (geo != null) {
                            coordinates["lat"] = geo.lat
                            coordinates["lon"] = geo.lon
                            status = true
                        } else {
                            status = false
                        }
                }
                val job2: Job = lifecycleScope.launch {
                    job1.join()
                    when(status) {
                        false -> {
                            binding.textviewTempValue.text = ""
                            errorAlert(this@MainActivity, "...").show()
                        }
                        true -> {
                            val urlWeather = "https://api.openweathermap.org/data/2.5/weather?" +
                                    "lat=${coordinates["lat"]}&lon=${coordinates["lon"]}&units=metric&appid=${appId}"
                            val weather = api.getTemp(urlWeather)
                            binding.textviewTempValue.text = weather.main.temp.toString()
                        }
                    }
                }
            }
        }
    }
}

Api.kt

class ApiWeather(cl: OkHttpClient) {
    private val client: OkHttpClient

    init {
        client = cl
    }

    suspend fun getGeo(url: String): GeocodingModel? {
        val request: Request = Request.Builder()
            .url(url)
            .build()
        val responseStr = client.newCall(request).await().body?.string().toString()
        val json = Json {
            ignoreUnknownKeys = true
        }
        return try {
            json.decodeFromString<List<GeocodingModel>>(responseStr)[0]
        } catch (e: Exception) {
            return null
        }
    }

    suspend fun getTemp(url: String): DetailWeatherModel {
        val request: Request = Request.Builder()
            .url(url)
            .build()
        val responseStr = client.newCall(request).await().body?.string().toString()
        val json = Json {
            ignoreUnknownKeys = true
        }
        return json.decodeFromString<DetailWeatherModel>(responseStr)
    }
}

您已经在使用协程。问题是 lifecycleScope 绑定到主线程。你想用 GlobalScopecoroutineScope 替换它(后者在复杂项目方面更好,但我假设你现在正在写 pet-project,所以 GlobalScope.launch 就可以了)

你应该更换

lifecycleScope.launch{

lifecycleScope.launch(Dispatchers.IO){

问题是 api.getGeo(urlGeocoding) 运行 在当前线程中。 lifecycleScope.launch {} 默认有 Dispatchers.Main 上下文,所以调用 api 函数将在主线程上 运行。要在后台线程中使其成为 运行,您需要使用 withContext(Dispatchers.IO) 切换上下文。它将如下所示:

lifecycleScope.launch {
      val geo = withContext(Dispatchers.IO) { api.getGeo(urlGeocoding) }
      if (geo != null) {
           coordinates["lat"] = geo.lat
           coordinates["lon"] = geo.lon
           status = true
      } else {
           status = false
      }

      when(status) { ... }           
}