Kotlin协程API调用问题:UI先加载且距离计算返回Null
解决Kotlin协程+OkHttp异步导致返回值为Null的问题
我一眼就看出你遇到的核心问题了:OkHttp的异步回调和方法同步返回的矛盾。你原来的getPostcodeLocation方法里,client.newCall(request).enqueue()是异步执行的——也就是说,方法会直接跳过回调代码,先执行到return distance这一行,而此时回调还没跑完,distance还是初始的null,所以UI拿到的就是空值,自然渲染不出正确结果。
下面我给你一步步拆解解决方案,用协程把异步代码改成"看起来像同步"的写法,彻底解决这个问题:
第一步:把方法改成协程挂起函数
我们需要把getPostcodeLocation标记为suspend函数,并用withContext切换到IO线程执行网络请求(Android主线程不能做网络操作),同时把OkHttp的异步调用改成同步调用(在IO线程里同步执行不会阻塞UI):
private suspend fun getPostcodeLocation(listOfPostCodes: List<Pair<Boolean, String>>): String { val jsonList = JSONObject() jsonList.put("postcodes", JSONArray(listOfPostCodes.map { it.second })) val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonList.toString()) val request = okhttp3.Request.Builder() .url("https://api.postcodes.io/postcodes") .post(body) .build() val client = OkHttpClient() return withContext(Dispatchers.IO) { try { // 用execute()同步执行请求,代替异步的enqueue() val response = client.newCall(request).execute() val responseBody = response.body()?.string() ?: return@withContext "Not Available" val jsonTree = JsonParser().parse(responseBody).asJsonObject val resultJsonArray = jsonTree.getAsJsonArray("result") val listOfLonLat = mutableListOf<PostcodeLongLat>() resultJsonArray.forEach { val resultObject = it.asJsonObject.getAsJsonObject("result") val lat = resultObject.get("latitude").asDouble val lon = resultObject.get("longitude").asDouble listOfLonLat.add(PostcodeLongLat(lon, lat)) } // 计算距离后返回结果 calculateDistance(listOfLonLat) } catch (e: IOException) { Log.d("PostCodeChecker", "Failed to get the information from the API", e) "Not Available" } } }
第二步:在Activity的协程作用域里调用挂起函数
因为挂起函数不能在普通函数里直接调用,我们需要用Activity的生命周期协程作用域lifecycleScope(需要依赖androidx.lifecycle:lifecycle-runtime-ktx库)来调用它,拿到结果后再更新UI:
// 比如在Activity的onCreate方法里,或者按钮点击事件中 lifecycleScope.launch { // 这里会挂起直到getPostcodeLocation返回结果 val distanceResult = getPostcodeLocation(yourPostCodeList) // 拿到结果后再更新UI,确保UI渲染的是正确值 yourDistanceTextView.text = distanceResult }
为什么这样改能解决问题?
- 挂起函数的特性:
suspend函数会在执行到耗时操作(比如网络请求)时挂起,直到操作完成再返回结果,不会像原来的异步回调那样让方法提前返回null。 - 线程切换安全:
withContext(Dispatchers.IO)把网络请求放到IO线程执行,既遵守了Android的网络操作规则,又不会阻塞主线程导致UI卡顿。 - 生命周期绑定:
lifecycleScope会和Activity的生命周期同步,避免Activity销毁后协程还在执行导致内存泄漏。
这样修改后,你就能确保UI在拿到正确的距离结果后再进行渲染,再也不会出现返回null的问题了。
内容的提问来源于stack exchange,提问作者OrangeSubmarine121
相关产品推荐
相关产品推荐

