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
绑定到主线程。你想用 GlobalScope
或 coroutineScope
替换它(后者在复杂项目方面更好,但我假设你现在正在写 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) { ... }
}
当我尝试从某些 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
绑定到主线程。你想用 GlobalScope
或 coroutineScope
替换它(后者在复杂项目方面更好,但我假设你现在正在写 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) { ... }
}